forked from EmielH/tale-hugo
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add table of contents See EmielH#64
- Loading branch information
Showing
8 changed files
with
178 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
aside.toc { | ||
position: sticky; | ||
top: 0; | ||
max-width: 30%; | ||
|
||
// We want the table of contents to be on the left (horizontally), to be | ||
// positioned at a specific point (vertically), stick to the top on scroll | ||
// and it should not affect the layout of other elements. | ||
// "float: left" accomplishes the horizontal positioning, position in the | ||
// element tree the vertical positioning and "position: sticky" does the | ||
// stickiness. "height: 0" makes sure that the flow of other elements is not | ||
// affected. | ||
// | ||
// There does not seem to be a better way to do this unless one is willing | ||
// to implement scrolling in JS: "position" must be set to sticky so neither | ||
// fixed, nor absolute, nor relative positioning can be used. | ||
float: left; | ||
height: 0; | ||
overflow: display; | ||
} | ||
|
||
#tocTitle { | ||
// This is so that we can measure it in JS | ||
width: fit-content; | ||
} | ||
|
||
#tocContainer:hover { | ||
width: calc(2rem + var(--measured-expanded-width)); | ||
} | ||
|
||
#tocContainer:hover div#tocCollapsible { | ||
height: var(--measured-height); | ||
width: var(--measured-expanded-width); | ||
} | ||
|
||
#tocContainer { | ||
background: $grey-3; | ||
border-radius: 1rem; | ||
margin: 2rem; | ||
padding: 1rem; | ||
|
||
// This makes the gap between the two contained divs vanish. Why that gap | ||
// exists, no clue. | ||
display: flex; | ||
flex-direction: column; | ||
|
||
// We want to hide the table of contents before revealing it on hover | ||
overflow: hidden; | ||
|
||
// In addition to the measured width of the title, we need to add the two | ||
// rems for the border (we are using box-sizing: border-box). Also add a | ||
// reasonable default value to minimize visual changes while the page is | ||
// loading. | ||
width: calc(2rem + var(--measured-title-width)); | ||
--measured-title-width: 2.4rem; | ||
|
||
@include transition(all .1s ease-out); | ||
} | ||
|
||
#tocContainer > div { | ||
border-left: 0.4rem solid black; | ||
padding-left: 1rem; | ||
} | ||
|
||
#tocContainer div#tocCollapsible { | ||
// Collapsed by default | ||
height: 0; | ||
|
||
// If we did not force this element to a given width, it would keep | ||
// re-wrapping during the opening/closing transition. | ||
width: var(--measured-expanded-width); | ||
|
||
// No transition on width so that there is no re-wrapping during the | ||
// opening/closing transition | ||
@include transition(height .1s ease-out); | ||
} | ||
|
||
nav#TableOfContents ul { | ||
list-style-type: none; | ||
padding-inline-start: 1rem; | ||
} | ||
|
||
nav#TableOfContents > ul { | ||
padding-inline-start: 0; | ||
} | ||
|
||
nav#TableOfContents li { | ||
margin-top: 0.4rem; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{{ if .Params.toc }} | ||
<aside class="toc"> | ||
<script src="{{ "js/toc.js" | relURL }}"></script> | ||
<div id="tocContainer"> | ||
<div id="tocTitle"> | ||
☰ <!-- Trigram for heaven, aka. hamburger menu --> | ||
</div> | ||
<div id="tocCollapsible"> | ||
{{ .TableOfContents }} | ||
</div> | ||
</div> | ||
</aside> | ||
{{ end }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
"use strict"; | ||
|
||
let oldWidth = -1, oldHeight = -1; | ||
|
||
function measureToc() { | ||
if (window.innerWidth === oldWidth && window.innerHeight === oldHeight) { | ||
// In addition to being a bit of optimization, this clause somehow prevents triggering a | ||
// bug in Firefox 98.2.0 on Android that makes the stickiness of the table of contents | ||
// wonky after scrolling to the bottom, then scrolling up. | ||
return; | ||
} | ||
|
||
oldWidth = window.innerWidth; | ||
oldHeight = window.innerHeight; | ||
|
||
let tocContainer = document.getElementById("tocContainer"); | ||
let tocTitle = document.getElementById("tocTitle"); | ||
let tocCollapsible = document.getElementById("tocCollapsible"); | ||
|
||
// Set relevant elements to automatic sizing. | ||
tocContainer.style.width = "fit-content"; | ||
tocCollapsible.style.width = "fit-content"; | ||
tocCollapsible.style.height = "fit-content"; | ||
|
||
// Apparently, this call is necessary so that the CSS properties set above take effect. | ||
requestAnimationFrame(() => { | ||
// These properties apparently round to the nearest integer but rounding down would make | ||
// them wrap text differently than when autosizing. Add a pixel because it's better to wrap | ||
// too little than to wrap too much, | ||
let titleWidth = tocTitle.offsetWidth + 1; | ||
let collapsibleWidth = tocCollapsible.offsetWidth + 1; | ||
let collapsibleHeight = tocCollapsible.offsetHeight + 1; | ||
|
||
// Make sure the TOC width cannot shrink under that of the title. Sadly, | ||
// calc() does not have min() / max() operators. | ||
let clampedCollapsibleWidth = Math.max(collapsibleWidth, titleWidth); | ||
|
||
tocContainer.style.setProperty("--measured-title-width", titleWidth + "px"); | ||
tocContainer.style.setProperty("--measured-expanded-width", clampedCollapsibleWidth + "px"); | ||
tocCollapsible.style.setProperty("--measured-expanded-width", clampedCollapsibleWidth + "px"); | ||
tocCollapsible.style.setProperty("--measured-height", collapsibleHeight + "px"); | ||
|
||
tocContainer.style.removeProperty("width"); | ||
tocCollapsible.style.removeProperty("width"); | ||
tocCollapsible.style.removeProperty("height"); | ||
}); | ||
} | ||
|
||
let resizeTimeout = null; | ||
|
||
function measureTocAfterResize() { | ||
// Chrome sometimes does not finish layout when the resize event handler is called, so wait a | ||
// bit before recalculating sizes. This does not usually result in weird visual effects because | ||
// it's really hard to resize a window while hovering over the table of contents so we can | ||
// assume that it's closed when the measurement is running. | ||
if (resizeTimeout != null) { | ||
clearTimeout(resizeTimeout); | ||
} | ||
resizeTimeout = setTimeout(measureToc, 200); | ||
} | ||
|
||
window.addEventListener("load", measureToc); | ||
window.addEventListener("resize", measureTocAfterResize); |