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

github.com/capnfabs/paperesque.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
authorFabian Tamp <fabian.tamp@gmail.com>2020-01-27 03:12:49 +0300
committerFabian Tamp <fabian.tamp@gmail.com>2020-01-27 03:12:49 +0300
commitde3eae439847ac6b4ea00cade2e4bb6393f36d81 (patch)
tree2dbc98d8b4eee70ed6a0b59a1c6e0167fbaa985b /js
parent175fab5477aa833e43472a0c4a9134f495f63960 (diff)
Updates from capnfabs.github.io repo.
Diffstat (limited to 'js')
-rw-r--r--js/.jshintrc3
-rw-r--r--js/anchorizeHeadings.js33
-rw-r--r--js/floatingFootnotes.js123
-rw-r--r--js/main.js5
-rw-r--r--js/utils.js29
5 files changed, 193 insertions, 0 deletions
diff --git a/js/.jshintrc b/js/.jshintrc
new file mode 100644
index 0000000..f165e8b
--- /dev/null
+++ b/js/.jshintrc
@@ -0,0 +1,3 @@
+{
+"esversion": 6
+}
diff --git a/js/anchorizeHeadings.js b/js/anchorizeHeadings.js
new file mode 100644
index 0000000..cd3b8b6
--- /dev/null
+++ b/js/anchorizeHeadings.js
@@ -0,0 +1,33 @@
+import { docReady } from "./utils.js";
+
+// Borrowed from https://github.com/gohugoio/gohugoioTheme/blob/2e7250ca437d4666329d3ca96708dd3a4ff59818/assets/js/anchorforid.js
+function anchorForId(id) {
+ const anchor = document.createElement("a");
+ anchor.className = "header-link";
+ anchor.title = "Link to this section";
+ anchor.href = "#" + id;
+ // Icon from https://useiconic.com/open#icons
+ anchor.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8"><path d="M5.88.03c-.18.01-.36.03-.53.09-.27.1-.53.25-.75.47a.5.5 0 1 0 .69.69c.11-.11.24-.17.38-.22.35-.12.78-.07 1.06.22.39.39.39 1.04 0 1.44l-1.5 1.5c-.44.44-.8.48-1.06.47-.26-.01-.41-.13-.41-.13a.5.5 0 1 0-.5.88s.34.22.84.25c.5.03 1.2-.16 1.81-.78l1.5-1.5c.78-.78.78-2.04 0-2.81-.28-.28-.61-.45-.97-.53-.18-.04-.38-.04-.56-.03zm-2 2.31c-.5-.02-1.19.15-1.78.75l-1.5 1.5c-.78.78-.78 2.04 0 2.81.56.56 1.36.72 2.06.47.27-.1.53-.25.75-.47a.5.5 0 1 0-.69-.69c-.11.11-.24.17-.38.22-.35.12-.78.07-1.06-.22-.39-.39-.39-1.04 0-1.44l1.5-1.5c.4-.4.75-.45 1.03-.44.28.01.47.09.47.09a.5.5 0 1 0 .44-.88s-.34-.2-.84-.22z" /></svg>';
+ return anchor;
+}
+
+function anchorizeHeadings() {
+ // If we've found more than 1 article, then abort. It probably means I've
+ // messed something up if this is the case, but I don't have enough
+ // confidence in the way I've set everything up to _not_ do this safety
+ // check.
+ const articles = document.querySelectorAll('section#main article');
+ if (articles.length != 1) {
+ return;
+ }
+ // Keep this list of header classes in sync with style.css
+ const headers = articles[0].querySelectorAll('h2, h3, h4');
+ Array.prototype.forEach.call(headers, function (el, i) {
+ var link = anchorForId(el.id);
+ el.appendChild(link);
+ });
+}
+
+export default function anchorizeOnReady() {
+ docReady(anchorizeHeadings);
+}
diff --git a/js/floatingFootnotes.js b/js/floatingFootnotes.js
new file mode 100644
index 0000000..dcd8fe1
--- /dev/null
+++ b/js/floatingFootnotes.js
@@ -0,0 +1,123 @@
+import { docReady, onWindowResize } from "./utils.js";
+import { ResizeObserver } from '@juggle/resize-observer';
+
+const ARTICLE_CONTENT_SELECTOR = "section#main";
+const FOOTNOTE_SECTION_SELECTOR = "section.footnotes[role=doc-endnotes]";
+const FLOATING_FOOTNOTE_MIN_WIDTH = 1260;
+
+// Computes an offset such that setting `top` on elemToAlign will put it
+// in vertical alignment with targetAlignment.
+function computeOffsetForAlignment(elemToAlign, targetAlignment) {
+ const offsetParentTop = elemToAlign.offsetParent.getBoundingClientRect().top;
+ // Distance between the top of the offset parent and the top of the target alignment
+ return targetAlignment.getBoundingClientRect().top - offsetParentTop;
+}
+
+function setFootnoteOffsets(footnotes) {
+ // Keep track of the bottom of the last element, because we don't want to
+ // overlap footnotes.
+ let bottomOfLastElem = 0;
+ Array.prototype.forEach.call(footnotes, function (footnote, i) {
+
+ // In theory, don't need to escape this because IDs can't contain
+ // quotes, in practice, not sure. ¯\_(ツ)_/¯
+
+ // Get the thing that refers to the footnote
+ const intextLink = document.querySelector("a.footnote-ref[href='#" + footnote.id + "']");
+ // Find its "content parent"; nearest paragraph or list item or
+ // whatever. We use this for alignment because it looks much cleaner.
+ // If it doesn't, your paragraphs are too long :P
+ // Fallback - use the same height as the link.
+ const verticalAlignmentTarget = intextLink.closest('p,li') || intextLink;
+
+ let offset = computeOffsetForAlignment(footnote, verticalAlignmentTarget);
+ if (offset < bottomOfLastElem) {
+ offset = bottomOfLastElem;
+ }
+ // computedStyle values are always in pixels, but have the suffix 'px'.
+ // offsetHeight doesn't include margins, but we want it to use them so
+ // we retain the style / visual fidelity when all the footnotes are
+ // crammed together.
+ bottomOfLastElem =
+ offset +
+ footnote.offsetHeight +
+ parseInt(window.getComputedStyle(footnote).marginBottom) +
+ parseInt(window.getComputedStyle(footnote).marginTop);
+
+ footnote.style.top = offset + 'px';
+ footnote.style.position = 'absolute';
+ });
+}
+
+function clearFootnoteOffsets(footnotes) {
+ // Reset all
+ Array.prototype.forEach.call(footnotes, function (fn, i) {
+ fn.style.top = null;
+ fn.style.position = null;
+ });
+}
+
+// contract: this is idempotent; i.e. it won't wreck anything if you call it
+// with the same value over and over again. Though maybe it'll wreck performance
+// lol.
+function updateFootnoteFloat(shouldFloat) {
+ const footnoteSection = document.querySelector(FOOTNOTE_SECTION_SELECTOR);
+ const footnotes = footnoteSection.querySelectorAll(
+ "li[role=doc-endnote]");
+
+ if (shouldFloat) {
+ // Do this first because we need styles applied before doing other
+ // calculations
+ footnoteSection.classList.add('floating-footnotes');
+ setFootnoteOffsets(footnotes);
+ subscribeToUpdates();
+ } else {
+ unsubscribeFromUpdates();
+ clearFootnoteOffsets(footnotes);
+ footnoteSection.classList.remove('floating-footnotes');
+ }
+}
+
+function subscribeToUpdates() {
+ const article = document.querySelector(ARTICLE_CONTENT_SELECTOR);
+ // Watch for dimension changes on the thing that holds all the footnotes so
+ // we can reposition as required
+ resizeObserver.observe(article);
+}
+
+function unsubscribeFromUpdates() {
+ resizeObserver.disconnect();
+}
+
+const notifySizeChange = function() {
+ // Default state, not expanded.
+ let bigEnough = false;
+
+ return function () {
+ // Pixel width at which this looks good
+ let nowBigEnough = window.innerWidth >= FLOATING_FOOTNOTE_MIN_WIDTH;
+ if (nowBigEnough !== bigEnough) {
+ updateFootnoteFloat(nowBigEnough);
+ bigEnough = nowBigEnough;
+ }
+ };
+}();
+
+const resizeObserver = new ResizeObserver((_entries, observer) => {
+ // By virtue of the fact that we're subscribed, we know this is true.
+ updateFootnoteFloat(true);
+});
+
+export default function enableFloatingFootnotes() {
+ docReady(() => {
+ const footnoteSection = document.querySelector(FOOTNOTE_SECTION_SELECTOR);
+ const article = document.querySelector(ARTICLE_CONTENT_SELECTOR);
+ const allowFloatingFootnotes = article && !article.classList.contains('no-floating-footnotes');
+
+ // only set it all up if there's actually a footnote section and
+ // we haven't explicitly disabled floating footnotes.
+ if (footnoteSection && allowFloatingFootnotes) {
+ onWindowResize(notifySizeChange);
+ }
+ });
+}
diff --git a/js/main.js b/js/main.js
new file mode 100644
index 0000000..25bfcd2
--- /dev/null
+++ b/js/main.js
@@ -0,0 +1,5 @@
+import anchorizeHeadings from "./anchorizeHeadings.js";
+import enableFloatingFootnotes from "./floatingFootnotes.js";
+
+enableFloatingFootnotes();
+anchorizeHeadings();
diff --git a/js/utils.js b/js/utils.js
new file mode 100644
index 0000000..d48d708
--- /dev/null
+++ b/js/utils.js
@@ -0,0 +1,29 @@
+// borrowed from https://stackoverflow.com/questions/9899372/pure-javascript-equivalent-of-jquerys-ready-how-to-call-a-function-when-t
+function docReady(fn) {
+ // see if DOM is already available
+ if (document.readyState === "complete" || document.readyState === "interactive") {
+ // call on next available tick
+ setTimeout(fn, 1);
+ } else {
+ document.addEventListener("DOMContentLoaded", fn);
+ }
+}
+
+function windowLoaded(fn) {
+ // see if we're already loaded
+ if (document.readyState === "complete") {
+ // call on next available tick
+ setTimeout(fn, 1);
+ } else {
+ window.addEventListener("load", fn);
+ }
+}
+
+function onWindowResize(fn) {
+ windowLoaded(function () {
+ window.addEventListener('resize', fn);
+ setTimeout(fn, 1);
+ });
+}
+
+export { docReady, windowLoaded, onWindowResize};