1/** 2 * Sparkling theme scripts. 3 * 4 * Plain DOM APIs -- no jQuery. The two globals (SparklingIsMobile and 5 * generateMobileMenu) are kept on window because child themes may call them. 6 */ 7( function () { 8 'use strict'; 9 10 /** 11 * Run a callback once the DOM is ready. 12 * 13 * @param {Function} fn Callback. 14 */ 15 function ready( fn ) { 16 if ( document.readyState !== 'loading' ) { 17 fn(); 18 } else { 19 document.addEventListener( 'DOMContentLoaded', fn ); 20 } 21 } 22 23 /** 24 * Add class names to every element matching a selector. 25 * 26 * @param {string} selector CSS selector. 27 * @param {Array} classes Class names to add. 28 */ 29 function addClasses( selector, classes ) { 30 document.querySelectorAll( selector ).forEach( function ( el ) { 31 el.classList.add.apply( el.classList, classes ); 32 } ); 33 } 34 35 /** 36 * jQuery's "swing" easing, so the scroll-to-top motion is unchanged. 37 * 38 * @param {number} p Progress from 0 to 1. 39 * @return {number} Eased progress. 40 */ 41 function swing( p ) { 42 return 0.5 - Math.cos( p * Math.PI ) / 2; 43 } 44 45 function prefersReducedMotion() { 46 return window.matchMedia && window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches; 47 } 48 49 /** 50 * Fade an element in or out, matching jQuery's default 400ms fade. 51 * 52 * @param {Element} el Target element. 53 * @param {boolean} into True to fade in, false to fade out. 54 */ 55 function fade( el, into ) { 56 if ( ! el || el.dataset.sparklingFading === String( into ) ) { 57 return; 58 } 59 60 el.dataset.sparklingFading = String( into ); 61 62 if ( prefersReducedMotion() ) { 63 el.style.opacity = into ? '1' : ''; 64 el.style.display = into ? 'block' : 'none'; 65 return; 66 } 67 68 var duration = 400; 69 var start = null; 70 var from = parseFloat( window.getComputedStyle( el ).opacity ); 71 72 if ( isNaN( from ) ) { 73 from = into ? 0 : 1; 74 } 75 76 if ( into ) { 77 el.style.display = 'block'; 78 } 79 80 function step( ts ) { 81 if ( start === null ) { 82 start = ts; 83 } 84 85 var p = Math.min( ( ts - start ) / duration, 1 ); 86 var v = from + ( ( into ? 1 : 0 ) - from ) * swing( p ); 87 88 el.style.opacity = String( v ); 89 90 if ( p < 1 ) { 91 window.requestAnimationFrame( step ); 92 } else if ( ! into ) { 93 el.style.display = 'none'; 94 el.style.opacity = ''; 95 } 96 } 97 98 window.requestAnimationFrame( step ); 99 } 100 101 /** 102 * Detect a mobile user agent. 103 * 104 * Kept as-is (including the UA sniff) because the mobile menu styling below 105 * depends on it and child themes may override this function. 106 * 107 * @return {boolean} True on a mobile user agent. 108 */ 109 window.SparklingIsMobile = function () { 110 return ( 111 navigator.userAgent.match( /Android/i ) || 112 navigator.userAgent.match( /webOS/i ) || 113 navigator.userAgent.match( /iPhone/i ) || 114 navigator.userAgent.match( /iPod/i ) || 115 navigator.userAgent.match( /iPad/i ) || 116 navigator.userAgent.match( /BlackBerry/ ) 117 ); 118 }; 119 120 /** 121 * Toggle the tap-friendly menu class on touch devices at tablet width and up. 122 */ 123 window.generateMobileMenu = function () { 124 var menu = document.querySelector( '#masthead .site-navigation-inner .navbar-collapse > ul.nav' ); 125 126 if ( ! menu ) { 127 return; 128 } 129 130 if ( window.SparklingIsMobile() && window.innerWidth > 767 ) { 131 menu.classList.add( 'sparkling-mobile-menu' ); 132 } else { 133 menu.classList.remove( 'sparkling-mobile-menu' ); 134 } 135 }; 136 137 /** 138 * Apply Bootstrap classes to markup WordPress generates. 139 */ 140 function styleCoreMarkup() { 141 addClasses( '.comment-reply-link', [ 'btn', 'btn-sm', 'btn-default' ] ); 142 addClasses( 143 '#submit, button[type=submit], html input[type=button], input[type=reset], input[type=submit]', 144 [ 'btn', 'btn-default' ] 145 ); 146 addClasses( '.widget_rss ul', [ 'media-list' ] ); 147 addClasses( '.postform', [ 'form-control' ] ); 148 addClasses( 'table#wp-calendar', [ 'table', 'table-striped' ] ); 149 } 150 151 /** 152 * Scroll-to-top button: reveal past 100px, and smooth-scroll on click. 153 */ 154 function initScrollToTop() { 155 var button = document.querySelector( '.scroll-to-top' ); 156 157 if ( ! button ) { 158 return; 159 } 160 161 function onScroll() { 162 fade( button, ( window.pageYOffset || document.documentElement.scrollTop ) > 100 ); 163 } 164 165 window.addEventListener( 'scroll', onScroll, { passive: true } ); 166 onScroll(); 167 168 button.addEventListener( 'click', function ( event ) { 169 event.preventDefault(); 170 171 var from = window.pageYOffset || document.documentElement.scrollTop; 172 173 if ( ! from ) { 174 return; 175 } 176 177 if ( prefersReducedMotion() ) { 178 window.scrollTo( 0, 0 ); 179 return; 180 } 181 182 var duration = 800; 183 var start = null; 184 185 function step( ts ) { 186 if ( start === null ) { 187 start = ts; 188 } 189 190 var p = Math.min( ( ts - start ) / duration, 1 ); 191 192 window.scrollTo( 0, from * ( 1 - swing( p ) ) ); 193 194 if ( p < 1 ) { 195 window.requestAnimationFrame( step ); 196 } 197 } 198 199 window.requestAnimationFrame( step ); 200 } ); 201 } 202 203 /** 204 * Open or close a menu item's submenu and report it on its toggle button. 205 * 206 * @param {Element} item The <li>. 207 * @param {boolean} open Whether to open it. 208 */ 209 function setSubmenuOpen( item, open ) { 210 item.classList.toggle( 'open', open ); 211 212 var toggle = item.querySelector( ':scope > .sparkling-dropdown' ); 213 214 if ( toggle ) { 215 toggle.setAttribute( 'aria-expanded', open ? 'true' : 'false' ); 216 } 217 } 218
219 /** 220 * Submenu toggles in the navbar. 221 */ 222 function initDropdowns() { 223 document.querySelectorAll( '.sparkling-dropdown' ).forEach( function ( toggle ) { 224 toggle.addEventListener( 'click', function ( event ) { 225 event.preventDefault(); 226 setSubmenuOpen( toggle.parentNode, ! toggle.parentNode.classList.contains( 'open' ) ); 227 } ); 228 } ); 229 230 // A "#" parent item has nowhere to go, so it opens its submenu instead of 231 // jumping to the top of the page. 232 document.querySelectorAll( '.navbar-nav li.dropdown > a[href="#"]' ).forEach( function ( link ) { 233 link.addEventListener( 'click', function ( event ) { 234 event.preventDefault(); 235 setSubmenuOpen( link.parentNode, ! link.parentNode.classList.contains( 'open' ) ); 236 } ); 237 } ); 238 239 // Escape closes the submenu that holds focus and returns focus to its toggle. 240 document.addEventListener( 'keydown', function ( event ) { 241 if ( 'Escape' !== event.key || ! event.target.closest ) { 242 return; 243 } 244 245 var item = event.target.closest( '.navbar-nav li.open' ); 246 247 if ( ! item ) { 248 return; 249 } 250 251 setSubmenuOpen( item, false ); 252 253 var toggle = item.querySelector( ':scope > .sparkling-dropdown' ); 254 255 if ( toggle ) { 256 toggle.focus(); 257 } 258 } ); 259 } 260 261 /** 262 * Keep the menu button's aria-expanded in step with the collapsed menu. 263 * 264 * Bootstrap only updates triggers whose data-target is an #id; this one 265 * targets the .navbar-ex1-collapse class, which child themes rely on. 266 * Bootstrap announces the change through jQuery events, so listen there. 267 */ 268 function initMenuToggleState() { 269 var toggle = document.querySelector( '.navbar-toggle[data-target=".navbar-ex1-collapse"]' ); 270 271 if ( ! toggle || ! window.jQuery ) { 272 return; 273 } 274 275 window.jQuery( document ).on( 'shown.bs.collapse hidden.bs.collapse', '.navbar-ex1-collapse', function ( event ) { 276 toggle.setAttribute( 'aria-expanded', 'shown' === event.type ? 'true' : 'false' ); 277 } ); 278 } 279 280 /** 281 * Sticky header: keep the fixed navbar below the admin bar, reserve its 282 * height above the content, and make in-page links stop below it. 283 * 284 * The space was measured once, at load, so it went stale when the menu 285 * wrapped to a second row on a narrower window. With the admin bar the 286 * navbar sat at top: 0, under the bar, and the space was set to the 287 * difference of the two heights, so the bar hid the top of the navbar. 288 * And an anchor -- #comments, a "Skip to main content" target, a menu 289 * link to a section -- scrolled its target under the navbar. 290 */ 291 function initStickyHeader() { 292 var navbar = document.querySelector( '.navbar-fixed-top' ); 293 var header = navbar ? navbar.closest( 'header' ) : null; 294 295 if ( ! header ) { 296 return; 297 } 298 299 var adminbar = document.getElementById( 'wpadminbar' ); 300 var queued = false; 301 302 function layout() { 303 queued = false; 304 305 var top = 0; 306 var barFixed = false; 307 308 if ( adminbar ) { 309 // Below 600px the admin bar scrolls away with the page. 310 barFixed = 'fixed' === window.getComputedStyle( adminbar ).position; 311 top = Math.max( 0, adminbar.getBoundingClientRect().bottom ); 312 navbar.style.top = top + 'px'; 313 } 314 315 var navHeight = navbar.offsetHeight; 316 317 header.style.marginBottom = navHeight + 'px'; 318 document.documentElement.style.scrollPaddingTop = ( ( barFixed ? top : 0 ) + navHeight ) + 'px'; 319 } 320 321 function queue() { 322 if ( ! queued ) { 323 queued = true; 324 window.requestAnimationFrame( layout ); 325 } 326 } 327 328 layout(); 329 window.addEventListener( 'resize', queue ); 330 331 if ( adminbar ) { 332 window.addEventListener( 'scroll', queue, { passive: true } ); 333 } 334 } 335 336 ready( function () { 337 styleCoreMarkup(); 338 initScrollToTop(); 339 initDropdowns(); 340 initMenuToggleState(); 341 initStickyHeader(); 342 window.generateMobileMenu(); 343 344 window.addEventListener( 'resize', window.generateMobileMenu ); 345 } ); 346}() );
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.