Skip to content

Commit

Permalink
Merge pull request #1367 from gethinode/develop
Browse files Browse the repository at this point in the history
Improve rendering of TOC items
  • Loading branch information
markdumay authored Jan 16, 2025
2 parents dd80ee2 + e52d14e commit 2ab8ee6
Show file tree
Hide file tree
Showing 10 changed files with 274 additions and 40 deletions.
29 changes: 29 additions & 0 deletions assets/scss/components/_toc.scss
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,35 @@
}
}

a.toc-item {
display: block;
}

a.toc-level-1 {
margin-left: 0;
}

a.toc-level-2 {
margin-left: 1em;
}

a.toc-level-3 {
margin-left: 2em;
}

a.toc-level-4 {
margin-left: 3em;
}

a.toc-level-5 {
margin-left: 4em;
}

a.toc-level-6 {
margin-left: 5em;
}


@if $enable-dark-mode {
[data-bs-theme="dark"] {
.toc-button {
Expand Down
7 changes: 4 additions & 3 deletions exampleSite/hugo_stats.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"footer",
"form",
"h2",
"h3",
"head",
"hr",
"html",
Expand Down Expand Up @@ -252,6 +251,7 @@
"font-monospace",
"footer",
"form-control",
"fs-2",
"fs-3",
"fs-5",
"fs-6",
Expand Down Expand Up @@ -357,7 +357,6 @@
"p-2",
"p-3",
"p-4",
"pb-2",
"pb-3",
"pb-4",
"pb-5",
Expand All @@ -373,6 +372,7 @@
"ps-1",
"ps-3",
"pt-1",
"pt-4",
"pt-5",
"pt-md-3",
"ptw-3",
Expand Down Expand Up @@ -469,6 +469,8 @@
"toast-header",
"toc",
"toc-button",
"toc-item",
"toc-level-1",
"toc-panel",
"toc-sidebar",
"toggler-icon",
Expand All @@ -485,7 +487,6 @@
"w-50"
],
"ids": [
"TableOfContents",
"abbr",
"accordion",
"accordion-0",
Expand Down
2 changes: 1 addition & 1 deletion layouts/partials/assets/persona.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
{{- $title := .title -}}
{{- $content := .content -}}

<h3>{{ $title }}</h3>
<div class="fs-3 mt-4">{{ $title }}</div>
{{ with $content }}<p>{{ . }}</p>{{ end -}}
{{- end -}}

Expand Down
76 changes: 50 additions & 26 deletions layouts/partials/assets/toc-dropdown.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,60 @@
Visit gethinode.com/license for more details.
-->

<!-- Initialize arguments -->
{{ $error := false }}

<!-- Validate arguments -->
{{ if partial "utilities/IsInvalidArgs.html" (dict "structure" "toc" "args" . "group" "partial") }}
{{- errorf "partial [assets/toc-dropdown.html] - Invalid arguments" -}}
{{ $args := partial "utilities/InitArgs.html" (dict "structure" "toc" "args" . "group" "partial") }}
{{ if $args.err }}
{{ partial "utilities/LogErr.html" (dict
"partial" "assets/schema.html"
"msg" "Invalid arguments"
"details" $args.errmsg
"file" page.File
)}}
{{ $error = true }}
{{ end }}

<!-- Initialize arguments -->
{{- $page := .page -}}
{{ $items := len (findRE "<li.*?>(.|\n)*?</li>" $page.TableOfContents) -}}
<!-- Initialize local arguments -->
{{- $startLevel := or (.Site.Params.navigation.startLevel | int) 2 }}
{{- $endLevel := or (.Site.Params.navigation.endLevel | int) 3 }}
{{- $minNumHeadings := or (.Site.Params.navigation.minNumHeadings | int) 2 }}

{{ if and (not $error) (gt $items 1) -}}
<div class="d-grid gap-2 mx-auto">
{{ partial "assets/button.html" (dict
"title" (T "toc")
"color" "secondary"
"outline" "true"
"class" "toc-button"
"icon" "fas sort"
"justify" "between"
"collapse" "toc-collapse"
"order" "last"
"spacing" false
) -}}
</div>
<!-- Main code -->
{{ if not $error }}
{{ $headings := partial "assets/toc-headings.html" $args.page }}
{{- with $headings }}
{{- if and (not $error) (ge (len .) $minNumHeadings) }}
<div class="d-grid gap-2 mx-auto">
{{ partial "assets/button.html" (dict
"title" (T "toc")
"color" "secondary"
"outline" "true"
"class" "toc-button"
"icon" "fas sort"
"justify" "between"
"collapse" "toc-collapse"
"order" "last"
"spacing" false
) -}}
</div>

<div class="collapse border bg-body-tertiary rounded p-1 navbar-nav-scroll" id="toc-collapse">
<div class="toc toc-panel text-body p-2">
<small>{{ $page.TableOfContents }}</small>
<div class="collapse border bg-body-tertiary rounded p-1 navbar-nav-scroll" id="toc-collapse">
<small>
<div class="toc toc-panel text-body p-2">
{{- range . }}
{{- $attrs := dict "class" (printf "toc-item toc-level-%d" (add 1 (sub .level $startLevel))) }}
{{- with .id }}
{{- $attrs = merge $attrs (dict "href" (printf "%s#%s" $args.page.RelPermalink .)) }}
{{- end }}
<a
{{- range $k, $v := $attrs }}
{{- printf " %s=%q" $k $v | safeHTMLAttr }}
{{- end -}}
>{{ .text }}</a>
{{- end }}
</div>
</small>
</div>
</div>
{{ end -}}
{{- end }}
{{- end }}
{{- end }}
75 changes: 75 additions & 0 deletions layouts/partials/assets/toc-headings.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{{- /*
Copyright 2023 Veriphor, LLC

Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
*/}}

{{- /* Initialize. */}}
{{- $partialName := "toc-headings" }}

{{- /* Verify minimum required version. */}}
{{- $minHugoVersion := "0.125.6" }}
{{- if lt hugo.Version $minHugoVersion }}
{{- errorf "The %q partial requires Hugo v%s or later." $partialName $minHugoVersion }}
{{- end }}

{{- /* Determine content path for warning and error messages. */}}
{{- $contentPath := "" }}
{{- with .File }}
{{- $contentPath = .Path }}
{{- else }}
{{- $contentPath = .Path }}
{{- end }}

{{- /* Get configuration. */}}
{{- $startLevel := or (.Site.Params.navigation.startLevel | int) 2 }}
{{- $endLevel := or (.Site.Params.navigation.endLevel | int) 3 }}

{{- /* Get headings. */}}
{{- $headings := slice }}
{{- $ids := slice }}
{{- range findRE `(?is)<h\d.*?</h\d>` .Content }}
{{- $level := substr . 2 1 | int }}
{{- if and (ge $level $startLevel) (le $level $endLevel) }}
{{- $text := replaceRE `(?is)<h\d.*?>(.+?)</h\d>` "$1" . }}
{{- $text = trim $text " " | plainify | safeHTML }}
{{- $id := "" }}
{{- if findRE `\s+id=` . }}
{{- $id = replaceRE `(?is).+?\s+id=(?:\x22|\x27)?(.*?)(?:\x22|\x27)?[\s>].+` "$1" . }}
{{- $ids = $ids | append $id }}
{{- if not $id }}
{{- errorf "The %q partial detected that the %q heading has an empty ID attribute. See %s" $partialName $text $contentPath }}
{{- end }}
{{- else }}
{{- errorf "The %q partial detected that the %q heading does not have an ID attribute. See %s" $partialName $text $contentPath }}
{{- end }}
{{- $headings = $headings | append (dict "id" $id "level" $level "text" $text) }}
{{- end }}
{{- end }}

{{- /* Check for duplicate heading IDs. */}}
{{- $unique := slice }}
{{- $duplicates := slice }}
{{- range $ids }}
{{- if in $unique . }}
{{- $duplicates = $duplicates | append . }}
{{- else }}
{{- $unique = $unique | append . }}
{{- end }}
{{- end }}
{{- with $duplicates }}
{{- errorf "The %q partial detected duplicate heading IDs (%s) in %s" $partialName (delimit . ", ") $contentPath }}
{{- end }}

{{ return $headings }}

107 changes: 107 additions & 0 deletions layouts/partials/assets/toc-parse-content.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
{{- /*
Copyright 2023 Veriphor, LLC

Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
*/}}

{{- /*
Renders a table of contents by parsing rendered content.

In site configuration, set the default start level, end level, and the minimum
number of headings required to show the table of contents:

[params.toc]
startLevel = 2 # default is 2
endLevel = 3 # default is 3
minNumHeadings = 2 # default is 2

To display the table of contents on a page:

+++
title = 'Post 1'
toc = true
+++

To display the table of contents on a page, and override one or more of the
default settings:

+++
title = 'Post 1'
[toc]
startLevel = 2 # default is 2
endLevel = 3 # default is 3
minNumHeadings = 2 # default is 2
+++

Change or localize the title with a "toc_title" key in your i18n file(s).

Start with these basic CSS rules to style the table of contents:

a.toc-item {
display: block;
}
a.toc-level-1 {
margin-left: 0em;
}
a.toc-level-2 {
margin-left: 1em;
}
a.toc-level-3 {
margin-left: 2em;
}
a.toc-level-4 {
margin-left: 3em;
}
a.toc-level-5 {
margin-left: 4em;
}
a.toc-level-6 {
margin-left: 5em;
}

@context {page} .

@returns {template.HTML}

@example {{ partial "toc-parse-content.html" . }}
*/}}

{{- /* Get configuration. */}}
{{- $startLevel := or (.Site.Params.navigation.startLevel | int) 2 }}
{{- $endLevel := or (.Site.Params.navigation.endLevel | int) 3 }}
{{- $minNumHeadings := or (.Site.Params.navigation.minNumHeadings | int) 2 }}

{{- /* Initialize. */}}
{{ $headings := partial "assets/toc-headings.html" . }}

{{- /* Render */}}
{{- if .Site.Params.navigation.toc }}
{{- with $headings }}
{{- if ge (len .) $minNumHeadings }}
<strong class="d-block h6 my-2 pt-4">{{ T "toc" }}:</strong>
<nav class="toc">
{{- range . }}
{{- $attrs := dict "class" (printf "toc-item toc-level-%d" (add 1 (sub .level $startLevel))) }}
{{- with .id }}
{{- $attrs = merge $attrs (dict "href" (printf "%s#%s" $.RelPermalink .)) }}
{{- end }}
<a
{{- range $k, $v := $attrs }}
{{- printf " %s=%q" $k $v | safeHTMLAttr }}
{{- end -}}
>{{ .text }}</a>
{{- end }}
</nav>
{{- end }}
{{- end }}
{{- end }}
8 changes: 3 additions & 5 deletions layouts/partials/assets/toc.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!--
Copyright © 2022 - 2025 The Hinode Team / Mark Dumay. All rights reserved.
Copyright © 2024 The Hinode Team / Mark Dumay. All rights reserved.
Use of this source code is governed by The MIT License (MIT) that can be found in the LICENSE file.
Visit gethinode.com/license for more details.
-->
Expand All @@ -14,10 +14,8 @@

<!-- Initialize arguments -->
{{- $page := .page -}}
{{ $items := len (findRE "<li.*?>(.|\n)*?</li>" $page.TableOfContents) -}}

<!-- Main code -->
{{ if and (not $error) (gt $items 1) -}}
<strong class="d-block h6 my-2 pb-2">{{ T "toc" }}:</strong>
{{ $page.TableOfContents }}
{{ if not $error -}}
{{ partial "assets/toc-parse-content.html" $page }}
{{ end -}}
Loading

0 comments on commit 2ab8ee6

Please sign in to comment.