|
| 1 | +/** Copied from https://www.w3.org/scripts/TR/2016/fixup.js 2018-06-14 */ |
| 2 | + |
| 3 | +/****************************************************************************** |
| 4 | + * JS Extension for the W3C Spec Style Sheet * |
| 5 | + * * |
| 6 | + * This code handles: * |
| 7 | + * - some fixup to improve the table of contents * |
| 8 | + * - the obsolete warning on outdated specs * |
| 9 | + ******************************************************************************/ |
| 10 | +(function() { |
| 11 | + "use strict"; |
| 12 | + var ESCAPEKEY = 27; |
| 13 | + var collapseSidebarText = '<span aria-hidden="true">←</span> ' |
| 14 | + + '<span>Collapse Sidebar</span>'; |
| 15 | + var expandSidebarText = '<span aria-hidden="true">→</span> ' |
| 16 | + + '<span>Pop Out Sidebar</span>'; |
| 17 | + var tocJumpText = '<span aria-hidden="true">↑</span> ' |
| 18 | + + '<span>Jump to Table of Contents</span>'; |
| 19 | + |
| 20 | + var sidebarMedia = window.matchMedia('screen and (min-width: 78em)'); |
| 21 | + var autoToggle = function(e){ toggleSidebar(e.matches) }; |
| 22 | + if(sidebarMedia.addListener) { |
| 23 | + sidebarMedia.addListener(autoToggle); |
| 24 | + } |
| 25 | + |
| 26 | + function toggleSidebar(on, skipScroll) { |
| 27 | + if (on == undefined) { |
| 28 | + on = !document.body.classList.contains('toc-sidebar'); |
| 29 | + } |
| 30 | + |
| 31 | + if (!skipScroll) { |
| 32 | + /* Don't scroll to compensate for the ToC if we're above it already. */ |
| 33 | + var headY = 0; |
| 34 | + var head = document.querySelector('.head'); |
| 35 | + if (head) { |
| 36 | + // terrible approx of "top of ToC" |
| 37 | + headY += head.offsetTop + head.offsetHeight; |
| 38 | + } |
| 39 | + skipScroll = window.scrollY < headY; |
| 40 | + } |
| 41 | + |
| 42 | + var toggle = document.getElementById('toc-toggle'); |
| 43 | + var tocNav = document.getElementById('toc'); |
| 44 | + if (on) { |
| 45 | + var tocHeight = tocNav.offsetHeight; |
| 46 | + document.body.classList.add('toc-sidebar'); |
| 47 | + document.body.classList.remove('toc-inline'); |
| 48 | + toggle.innerHTML = collapseSidebarText; |
| 49 | + if (!skipScroll) { |
| 50 | + window.scrollBy(0, 0 - tocHeight); |
| 51 | + } |
| 52 | + tocNav.focus(); |
| 53 | + sidebarMedia.addListener(autoToggle); // auto-collapse when out of room |
| 54 | + } |
| 55 | + else { |
| 56 | + document.body.classList.add('toc-inline'); |
| 57 | + document.body.classList.remove('toc-sidebar'); |
| 58 | + toggle.innerHTML = expandSidebarText; |
| 59 | + if (!skipScroll) { |
| 60 | + window.scrollBy(0, tocNav.offsetHeight); |
| 61 | + } |
| 62 | + if (toggle.matches(':hover')) { |
| 63 | + /* Unfocus button when not using keyboard navigation, |
| 64 | + because I don't know where else to send the focus. */ |
| 65 | + toggle.blur(); |
| 66 | + } |
| 67 | + } |
| 68 | + } |
| 69 | + |
| 70 | + function createSidebarToggle() { |
| 71 | + /* Create the sidebar toggle in JS; it shouldn't exist when JS is off. */ |
| 72 | + var toggle = document.createElement('a'); |
| 73 | + /* This should probably be a button, but appearance isn't standards-track.*/ |
| 74 | + toggle.id = 'toc-toggle'; |
| 75 | + toggle.class = 'toc-toggle'; |
| 76 | + toggle.href = '#toc'; |
| 77 | + toggle.innerHTML = collapseSidebarText; |
| 78 | + |
| 79 | + sidebarMedia.addListener(autoToggle); |
| 80 | + var toggler = function(e) { |
| 81 | + e.preventDefault(); |
| 82 | + sidebarMedia.removeListener(autoToggle); // persist explicit off states |
| 83 | + toggleSidebar(); |
| 84 | + return false; |
| 85 | + } |
| 86 | + toggle.addEventListener('click', toggler, false); |
| 87 | + |
| 88 | + |
| 89 | + /* Get <nav id=toc-nav>, or make it if we don't have one. */ |
| 90 | + var tocNav = document.getElementById('toc-nav'); |
| 91 | + if (!tocNav) { |
| 92 | + tocNav = document.createElement('p'); |
| 93 | + tocNav.id = 'toc-nav'; |
| 94 | + /* Prepend for better keyboard navigation */ |
| 95 | + document.body.insertBefore(tocNav, document.body.firstChild); |
| 96 | + } |
| 97 | + /* While we're at it, make sure we have a Jump to Toc link. */ |
| 98 | + var tocJump = document.getElementById('toc-jump'); |
| 99 | + if (!tocJump) { |
| 100 | + tocJump = document.createElement('a'); |
| 101 | + tocJump.id = 'toc-jump'; |
| 102 | + tocJump.href = '#toc'; |
| 103 | + tocJump.innerHTML = tocJumpText; |
| 104 | + tocNav.appendChild(tocJump); |
| 105 | + } |
| 106 | + |
| 107 | + tocNav.appendChild(toggle); |
| 108 | + } |
| 109 | + |
| 110 | + var toc = document.getElementById('toc'); |
| 111 | + if (toc) { |
| 112 | + if (!document.getElementById('toc-toggle')) { |
| 113 | + createSidebarToggle(); |
| 114 | + } |
| 115 | + toggleSidebar(sidebarMedia.matches, true); |
| 116 | + |
| 117 | + /* If the sidebar has been manually opened and is currently overlaying the text |
| 118 | + (window too small for the MQ to add the margin to body), |
| 119 | + then auto-close the sidebar once you click on something in there. */ |
| 120 | + toc.addEventListener('click', function(e) { |
| 121 | + if(e.target.tagName.toLowerCase() == "a" && document.body.classList.contains('toc-sidebar') && !sidebarMedia.matches) { |
| 122 | + toggleSidebar(false); |
| 123 | + } |
| 124 | + }, false); |
| 125 | + } |
| 126 | + else { |
| 127 | + console.warn("Can't find Table of Contents. Please use <nav id='toc'> around the ToC."); |
| 128 | + } |
| 129 | + |
| 130 | + /* Wrap tables in case they overflow */ |
| 131 | + var tables = document.querySelectorAll(':not(.overlarge) > table.data, :not(.overlarge) > table.index'); |
| 132 | + var numTables = tables.length; |
| 133 | + for (var i = 0; i < numTables; i++) { |
| 134 | + var table = tables[i]; |
| 135 | + if (!table.matches('.example *, .note *, .advisement *, .def *, .issue *')) { |
| 136 | + /* Overflowing colored boxes looks terrible, and also |
| 137 | + the kinds of tables inside these boxes |
| 138 | + are less likely to need extra space. */ |
| 139 | + var wrapper = document.createElement('div'); |
| 140 | + wrapper.className = 'overlarge'; |
| 141 | + table.parentNode.insertBefore(wrapper, table); |
| 142 | + wrapper.appendChild(table); |
| 143 | + } |
| 144 | + } |
| 145 | + |
| 146 | + /* Deprecation warning */ |
| 147 | + if (document.location.hostname === "www.w3.org") { |
| 148 | + var request = new XMLHttpRequest(); |
| 149 | + |
| 150 | + request.open('GET', '//www.w3.org/TR/tr-outdated-spec'); |
| 151 | + request.onload = function() { |
| 152 | + if (request.status < 200 || request.status >= 400) { |
| 153 | + return; |
| 154 | + } |
| 155 | + try { |
| 156 | + var currentSpec = JSON.parse(request.responseText); |
| 157 | + } catch (err) { |
| 158 | + console.error(err); |
| 159 | + return; |
| 160 | + } |
| 161 | + document.body.classList.add("outdated-spec"); |
| 162 | + var node = document.createElement("p"); |
| 163 | + node.classList.add("outdated-warning"); |
| 164 | + node.tabIndex = -1; |
| 165 | + node.setAttribute("role", "dialog"); |
| 166 | + node.setAttribute("aria-modal", "true"); |
| 167 | + node.setAttribute("aria-labelledby", "outdatedWarning"); |
| 168 | + if (currentSpec.style) { |
| 169 | + node.classList.add(currentSpec.style); |
| 170 | + } |
| 171 | + |
| 172 | + var frag = document.createDocumentFragment(); |
| 173 | + var heading = document.createElement("strong"); |
| 174 | + heading.id = "outdatedWarning"; |
| 175 | + heading.innerHTML = currentSpec.header; |
| 176 | + frag.appendChild(heading); |
| 177 | + |
| 178 | + var anchor = document.createElement("a"); |
| 179 | + anchor.id = "outdated-note"; |
| 180 | + anchor.href = currentSpec.latestUrl; |
| 181 | + anchor.innerText = currentSpec.latestUrl + "."; |
| 182 | + |
| 183 | + var warning = document.createElement("span"); |
| 184 | + warning.innerText = currentSpec.warning; |
| 185 | + warning.appendChild(anchor); |
| 186 | + frag.appendChild(warning); |
| 187 | + |
| 188 | + var button = document.createElement("button"); |
| 189 | + var handler = makeClickHandler(node); |
| 190 | + button.addEventListener("click", handler); |
| 191 | + button.innerHTML = "▾ collapse"; |
| 192 | + frag.appendChild(button); |
| 193 | + node.appendChild(frag); |
| 194 | + |
| 195 | + function makeClickHandler(node) { |
| 196 | + var isOpen = true; |
| 197 | + return function collapseWarning(event) { |
| 198 | + var button = event.target; |
| 199 | + isOpen = !isOpen; |
| 200 | + node.classList.toggle("outdated-collapsed"); |
| 201 | + document.body.classList.toggle("outdated-spec"); |
| 202 | + button.innerText = (isOpen) ? '\u25BE collapse' : '\u25B4 expand'; |
| 203 | + } |
| 204 | + } |
| 205 | + |
| 206 | + document.body.appendChild(node); |
| 207 | + button.focus(); |
| 208 | + window.onkeydown = function (event) { |
| 209 | + var isCollapsed = node.classList.contains("outdated-collapsed"); |
| 210 | + if (event.keyCode === ESCAPEKEY && !isCollapsed) { |
| 211 | + button.click(); |
| 212 | + } |
| 213 | + } |
| 214 | + |
| 215 | + document.addEventListener("focus", function(event) { |
| 216 | + var isCollapsed = node.classList.contains("outdated-collapsed"); |
| 217 | + var containsTarget = node.contains(event.target); |
| 218 | + if (!isCollapsed && !containsTarget) { |
| 219 | + event.stopPropagation(); |
| 220 | + node.focus(); |
| 221 | + } |
| 222 | + }, true); // use capture to enable event delegation as focus doesn't bubble up |
| 223 | + }; |
| 224 | + |
| 225 | + request.onerror = function() { |
| 226 | + console.error("Request to https://www.w3.org/TR/tr-outdated-spec failed."); |
| 227 | + }; |
| 228 | + |
| 229 | + request.send(); |
| 230 | + } |
| 231 | +})(); |
0 commit comments