1// Hover footnotes, dependency-free (previously required jQuery). 2// 3// Exposes window.Footnotes.setup(root) so dynamically inserted content (the 4// infinite-scroll "load more") can re-bind its footnote links. The fade is 5// handled by a CSS transition on #footnotediv (see blog.css). 6(function () { 7 "use strict"; 8 9 var footnotetimeout = false; 10 11 function isTouch() { 12 var agent = navigator.userAgent.toLowerCase(); 13 return ( 14 agent.indexOf("iphone") >= 0 || 15 agent.indexOf("ipad") >= 0 || 16 agent.indexOf("ipod") >= 0 || 17 agent.indexOf("android") >= 0 18 ); 19 } 20 21 function pageOffset(el) { 22 var rect = el.getBoundingClientRect(); 23 return { 24 left: rect.left + window.pageXOffset, 25 top: rect.top + window.pageYOffset 26 }; 27 } 28 29 function remove() { 30 var div = document.getElementById("footnotediv"); 31 if (!div) { 32 return; 33 } 34 div.style.opacity = "0"; 35 window.setTimeout(function () { 36 if (div.parentNode) { 37 div.parentNode.removeChild(div); 38 } 39 }, 600); 40 } 41 42 function divOver() { 43 window.clearTimeout(footnotetimeout); 44 var div = document.getElementById("footnotediv"); 45 if (div) { 46 div.style.opacity = "0.9"; 47 } 48 } 49 50 function footnoteOut() { 51 footnotetimeout = window.setTimeout(remove, 100); 52 } 53 54 function footnoteOver(event) { 55 window.clearTimeout(footnotetimeout); 56 var existing = document.getElementById("footnotediv"); 57 if (existing && existing.parentNode) { 58 existing.parentNode.removeChild(existing); 59 } 60 61 var link = event.currentTarget; 62 var id = link.getAttribute("href").substr(1); 63 var target = document.getElementById(id); 64 if (!target) { 65 return; 66 } 67 68 var position = pageOffset(link); 69 var div = document.createElement("div"); 70 div.id = "footnotediv"; 71 div.innerHTML = "<div>" + target.innerHTML + "</div>"; 72 73 if (isTouch()) { 74 // On iOS, binding mouseover here would swallow the first tap; a 75 // click-to-dismiss is the reliable behaviour. 76 div.addEventListener("click", remove); 77 } else { 78 div.addEventListener("mouseover", divOver); 79 div.addEventListener("mouseout", footnoteOut); 80 } 81 82 document.body.appendChild(div); 83 84 var left = position.left; 85 if (left + 420 > window.innerWidth + window.pageXOffset) { 86 left = window.innerWidth - 420 + window.pageXOffset; 87 } 88 var top = position.top + 20; 89 if (top + div.offsetHeight > window.innerHeight + window.pageYOffset) { 90 top = position.top - div.offsetHeight - 15; 91 } 92 div.style.left = left + "px"; 93 div.style.top = top + "px"; 94 div.style.opacity = "0.9"; 95 } 96 97 function setup(root) { 98 root = root || document; 99 // Modern Markdown Extra marks footnote links with class="footnote-ref"; 100 // very old posts used rel="footnote". Match both. 101 var links = root.querySelectorAll("a.footnote-ref, a[rel='footnote']"); 102 Array.prototype.forEach.call(links, function (link) { 103 link.removeEventListener("mouseover", footnoteOver); 104 link.removeEventListener("mouseout", footnoteOut); 105 link.addEventListener("mouseover", footnoteOver); 106 link.addEventListener("mouseout", footnoteOut); 107 }); 108 } 109 110 window.Footnotes = { setup: setup }; 111 112 if (document.readyState === "loading") { 113 document.addEventListener("DOMContentLoaded", function () { 114 setup(document); 115 }); 116 } else { 117 setup(document); 118 } 119})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.