From fb5996755bd7adb896925918254b438616fd341e Mon Sep 17 00:00:00 2001 From: SqrtMinusOne Date: Sun, 26 Jun 2022 21:29:52 +0300 Subject: [PATCH] feat(toc): dynamic heading --- .editorconfig | 6 +++- assets/sass/researcher.scss | 47 ++++++++++++++----------- config.toml | 2 ++ layouts/_default/single.html | 3 +- scripts/publish-configs.el | 3 ++ static/js/dynamic-toc.js | 68 ++++++++++++++++++++++++++++++++++++ 6 files changed, 107 insertions(+), 22 deletions(-) create mode 100644 static/js/dynamic-toc.js diff --git a/.editorconfig b/.editorconfig index dfa53db..17feeda 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,4 +4,8 @@ indent_size = 4 [*.scss] indent_style = "space" -indent_size = 4 \ No newline at end of file +indent_size = 4 + +[*.js] +indent_style = "space" +indent_size = 2 \ No newline at end of file diff --git a/assets/sass/researcher.scss b/assets/sass/researcher.scss index 1ffa21c..b3aa6ab 100644 --- a/assets/sass/researcher.scss +++ b/assets/sass/researcher.scss @@ -49,30 +49,26 @@ $y-medium: 1.0rem; font-family: $font-family; line-height: 1.2; } + +$toc-left-width: $toc-width + $max-width + 25px; + .root { display: flex; flex-direction: column; - - @media(max-width: $toc-width + $max-width) { - margin-right: auto; - margin-left: auto; - width: 100%; - max-width: $max-width; - } } .table-of-contents { order: 0; - @media(max-width: $toc-width + $max-width) { - padding-left: 15px; - padding-right: 15px; - } - ul { padding-left: 1.0rem !important; & > li { margin-left: 0.3em !important; } } + + a.active { + font-weight: bold; + } + @media(max-width: 578px) { align-self: center; } @@ -92,16 +88,30 @@ $y-medium: 1.0rem; } } +@media (max-width: $toc-left-width) { + .root { + margin-right: auto; + margin-left: auto; + width: 100%; + max-width: $max-width; + } + + .table-of-contents { + padding-left: 15px; + padding-right: 15px; + } +} + @media (min-width: $toc-width * 1.5 + $max-width) { .root { margin-left: calc((100vw - 750px) / 2); } - .content { + #actual-content { margin: 0; } } -@media(min-width: $toc-width + $max-width + 25px) { +@media(min-width: $toc-left-width) { .root { flex-direction: row; } @@ -114,19 +124,16 @@ $y-medium: 1.0rem; padding: 1em; align-self: start; scrollbar-width: thin; - // flex-grow: 1; .table-of-contents-text { overflow-x: hidden; overflow-y: auto; max-height: calc(100vh - 155px); - - } - } - .content { - // flex-grow: 1; + .hidden { + display: none; + } } #title-small-screen { diff --git a/config.toml b/config.toml index 020e453..3614ddc 100644 --- a/config.toml +++ b/config.toml @@ -45,6 +45,8 @@ staticDir = ["static"] noHl = false style = 'pygments' tabWidth = 4 + [markup.tableOfContents] + endLevel = 4 [markup.goldmark.renderer] unsafe = true # allow raw HTML in markdown files \ No newline at end of file diff --git a/layouts/_default/single.html b/layouts/_default/single.html index 025b4e1..8038fdd 100644 --- a/layouts/_default/single.html +++ b/layouts/_default/single.html @@ -1,7 +1,8 @@ {{ define "main" }} +

{{ .Title }}

-
+

{{ .Title }}

{{ .Content }}
diff --git a/scripts/publish-configs.el b/scripts/publish-configs.el index bf71c9c..353156d 100644 --- a/scripts/publish-configs.el +++ b/scripts/publish-configs.el @@ -31,6 +31,9 @@ :ensure t) (setq org-make-toc-link-type-fn #'org-make-toc--link-entry-org) +(setq org-hugo-anchor-functions '(org-hugo-get-page-or-bundle-name + org-hugo-get-custom-id + org-hugo-get-md5)) (setq org-hugo-section "configs") (setq org-hugo-base-dir (vc-find-root default-directory ".git")) diff --git a/static/js/dynamic-toc.js b/static/js/dynamic-toc.js new file mode 100644 index 0000000..84827c8 --- /dev/null +++ b/static/js/dynamic-toc.js @@ -0,0 +1,68 @@ +const tocId = "TableOfContents"; +const actualContentId = "actual-content"; +let headerObserver; + +function observeHeadings() { + const links = document.querySelectorAll(`#${tocId} a`); + const headings = document.querySelectorAll(`${actualContentId} h1,h2,h3,h4`); + const elemsToHide = []; + const linksById = {}; + + for (const link of links) { + linksById[link.getAttribute("href")] = link; + } + for (const elem of document.querySelectorAll(`#${tocId} ul`)) { + if (elem.parentElement.id !== tocId) { + elemsToHide.push(elem); + } + } + + headerObserver = new IntersectionObserver( + (entries) => { + let newActiveLinkId; + for (const entry of entries) { + if (entry.isIntersecting && linksById[`#${entry.target.id}`]) { + newActiveLinkId = `#${entry.target.id}`; + break; + } + } + if (newActiveLinkId) { + for (const link of links) { + link.classList.remove("active"); + } + for (const elem of elemsToHide) { + elem.classList.add("hidden"); + } + linksById[newActiveLinkId].classList.add("active"); + for ( + let elem = linksById[newActiveLinkId]; + (elem = elem.parentElement); + elem.id !== tocId + ) { + elem.classList.remove("hidden"); + } + for (const elem of linksById[newActiveLinkId].parentElement.children) { + elem.classList.remove("hidden"); + } + } + }, + { + threshold: 0.1, + root: document.querySelector(`${actualContentId}`), + } + ); + + for (const heading of headings) { + headerObserver.observe(heading); + } +} + +window.addEventListener("load", (event) => { + if ("IntersectionObserver" in window) { + observeHeadings(); + } +}); + +window.addEventListener("unload", (event) => { + headerObserver.disconnect(); +});