feat(toc): dynamic heading

This commit is contained in:
Pavel Korytov 2022-06-26 21:29:52 +03:00
parent 598050748c
commit fb5996755b
6 changed files with 107 additions and 22 deletions

View file

@ -5,3 +5,7 @@ indent_size = 4
[*.scss] [*.scss]
indent_style = "space" indent_style = "space"
indent_size = 4 indent_size = 4
[*.js]
indent_style = "space"
indent_size = 2

View file

@ -49,30 +49,26 @@ $y-medium: 1.0rem;
font-family: $font-family; font-family: $font-family;
line-height: 1.2; line-height: 1.2;
} }
$toc-left-width: $toc-width + $max-width + 25px;
.root { .root {
display: flex; display: flex;
flex-direction: column; 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 { .table-of-contents {
order: 0; order: 0;
@media(max-width: $toc-width + $max-width) {
padding-left: 15px;
padding-right: 15px;
}
ul { ul {
padding-left: 1.0rem !important; padding-left: 1.0rem !important;
& > li { & > li {
margin-left: 0.3em !important; margin-left: 0.3em !important;
} }
} }
a.active {
font-weight: bold;
}
@media(max-width: 578px) { @media(max-width: 578px) {
align-self: center; 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) { @media (min-width: $toc-width * 1.5 + $max-width) {
.root { .root {
margin-left: calc((100vw - 750px) / 2); margin-left: calc((100vw - 750px) / 2);
} }
.content { #actual-content {
margin: 0; margin: 0;
} }
} }
@media(min-width: $toc-width + $max-width + 25px) { @media(min-width: $toc-left-width) {
.root { .root {
flex-direction: row; flex-direction: row;
} }
@ -114,19 +124,16 @@ $y-medium: 1.0rem;
padding: 1em; padding: 1em;
align-self: start; align-self: start;
scrollbar-width: thin; scrollbar-width: thin;
// flex-grow: 1;
.table-of-contents-text { .table-of-contents-text {
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
max-height: calc(100vh - 155px); max-height: calc(100vh - 155px);
} }
}
.content { .hidden {
// flex-grow: 1; display: none;
}
} }
#title-small-screen { #title-small-screen {

View file

@ -45,6 +45,8 @@ staticDir = ["static"]
noHl = false noHl = false
style = 'pygments' style = 'pygments'
tabWidth = 4 tabWidth = 4
[markup.tableOfContents]
endLevel = 4
[markup.goldmark.renderer] [markup.goldmark.renderer]
unsafe = true # allow raw HTML in markdown files unsafe = true # allow raw HTML in markdown files

View file

@ -1,7 +1,8 @@
{{ define "main" }} {{ define "main" }}
<script defer language="javascript" type="text/javascript" src="{{ "/js/dynamic-toc.js" | urlize | relURL }}"></script>
<div class="root"> <div class="root">
<h1 id="title-small-screen">{{ .Title }}</h1> <h1 id="title-small-screen">{{ .Title }}</h1>
<div class="container content"> <div class="container" id="actual-content">
<h1 id="title-large-screen">{{ .Title }}</h1> <h1 id="title-large-screen">{{ .Title }}</h1>
{{ .Content }} {{ .Content }}
</div> </div>

View file

@ -31,6 +31,9 @@
:ensure t) :ensure t)
(setq org-make-toc-link-type-fn #'org-make-toc--link-entry-org) (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-section "configs")
(setq org-hugo-base-dir (vc-find-root default-directory ".git")) (setq org-hugo-base-dir (vc-find-root default-directory ".git"))

68
static/js/dynamic-toc.js Normal file
View file

@ -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();
});