Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/EmielH/tale-hugo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlberki <lberki@users.noreply.github.com>2022-05-05 12:08:42 +0300
committerGitHub <noreply@github.com>2022-05-05 12:08:42 +0300
commitc95051b93d756d5653ebe36a8f5f2d556b3843a0 (patch)
tree451aa9a3af1e363fbe239ceef35b9e58ba388d15
parent93a5ae3a16c5654414af8c0c83699b165a2d2bcf (diff)
Add table of contents
Add table of contents See #64
-rw-r--r--assets/scss/tale.scss1
-rw-r--r--assets/scss/tale/_code.scss4
-rw-r--r--assets/scss/tale/_layout.scss6
-rw-r--r--assets/scss/tale/_toc.scss89
-rw-r--r--assets/scss/tale/_variables.scss3
-rw-r--r--layouts/_default/single.html4
-rw-r--r--layouts/partials/toc.html13
-rw-r--r--static/js/toc.js63
8 files changed, 178 insertions, 5 deletions
diff --git a/assets/scss/tale.scss b/assets/scss/tale.scss
index 9d1c12f..53f9dec 100644
--- a/assets/scss/tale.scss
+++ b/assets/scss/tale.scss
@@ -4,6 +4,7 @@
@import 'tale/post';
@import 'tale/syntax';
@import 'tale/layout';
+@import 'tale/toc';
@import 'tale/pagination';
@import 'tale/catalogue';
@import 'tale/disqus';
diff --git a/assets/scss/tale/_code.scss b/assets/scss/tale/_code.scss
index 8d3db57..11f1c53 100644
--- a/assets/scss/tale/_code.scss
+++ b/assets/scss/tale/_code.scss
@@ -4,7 +4,7 @@ code {
}
code {
- background-color: $grey-3;
+ background-color: $grey-4;
border-radius: 3px;
color: $code-color;
font-size: 85%;
@@ -24,7 +24,7 @@ pre code {
}
.highlight {
- background-color: $grey-3;
+ background-color: $grey-4;
border-radius: 3px;
line-height: 1.4;
margin: 0 0 1rem;
diff --git a/assets/scss/tale/_layout.scss b/assets/scss/tale/_layout.scss
index ba4a7b0..8d56834 100644
--- a/assets/scss/tale/_layout.scss
+++ b/assets/scss/tale/_layout.scss
@@ -4,7 +4,11 @@
width: 80%;
}
-main,
+// Carve out an exception from the 80% narrowing of the viewport for the table
+// of contents. This is ugly, but there isn't a better way, since the table of
+// contents must be a child of the "main" element so that it its scrolling
+// behavior is intuitive.
+main > *,
footer,
.nav-container {
display: block;
diff --git a/assets/scss/tale/_toc.scss b/assets/scss/tale/_toc.scss
new file mode 100644
index 0000000..6492ba7
--- /dev/null
+++ b/assets/scss/tale/_toc.scss
@@ -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;
+}
diff --git a/assets/scss/tale/_variables.scss b/assets/scss/tale/_variables.scss
index 8a064f3..92d1114 100644
--- a/assets/scss/tale/_variables.scss
+++ b/assets/scss/tale/_variables.scss
@@ -4,7 +4,8 @@ $default-shade: #353535;
$default-tint: #aaa;
$grey-1: #979797;
$grey-2: #e5e5e5;
-$grey-3: #f9f9f9;
+$grey-3: #f0f0f0;
+$grey-4: #f9f9f9;
$white: #fff;
$blue: #4a9ae1;
$shadow-color: rgba(0, 0, 0, .2);
diff --git a/layouts/_default/single.html b/layouts/_default/single.html
index dba8344..f5f6f6e 100644
--- a/layouts/_default/single.html
+++ b/layouts/_default/single.html
@@ -1,7 +1,9 @@
{{ define "main" }}
<main>
- <div class="post">
+ {{ partial "toc.html" . }}
+
+ <div class="post">
{{ partial "single/post-info.html" . }}
{{ partial "single/title.html" . }}
diff --git a/layouts/partials/toc.html b/layouts/partials/toc.html
new file mode 100644
index 0000000..81d524a
--- /dev/null
+++ b/layouts/partials/toc.html
@@ -0,0 +1,13 @@
+{{ if .Params.toc }}
+<aside class="toc">
+ <script src="{{ "js/toc.js" | relURL }}"></script>
+ <div id="tocContainer">
+ <div id="tocTitle">
+ &#9776; <!-- Trigram for heaven, aka. hamburger menu -->
+ </div>
+ <div id="tocCollapsible">
+ {{ .TableOfContents }}
+ </div>
+ </div>
+</aside>
+{{ end }}
diff --git a/static/js/toc.js b/static/js/toc.js
new file mode 100644
index 0000000..7229c04
--- /dev/null
+++ b/static/js/toc.js
@@ -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);