diff --git a/config/_default/hugo.toml b/config/_default/hugo.toml index 5efeadf..43d0550 100644 --- a/config/_default/hugo.toml +++ b/config/_default/hugo.toml @@ -40,6 +40,12 @@ page = ["HTML", "RSS"] [pagination] pagerSize = 12 +[markup] +[markup.tableOfContents] +endLevel = 4 +ordered = true +startLevel = 2 + [params] dateform = "Jan 2, 2006" dateformShort = "Jan 2" diff --git a/themes/maik-blog/assets/js/main.js b/themes/maik-blog/assets/js/main.js index a105db3..f90f247 100644 --- a/themes/maik-blog/assets/js/main.js +++ b/themes/maik-blog/assets/js/main.js @@ -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", () => { // Enlarged floating figures 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 for (let codeblock of document.querySelectorAll(".highlight pre")) { if (codeblock.querySelector(".lnt")) continue; // skip line numbers @@ -70,6 +125,5 @@ window.addEventListener("load", () => { button.classList.add("current-code"); }); - console.log(codeblock); } }); diff --git a/themes/maik-blog/assets/scss/_main.scss b/themes/maik-blog/assets/scss/_main.scss index 34af4cd..712daaf 100644 --- a/themes/maik-blog/assets/scss/_main.scss +++ b/themes/maik-blog/assets/scss/_main.scss @@ -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; + } + } + } + } +} diff --git a/themes/maik-blog/layouts/_default/single.html b/themes/maik-blog/layouts/_default/single.html index d35fc3f..dbcbc8a 100644 --- a/themes/maik-blog/layouts/_default/single.html +++ b/themes/maik-blog/layouts/_default/single.html @@ -38,14 +38,12 @@ {{- partial "image.html" (dict "path" .Params.Cover "alt" (.Title | plainify) "class" "post-cover") }} {{- end }} - {{- if .Params.toc }} -