Add a ToC that is always visible on desktop

master
Maik de Kruif 2 weeks ago
parent 46c6f9037e
commit af3694f0ef
No known key found for this signature in database
GPG Key ID: DB1A8C782DD43CB3
  1. 6
      config/_default/hugo.toml
  2. 56
      themes/maik-blog/assets/js/main.js
  3. 129
      themes/maik-blog/assets/scss/_main.scss
  4. 12
      themes/maik-blog/layouts/_default/single.html

@ -40,6 +40,12 @@ page = ["HTML", "RSS"]
[pagination] [pagination]
pagerSize = 12 pagerSize = 12
[markup]
[markup.tableOfContents]
endLevel = 4
ordered = true
startLevel = 2
[params] [params]
dateform = "Jan 2, 2006" dateform = "Jan 2, 2006"
dateformShort = "Jan 2" dateformShort = "Jan 2"

@ -1,3 +1,14 @@
const throttle = (func, timeFrame) => {
let lastTime = 0;
return () => {
let now = new Date();
if (now - lastTime >= timeFrame) {
func();
lastTime = now;
}
};
};
window.addEventListener("load", () => { window.addEventListener("load", () => {
// Enlarged floating figures // Enlarged floating figures
for (let figure of document.getElementsByTagName("figure")) { for (let figure of document.getElementsByTagName("figure")) {
@ -51,6 +62,50 @@ window.addEventListener("load", () => {
} }
} }
// set active toc heading
const toc = document.getElementById("toc");
const tocNav = toc.getElementsByTagName("nav")[0];
const headingsForToc = [
...document.getElementsByTagName("article"),
].flatMap((article) =>
[...article.querySelectorAll("h1, h2, h3, h4")]
.filter((heading) => heading.offsetParent.tagName === "MAIN")
.reverse()
);
const onTocScroll = () => {
for (const heading of headingsForToc) {
if (window.scrollY + window.innerHeight / 3 > heading.offsetTop) {
const headingAnchor = heading.getElementsByTagName("a")[0];
for (const tocAnchor of toc.getElementsByTagName("a")) {
tocAnchor.classList.remove("current-toc");
if (tocAnchor.href !== headingAnchor.href) continue;
tocAnchor.classList.add("current-toc");
// Scroll toc element into view
const tocNavRect = tocNav.getBoundingClientRect();
const tocAnchorRect = tocAnchor.getBoundingClientRect();
const offset =
tocAnchorRect.top -
tocNavRect.top -
tocNav.clientHeight / 2;
const direction = offset / Math.abs(offset);
const limit = (tocNav.clientHeight / 2) * 0.5;
if (Math.abs(offset) > limit)
tocNav.scrollTop += offset - direction * limit;
}
return;
}
}
};
window.addEventListener("scroll", throttle(onTocScroll, 50));
onTocScroll();
// Code copy button // Code copy button
for (let codeblock of document.querySelectorAll(".highlight pre")) { for (let codeblock of document.querySelectorAll(".highlight pre")) {
if (codeblock.querySelector(".lnt")) continue; // skip line numbers if (codeblock.querySelector(".lnt")) continue; // skip line numbers
@ -70,6 +125,5 @@ window.addEventListener("load", () => {
button.classList.add("current-code"); button.classList.add("current-code");
}); });
console.log(codeblock);
} }
}); });

@ -715,3 +715,132 @@ table {
} }
} }
} }
#toc {
&.desktop-only {
display: none;
}
.toc-title {
display: flex;
align-items: center;
font-weight: bold;
font-size: 1.625rem;
line-height: 1.3;
margin: 0.83em 0;
}
ul,
ol {
li::marker {
content: "- ";
}
}
@media (min-width: 1840px) {
display: block !important;
position: absolute;
top: 100vh;
height: calc(100% - 100vh - 380px);
.toc-content {
position: sticky;
top: 10vh;
height: 70vh;
margin-bottom: -2em;
margin-left: calc(-300px - 100px);
display: flex !important;
flex-direction: column;
width: 300px;
right: calc(100% + 100px);
::-webkit-scrollbar {
width: 6px;
height: 6px;
background: darken($light-background, 10%);
border-radius: 8px;
}
::-webkit-scrollbar-thumb {
background: darken($light-background, 25%);
border-radius: 8px;
&:hover {
background: darken($light-background, 40%);
}
}
.dark-theme & {
::-webkit-scrollbar {
background: lighten($dark-background, 10%);
}
::-webkit-scrollbar-thumb {
background: lighten($dark-background, 25%);
&:hover {
background: lighten($dark-background, 40%);
}
}
}
.toc-title {
font-size: 1.375rem;
min-height: calc(1.625rem * 1.3);
margin: 0;
}
nav {
overflow-y: auto;
margin-top: 1em;
}
a {
display: block;
text-decoration: none;
padding: 5px;
border-radius: 8px;
&:hover {
background: darken($light-background, 10%);
.dark-theme & {
background: lighten($dark-background, 10%);
}
}
&.current-toc {
background: darken($light-background, 10%);
.dark-theme & {
background: lighten($dark-background, 10%);
}
}
&::before {
content: "- ";
}
}
ul,
ol {
margin: 0;
padding: 0;
li::marker {
content: "";
}
li a {
padding-left: 20px;
}
li li a {
padding-left: 40px;
}
li li li a {
padding-left: 60px;
}
}
}
}
}

@ -38,14 +38,12 @@
{{- partial "image.html" (dict "path" .Params.Cover "alt" (.Title | plainify) "class" "post-cover") }} {{- partial "image.html" (dict "path" .Params.Cover "alt" (.Title | plainify) "class" "post-cover") }}
{{- end }} {{- end }}
{{- if .Params.toc }} <aside id="toc" class="{{- if not .Params.toc }}desktop-only{{- end }}">
<hr /> <div class="toc-content">
<aside id="toc"> <div class="toc-title">{{ i18n "tableOfContents" }}</div>
<div class="toc-title">{{ i18n "tableOfContents" }}</div> {{ .TableOfContents }}
{{ .TableOfContents }} </div>
</aside> </aside>
<hr />
{{- end }}
<div class="post-content"> <div class="post-content">
{{ .Content }} {{ .Content }}

Loading…
Cancel
Save