1/* Blog article behaviour (legacy blog-post.html:6991â7082): the table-of-contents scroll-spy and 2 the share buttons. Nothing else lives here â the newsletter form is forms.js. */ 3(function () { 4 'use strict'; 5 6 /* ââ table of contents ââââââââââââââââââââââââââââââââââââââââââââââââ 7 Marks the section you are actually reading. rootMargin pulls the 8 trigger line down to roughly a third of the viewport so the highlight 9 changes when a heading reaches reading position, not when it grazes 10 the top edge. */ 11 var toc = document.getElementById('zr-toc'); 12 if (toc && 'IntersectionObserver' in window) { 13 var links = {}; 14 toc.querySelectorAll('a[href^="#"]').forEach(function (a) { 15 links[a.getAttribute('href').slice(1)] = a; 16 }); 17 var targets = Object.keys(links) 18 .map(function (id) { return document.getElementById(id); }) 19 .filter(Boolean); 20 21 var mark = function (id) {
22 Object.keys(links).forEach(function (k) { 23 links[k].classList.toggle('is-here', k === id); 24 }); 25 }; 26 27 var seen = new Set(); 28 var io = new IntersectionObserver(function (entries) { 29 entries.forEach(function (e) { 30 if (e.isIntersecting) seen.add(e.target.id); else seen.delete(e.target.id); 31 }); 32 /* the topmost heading currently in the band wins */ 33 for (var i = 0; i < targets.length; i++) { 34 if (seen.has(targets[i].id)) { mark(targets[i].id); return; } 35 } 36 }, { rootMargin: '-110px 0px -66% 0px' }); 37 targets.forEach(function (t) { io.observe(t); }); 38 } 39 40 /* ââ share ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 41 The intent URLs are rendered server-side into data-href (so the 42 shared URL is the canonical one, never an empty string); the copy 43 mirrors the MCP pill: async clipboard first, a hidden textarea behind 44 it, and the tick only shows if one of them actually reported success. */ 45 var open = function (href) { 46 window.open(href, '_blank', 'noopener,noreferrer,width=620,height=640'); 47 }; 48 document.querySelectorAll('.zr-art__share button[data-href]').forEach(function (btn) { 49 btn.addEventListener('click', function () { open(btn.getAttribute('data-href')); }); 50 }); 51 52 var copyBtn = document.getElementById('zr-share-copy'); 53 if (copyBtn) { 54 var url = copyBtn.getAttribute('data-copy') || location.href.split('#')[0]; 55 var fallback = function (text) { 56 var ta = document.createElement('textarea'); 57 ta.value = text; 58 ta.setAttribute('readonly', ''); 59 ta.style.cssText = 'position:fixed;top:0;left:-9999px;opacity:0'; 60 document.body.appendChild(ta); 61 ta.select(); 62 var ok = false; 63 try { ok = document.execCommand('copy'); } catch (err) { ok = false; } 64 document.body.removeChild(ta); 65 return ok; 66 }; 67 var timer, flag = function (ok) { 68 clearTimeout(timer); 69 copyBtn.classList.toggle('is-copied', ok); 70 copyBtn.setAttribute('aria-label', ok ? 'Link copied' : 'Copy link to this article'); 71 if (!ok) return; 72 timer = setTimeout(function () { 73 copyBtn.classList.remove('is-copied'); 74 copyBtn.setAttribute('aria-label', 'Copy link to this article'); 75 }, 1800); 76 }; 77 copyBtn.addEventListener('click', function () { 78 if (navigator.clipboard && navigator.clipboard.writeText) { 79 navigator.clipboard.writeText(url).then(function () { flag(true); }) 80 .catch(function () { flag(fallback(url)); }); 81 } else { 82 flag(fallback(url)); 83 } 84 }); 85 } 86})();
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.