feat: Table of Contents (#232)

Currently, the new table of contents is disabled by default. I will change that after several versions.

-------------

* feat: Table of Content (WIP)

* fix: set scratch inside define block

* fix(toc): darkmode text color

* feat: unify page layout, and add config to disable toc

* feat: add scroll-behavior: smooth to html

* fix: use <ol> for TOC. And Disable TOC by default for now

* refactor: use css flexbox for article page with toc

enable toc for example site

* feat(i18n): add i18n entry for "Back" and "Table of Contents"

* style: remove unused `keep-sidebar` class

* doc: add table of contents as feature
This commit is contained in:
Jimmy Cai 2021-06-21 18:44:54 +02:00 committed by GitHub
parent dda55f87e2
commit 41fa65cbf5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 182 additions and 34 deletions

View file

@ -27,6 +27,7 @@ Stack is a simple card-style Hugo theme designed for bloggers, some of its featu
- No CSS framework, keep it simple and minimal - No CSS framework, keep it simple and minimal
- Properly cropped thumbnails - Properly cropped thumbnails
- Subsection support - Subsection support
- Table of contents
## Requirements ## Requirements

View file

@ -92,7 +92,6 @@
main.main { main.main {
min-width: 0; min-width: 0;
padding: 0 15px;
max-width: 100%; max-width: 100%;
flex-grow: 1; flex-grow: 1;
padding-top: var(--main-top-padding); padding-top: var(--main-top-padding);
@ -101,12 +100,10 @@ main.main {
.main-container { .main-container {
min-height: 100vh; min-height: 100vh;
align-items: flex-start; align-items: flex-start;
padding: 0 15px;
column-gap: var(--section-separation);
@include respond(md) { @include respond(md) {
padding: 0 10px;
}
@include respond(lg) {
padding: 0 20px; padding: 0 20px;
} }
} }

View file

@ -1,6 +1,7 @@
html { html {
font-size: 62.5%; font-size: 62.5%;
overflow-y: scroll; overflow-y: scroll;
scroll-behavior: smooth;
} }
* { * {

View file

@ -114,6 +114,114 @@
} }
} }
.article-page.has-toc {
scroll-behavior: smooth;
.left-sidebar {
display: none;
}
.right-sidebar {
width: 100%;
padding: 0;
display: none;
@include respond(xl) {
display: block;
top: var(--main-top-padding);
}
}
#article-toolbar {
display: block;
@include respond(md) {
padding: 0;
}
@include respond(xl) {
margin-top: 0;
position: sticky;
top: var(--main-top-padding);
flex-shrink: 1;
a {
background: transparent;
box-shadow: none;
border: 1px solid var(--body-text-color);
width: 100%;
margin-right: 0;
svg {
flex-shrink: 0;
}
}
}
}
.main-container {
align-items: start;
flex-direction: column;
@include respond(xl) {
flex-direction: row;
}
}
.main {
padding-top: 0;
@include respond(xl) {
padding-top: var(--main-top-padding);
}
}
}
.widget--toc {
background-color: var(--card-background);
border-radius: var(--card-border-radius);
box-shadow: var(--shadow-l1);
display: flex;
flex-direction: column;
color: var(--card-text-color-main);
#TableOfContents {
ol {
counter-reset: item;
list-style-type: none;
padding: 0;
margin: 0;
}
& > li {
padding: 0;
margin: 0;
}
li {
margin: 15px 20px;
padding: 5px;
&::before {
counter-increment: item;
content: counters(item, ".") ". ";
font-weight: bold;
margin-right: 5px;
}
& > ol {
margin-top: 10px;
padding-left: 10px;
margin-bottom: -5px;
& > li:last-child {
margin-bottom: 0;
}
}
}
}
}
.related-contents--wrapper { .related-contents--wrapper {
margin-bottom: var(--section-separation); margin-bottom: var(--section-separation);
} }

View file

@ -1,5 +1,4 @@
.sidebar { .sidebar {
padding: 0 15px;
&.sticky { &.sticky {
@include respond(md) { @include respond(md) {
position: sticky; position: sticky;
@ -22,8 +21,8 @@
@include respond(md) { @include respond(md) {
width: auto; width: auto;
margin-right: 1%; padding-top: var(--main-top-padding);
padding: var(--main-top-padding) 15px; padding-bottom: var(--main-top-padding);
max-height: 100vh; max-height: 100vh;
} }
@ -46,7 +45,6 @@
} }
@include respond(lg) { @include respond(lg) {
margin-left: 1%;
padding-top: var(--main-top-padding); padding-top: var(--main-top-padding);
} }
} }
@ -55,7 +53,7 @@
z-index: 1; z-index: 1;
transition: box-shadow 0.5s ease; transition: box-shadow 0.5s ease;
padding: 15px 30px; padding: 15px;
@include respond(md) { @include respond(md) {
padding: 0; padding: 0;

View file

@ -42,6 +42,7 @@ params:
article: article:
math: false math: false
toc: true
license: license:
enabled: true enabled: true
default: Licensed under CC BY-NC-SA 4.0 default: Licensed under CC BY-NC-SA 4.0
@ -145,5 +146,9 @@ related:
weight: 200 weight: 200
markup: markup:
tableOfContents:
endLevel: 4
ordered: true
startLevel: 2
highlight: highlight:
noClasses: false noClasses: false

View file

@ -17,8 +17,15 @@ list:
other: Subsections other: Subsections
article: article:
back:
other: Back
tableOfContents:
other: Table of contents
relatedContents: relatedContents:
other: Related contents other: Related contents
lastUpdatedOn: lastUpdatedOn:
other: Last updated on other: Last updated on
@ -32,8 +39,10 @@ widget:
archives: archives:
title: title:
other: Archives other: Archives
more: more:
other: More other: More
tagCloud: tagCloud:
title: title:
other: Tags other: Tags
@ -41,13 +50,16 @@ widget:
search: search:
title: title:
other: Search other: Search
placeholder: placeholder:
other: Type something... other: Type something...
resultTitle: resultTitle:
other: "#PAGES_COUNT pages (#TIME_SECONDS seconds)" other: "#PAGES_COUNT pages (#TIME_SECONDS seconds)"
footer: footer:
builtWith: builtWith:
other: Built with {{ .Generator }} other: Built with {{ .Generator }}
designedBy: designedBy:
other: Theme {{ .Theme }} designed by {{ .DesignedBy }} other: Theme {{ .Theme }} designed by {{ .DesignedBy }}

View file

@ -6,8 +6,10 @@
</head> </head>
<body class="{{ block `body-class` . }}{{ end }}"> <body class="{{ block `body-class` . }}{{ end }}">
{{- partial "head/colorScheme" . -}} {{- partial "head/colorScheme" . -}}
<div class="container main-container flex on-phone--column {{ if .Site.Params.widgets.enabled }}extended{{ else }}compact{{ end }} {{ block `container-class` . }}{{end}}"> <div class="container main-container flex {{ block `container-class` . }}on-phone--column {{ if .Site.Params.widgets.enabled }}extended{{ else }}compact{{ end }}{{ end }}">
{{ partial "sidebar/left.html" . }} {{- block "left-sidebar" . -}}
{{ partial "sidebar/left.html" . }}
{{- end -}}
<main class="main full-width"> <main class="main full-width">
{{- block "main" . }}{{- end }} {{- block "main" . }}{{- end }}
</main> </main>

View file

@ -1,7 +1,22 @@
{{ define "body-class" }}article-page{{ end }} {{ define "body-class" }}
{{ $TOCEnabled := default (default false .Site.Params.article.toc) .Params.toc }}
{{- .Scratch.Set "hasTOC" (and (ge (len .TableOfContents) 100) $TOCEnabled) -}}
article-page {{ if (.Scratch.Get "hasTOC") }}has-toc{{ end }}
{{ end }}
{{ define "container-class" }}
{{ if (.Scratch.Get "hasTOC") }}
extended
{{ else }}
on-phone--column {{ if .Site.Params.widgets.enabled }}extended{{ else }}compact{{ end }}
{{ end }}
{{ end }}
{{ define "main" }} {{ define "main" }}
{{ partial "article/article.html" . }} {{ partial "article/article.html" . }}
{{ partial "article/components/related-contents" . }}
{{ if or (not (isset .Params "comments")) (eq .Params.comments "true")}} {{ if or (not (isset .Params "comments")) (eq .Params.comments "true")}}
{{ partial "comments/include" . }} {{ partial "comments/include" . }}
{{ end }} {{ end }}
@ -10,3 +25,33 @@
{{ partialCached "article/components/photoswipe" . }} {{ partialCached "article/components/photoswipe" . }}
{{ end }} {{ end }}
{{ define "left-sidebar" }}
{{ if (.Scratch.Get "hasTOC") }}
<div id="article-toolbar">
<a href="{{ .Site.BaseURL }}" class="back-home">
{{ (resources.Get "icons/back.svg").Content | safeHTML }}
<span>{{ T "article.back" }}</span>
</a>
</div>
{{ else }}
{{ partial "sidebar/left.html" . }}
{{ end }}
{{ end }}
{{ define "right-sidebar" }}
{{ if (.Scratch.Get "hasTOC") }}
<aside class="sidebar right-sidebar sticky">
<section class="widget archives">
<div class="widget-icon">
{{ partial "helper/icon" "hash" }}
</div>
<h2 class="widget-title section-title">{{ T "article.tableOfContents" }}</h2>
<div class="widget--toc">
{{ .TableOfContents }}
</div>
</section>
</aside>
{{ end }}
{{ end }}

View file

@ -1,21 +0,0 @@
{{ define "container-class" }}article-page with-toolbar hide-sidebar-sm{{ end }}
{{ define "main" }}
<div id="article-toolbar">
<a href="{{ .Site.BaseURL }}" class="back-home">
{{ (resources.Get "icons/back.svg").Content | safeHTML }}
<span>Back</span>
</a>
</div>
{{ partial "article/article.html" . }}
{{ partial "article/components/related-contents" . }}
{{ if or (not (isset .Params "comments")) (eq .Params.comments "true")}}
{{ partial "comments/include" . }}
{{ end }}
{{ partialCached "footer/footer" . }}
{{- partialCached "article/components/photoswipe.html" . -}}
{{ end }}