1(() => { 2 'use strict'; 3 const html = document.querySelector( 'html' ); 4 const body = document.querySelector( 'body' ); 5 let errors = []; 6 if ( wpa.lang ) { 7 let lang = html.getAttribute( 'lang' ); 8 if ( ! lang ) { 9 html.setAttribute( 'lang', wpa.lang ); 10 if ( wpa.errors || wpa.tracking ) { 11 errors.push( 'html-lang' ); 12 console.log( 'HTML language set by WP Accessibility' ); 13 } 14 } 15 } 16 17 if ( wpa.dir ) { 18 let dir = html.getAttribute( 'dir' ); 19 if ( ! dir && wpa.dir !== 'ltr' ) { 20 html.setAttributeattr( 'dir', wpa.dir ); 21 if ( wpa.errors || wpa.tracking ) { 22 errors.push( 'html-lang-direction' ); 23 console.log( 'HTML language direction set by WP Accessibility' ); 24 } 25 } 26 } 27 28 if ( wpa.continue ) { 29 let readMore = document.querySelectorAll( '.wp-block-post-excerpt__more-link' ); 30 if ( readMore.length !== 0 ) { 31 readMore.forEach( (el) => { 32 if ( ! el.hasAttribute( 'aria-describedby' ) ) { 33 let post = el.closest( '.wp-block-post' ); 34 let readMoreId = post.getAttribute( 'class' ); 35 readMoreId = readMoreId.replaceAll( ' ', '-' ); 36 let heading = post.querySelector( '.wp-block-post-title' ); 37 if ( heading ) { 38 if ( ! heading.hasAttribute( 'id' ) ) { 39 heading.setAttribute( 'id', readMoreId ); 40 } else { 41 readMoreId = heading.getAttribute( 'id' ); 42 } 43 el.setAttribute( 'aria-describedby', readMoreId ); 44 if ( wpa.errors || wpa.tracking ) { 45 console.log( 'Continue Reading link description set by WP Accessibility' ); 46 } 47 } 48 } 49 }); 50 } 51 } 52 53 const viewport = document.querySelector( 'meta[name="viewport"]' ); 54 if ( viewport && wpa.viewport ) { 55 let conditionsBefore = viewport.getAttribute( 'content' ); 56 let conditionsAfter = viewport.getAttribute( 'content' ); 57 if ( conditionsBefore.search(/user-scalable=no/g) ) { 58 conditionsAfter = conditionsBefore.replace( 'user-scalable=no', 'user-scalable=yes' ); 59 viewport.setAttribute( 'content', conditionsAfter ); 60 if ( ( wpa.errors || wpa.tracking ) && conditionsAfter != conditionsBefore ) { 61 errors.push( 'viewport-scalable' ); 62 console.log( 'Viewport made scalable by WP Accessibility' ); 63 } 64 } 65 if ( conditionsBefore.search(/maximum-scale=1/g) ) { 66 conditionsAfter = conditionsBefore.replace( 'maximum-scale=1', 'maximum-scale=5' ); 67 conditionsAfter = conditionsAfter.replace( 'maximum-scale=0', 'maximum-scale=5' ); 68 viewport.setAttribute( 'content', conditionsAfter ); 69 if ( ( wpa.errors || wpa.tracking ) && conditionsAfter != conditionsBefore ) { 70 errors.push( 'viewport-maxscale' ); 71 console.log( 'Viewport maximum scale set by WP Accessibility' ); 72 } 73 } 74 } 75 76 if ( wpa.skiplinks.enabled ) { 77 body.insertAdjacentHTML( 'afterbegin', wpa.skiplinks.output ); 78 if ( wpa.errors || wpa.tracking ) { 79 errors.push( 'skiplinks' ); 80 console.log( 'Skip links added by WP Accessibility' ); 81 } 82 } 83 84 if ( wpa.current ) { 85 let current = document.querySelectorAll( '.current-menu-item a, .current_page_item a' ); 86 if ( current.length !== 0 ) { 87 current.forEach((el) => { 88 el.setAttribute( 'aria-current', 'page' ); 89 }); 90 if ( wpa.errors || wpa.tracking ) { 91 errors.push( 'aria-current' ); 92 console.log( 'ARIA current added by WP Accessibility' ); 93 } 94 } 95 } 96 97 if ( wpa.labels ) { 98 let wpa_names = [ 's', 'author', 'email', 'url', 'comment' ]; 99 wpa_names.forEach((value) => { 100 let fields, field_id, implicit, aria, ariaId, ariaTarget, hasAria, hasAriaId, label, labelText; 101 if ( value == 'comment' ) { 102 fields = document.querySelectorAll( 'textarea[name=' + value + ']' ); 103 } else { 104 fields = document.querySelectorAll( 'input[name=' + value + ']:not(#adminbar-search)' ); 105 } 106 if ( fields.length !== 0 ) { 107 fields.forEach( (field) => { 108 if ( 0 !== field.length ) { 109 field_id = field.getAttribute( 'id' ); 110 implicit = field.closest( 'label' ); 111 aria = field.getAttribute( 'aria-label' ); 112 ariaId = field.getAttribute( 'aria-labelledby' ); 113 ariaTarget = {}; 114 if ( ariaId ) { 115 ariaTarget = document.getElementById( ariaId ); 116 } 117 hasAria = ( null == aria || '' == aria || 'undefined' == typeof( aria ) ) ? false : true; 118 hasAriaId = ( null == ariaId || '' == ariaId || 'undefined' == typeof( ariaId ) ) ? false : true; 119 // Add label if aria label empty, aria labelledby empty, or aria reference ID does not exist. 120 if ( ( ! hasAria && ! hasAriaId ) || ( ! hasAria && ( hasAriaId && ! ariaTarget ) ) ) { 121 if ( field_id ) { 122 label = document.querySelector( 'label[for=' + field_id + ']' ); 123 if ( label ) { 124 labelText = label.innerText; 125 if ( label && ! labelText ) { 126 label.innerText = wpa.wpalabels[value]; 127 if ( wpa.errors || wpa.tracking ) { 128 errors.push( ['empty-label', wpa.wpalabels[value]] ); 129 console.log( 'Empty label on ' + wpa.wpalabels[value] + ' added by WP Accessibility' ); 130 } 131 } 132 } 133 if ( !label && !implicit ) { 134 field.insertAdjacentHTML( 'beforebegin', "<label for='" + field_id + "' class='wpa-screen-reader-text'>" + wpa.wpalabels[value] + "</label>" ); 135 if ( wpa.errors || wpa.tracking ) { 136 errors.push( ['explicit-label', wpa.wpalabels[value]] ); 137 console.log( 'Explicit label on ' + wpa.wpalabels[value] + ' added by WP Accessibility' ); 138 } 139 } 140 } else { 141 if ( !implicit ) { 142 field.setAttribute( 'id', 'wpa_label_' + value ); 143 field.insertAdjacentHTML( 'beforebegin', "<label for='wpa_label_" + value + "' class='wpa-screen-reader-text'>" + wpa.wpalabels[value] + "</label>" ); 144 if ( wpa.errors || wpa.tracking ) { 145 errors.push( ['implicit-label', wpa.wpalabels[value]] ); 146 console.log( 'Implicit label on ' + wpa.wpalabels[value] + ' added by WP Accessibility' ); 147 } 148 } 149 } 150 } 151 } 152 }); 153 } 154 }); 155 } 156 157 if ( wpa.titles ) { 158 let images = 0, 159 controls = 0, 160 fields = 0, 161 noremove = false; 162 const els = document.querySelectorAll( 'img, a, input, textarea, select, button' ); 163 if ( els.length !== 0 ) {
164 els.forEach((el) => { 165 let title = el.getAttribute( 'title' ); 166 if ( el.classList.contains( 'nturl' ) ) { 167 // Exempt title attributes from Translate WordPress - Google Language Translator, which uses them as a CSS hook. 168 noremove = true; 169 } 170 if ( title && '' !== title ) { 171 switch ( el.tagName ) { 172 case 'IMG': 173 // If image has alt, remove title. If not, set title as alt. 174 let alt = el.getAttribute( 'alt' ); 175 title = el.getAttribute( 'title' ); 176 if ( ! alt || '' === alt ) { 177 el.setAttribute( 'alt', title ); 178 el.removeAttribute( 'title' ); 179 } else { 180 if ( title === alt ) { 181 el.removeAttribute( 'title' ); 182 } 183 } 184 images++; 185 break; 186 case 'A': 187 case 'BUTTON': 188 // If link or button has contained text or an img with alt, remove title. Otherwise, set title as aria-label unless element already has aria-label. 189 let linkText = wpaElementText(el); 190 if ( ! linkText || '' === linkText ) { 191 let ariaLabel = el.getAttribute( 'aria-label' ); 192 if ( ! ariaLabel || '' === ariaLabel ) { 193 el.setAttribute( 'aria-label', title ); 194 if ( ! noremove ) { 195 el.removeAttribute( 'title' ); 196 } 197 } 198 } else { 199 el.removeAttribute( 'title' ); 200 } 201 controls++; 202 break; 203 case 'INPUT': 204 case 'SELECT': 205 case 'TEXTAREA': 206 // If input field has an aria-label, aria-labelledby, associated label, or wrapping label, remove title. Else, add title as aria-label. 207 let ariaLabel = el.getAttribute( 'aria-label' ); 208 let ariaLabelled = el.getAttribute( 'aria-labelledby' ); 209 let ariaLabeller = ( ariaLabelled ) ? document.getElementById( ariaLabelled ) : false; 210 let labelId = el.getAttribute( 'id' ); 211 let label = ( labelId ) ? document.querySelector( 'label[for="' + labelId + '"]' ) : false; 212 let parentLabel = el.closest( 'label' ); 213 let hasAriaLabel = ( ariaLabel && '' !== ariaLabel ) ? true : false; 214 let hasRealLabel = ( label && '' !== wpaElementText( label ) ) ? true : false; 215 let hasImplicitLabel = ( parentLabel && '' !== wpaElementText( parentLabel ) ) ? true : false; 216 let hasAriaLabelled = ( ariaLabeller && '' !== wpaElementText( ariaLabeller ) ) ? true : false; 217 if ( hasAriaLabel || hasRealLabel || hasImplicitLabel || hasAriaLabelled ) { 218 // This has a label. 219 el.removeAttribute( 'title' ); 220 } else { 221 el.setAttribute( 'aria-label', title ); 222 el.removeAttribute( 'title' ); 223 } 224 fields++; 225 break; 226 } 227 } 228 }); 229 if ( wpa.errors || wpa.tracking ) { 230 if ( images > 0 ) { 231 errors.push( ['images-titles', images] ); 232 console.log( images + ' title attributes removed from images by WP Accessibility' ); 233 } 234 if ( controls > 0 ) { 235 errors.push( ['control-titles', controls] ); 236 console.log( controls + ' title attributes removed from links and buttons by WP Accessibility' ); 237 } 238 if ( fields > 0 ) { 239 errors.push( ['input-titles', fields] ); 240 console.log( fields + ' title attributes removed from input fields by WP Accessibility' ); 241 } 242 } 243 } 244 } 245 246 if ( wpa.target ) { 247 let targeted = document.querySelectorAll('a:not(.wpa-allow-target)'); 248 let targetRemoved = 0; 249 if ( targeted.length !== 0 ) { 250 targeted.forEach( (el) => { 251 let target = el.getAttribute( 'target' ); 252 let href = el.getAttribute( 'href' ); 253 254 if ( target ) { 255 // For on-page fragments. 256 if ( href.startsWith( '#') ) { 257 el.removeAttribute( 'target' ); 258 } else { 259 try { 260 let url = new URL( href ); 261 let hostname = url.hostname; 262 if ( ! hostname.includes( 'facebook' ) ) { 263 el.removeAttribute( 'target' ); 264 targetRemoved++; 265 } 266 } catch (exception) { 267 console.log( "href value is not a URL or a fragment:", href ); 268 // No action; the `href` attribute didn't resolve as a URL. 269 } 270 } 271 } 272 }); 273 if ( targetRemoved > 0 && ( wpa.errors || wpa.tracking ) ) { 274 errors.push( ['link-targets', targetRemoved] ); 275 console.log( targetRemoved + ' target attributes removed from links by WP Accessibility' ); 276 } 277 } 278 } 279 280 if ( wpa.tabindex ) { 281 // Remove tabindex from elements that should be natively focusable. 282 let focusable = document.querySelectorAll('input:not(.wpa-ignore-tabindex),a[href]:not(.wpa-ignore-tabindex),select:not(.wpa-ignore-tabindex),textarea:not(.wpa-ignore-tabindex),button:not(.wpa-ignore-tabindex)'); 283 let tabRemoved = 0;
284 if ( focusable.length !== 0 ) { 285 focusable.forEach( (el) => { 286 let tabindex = el.getAttribute('tabindex'); 287 let skip = false; 288 // Check for intentionally hidden elements with tabindex of -1. 289 if ( el.hasAttribute('tabindex') && tabindex === '-1' ) { 290 let hasAriaHidden = el.getAttribute('aria-hidden'); 291 let hasHidden = el.hasAttribute('hidden'); 292 if ( hasAriaHidden === 'true' || hasHidden ) { 293 skip = true; 294 } 295 } 296 if ( tabindex && !skip ) { 297 el.removeAttribute('tabindex'); 298 tabRemoved++; 299 } 300 }); 301 302 if ( tabRemoved > 0 && ( wpa.errors || wpa.tracking ) ) { 303 errors.push( ['control-tabindex', tabRemoved] ); 304 console.log( tabRemoved + ' tabindex attributes removed from links, buttons and inputs by WP Accessibility' ); 305 } 306 } 307 308 // Add tabindex to elements that appear active but are not natively focusable. 309 let fakeButtons = document.querySelectorAll( '[role="button"]:not([tabindex]):not(a):not(button)' ); 310 let buttonLinks = document.querySelectorAll( 'a[role="button"]:not([tabindex]):not([href])'); 311 let fakeLinks = document.querySelectorAll( '[role="link"]:not([tabindex]):not(a)'); 312 let linkLinks = document.querySelectorAll( 'a:not([href]):not([tabindex])' ); 313 if ( fakeButtons.length !== 0 ) { 314 fakeButtons.forEach( (el) => { 315 el.setAttribute( 'tabindex', '0' ); 316 el.classList.add('wpa-focusable'); 317 }); 318 if ( fakeButtons.length > 0 && ( wpa.errors || wpa.tracking ) ) { 319 errors.push( ['button-add-tabindex', fakeButtons.length] ); 320 console.log( fakeButtons.length + ' tabindex attributes added to divs with the button role by WP Accessibility' ); 321 } 322 } 323 if ( buttonLinks.length !== 0 ) { 324 buttonLinks.forEach( (el) => { 325 el.setAttribute( 'tabindex', '0' ); 326 el.classList.add('wpa-focusable'); 327 }); 328 if ( buttonLinks.length > 0 && ( wpa.errors || wpa.tracking ) ) { 329 errors.push( ['link-add-tabindex', buttonLinks.length] ); 330 console.log( buttonLinks.length + ' tabindex attributes added to anchor elements with the button role and no href value by WP Accessibility' ); 331 } 332 } 333 if ( fakeLinks.length !== 0 ) { 334 fakeLinks.forEach( (el) => { 335 el.setAttribute( 'tabindex', '0' ); 336 el.classList.add('wpa-focusable'); 337 }); 338 if ( fakeLinks.length > 0 && ( wpa.errors || wpa.tracking ) ) { 339 errors.push( ['fakelink-add-tabindex', fakeLinks.length] ); 340 console.log( fakeLinks.length + ' tabindex attributes added to elements with the link role not using the a element by WP Accessibility' ); 341 } 342 } 343 if ( linkLinks.length !== 0 ) { 344 linkLinks.forEach( (el) => { 345 el.setAttribute( 'tabindex', '0' ); 346 el.classList.add('wpa-focusable'); 347 }); 348 if ( linkLinks.length > 0 && ( wpa.errors || wpa.tracking ) ) { 349 errors.push( ['links-add-tabindex', linkLinks.length] ); 350 console.log( linkLinks.length + ' tabindex attributes added to a with no href attribute by WP Accessibility' ); 351 } 352 } 353 } 354 355 // If stats are disabled, skip this. 356 if ( 'disabled' !== wpa.url ) { 357 /* 358 * fingerprintJS 0.1 - Fast browser fingerprint library 359 * https://github.com/Valve/fingerprintJS 360 * Copyright (c) 2013 Valentin Vasilyev ([email protected]) 361 * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. 362 * Modified by Joe Dolson 2023. 363 */ 364 class Fingerprint { 365 constructor(hasher) { 366 var nativeForEach = Array.prototype.forEach; 367 var nativeMap = Array.prototype.map; 368 this.each = function (obj, iterator, context) { 369 if (obj == null) return; 370 if (nativeForEach && obj.forEach === nativeForEach) { 371 obj.forEach(iterator, context); 372 } else if (obj.length === +obj.length) { 373 for (var i = 0, l = obj.length; i < l; i++) { 374 if (JSON.stringify(iterator.call(context, obj[i], i, obj)) === '{}') return; 375 } 376 } else { 377 for (var key in obj) { 378 if (obj.hasOwnProperty(key)) { 379 if (JSON.stringify(iterator.call(context, obj[key], key, obj)) === '{}') return; 380 } 381 } 382 } 383 }; 384 this.map = function (obj, iterator, context) { 385 var results = []; 386 if (obj == null) return results; 387 if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); 388 this.each(obj, function (value, index, list) { 389 results[results.length] = iterator.call(context, value, index, list); 390 });
391 return results; 392 }; 393 394 if (hasher) { 395 this.hasher = hasher; 396 } 397 } 398 get() { 399 let keys = []; 400 keys.push(navigator.userAgent); 401 keys.push([screen.height, screen.width, screen.colorDepth].join('x')); 402 keys.push(new Date().getTimezoneOffset()); 403 keys.push(!!window.sessionStorage); 404 keys.push(!!window.localStorage); 405 var pluginsString = this.map(navigator.plugins, function (p) { 406 var mimeTypes = this.map(p, function (mt) { 407 return [mt.type, mt.suffixes].join('~'); 408 }).join(','); 409 return [p.name, p.description, mimeTypes].join('::'); 410 }, this).join(';'); 411 keys.push(pluginsString); 412 if (this.hasher) { 413 return this.hasher(keys.join('###'), 31); 414 } else { 415 return this.murmurhash3_32_gc(keys.join('###'), 31); 416 } 417 } 418 /** 419 * JS Implementation of MurmurHash3 (r136) (as of May 20, 2011) 420 * 421 * @author <a href="mailto:[email protected]">Gary Court</a> 422 * @see http://github.com/garycourt/murmurhash-js 423 * @author <a href="mailto:[email protected]">Austin Appleby</a> 424 * @see http://sites.google.com/site/murmurhash/ 425 * 426 * @param {string} key ASCII only 427 * @param {number} seed Positive integer only 428 * @return {number} 32-bit positive integer hash 429 */ 430 murmurhash3_32_gc(key, seed) { 431 let remainder, bytes, h1, h1b, c1, c2, k1, i; 432 433 remainder = key.length & 3; // key.length % 4 434 bytes = key.length - remainder; 435 h1 = seed; 436 c1 = 0xcc9e2d51; 437 c2 = 0x1b873593; 438 i = 0; 439 440 while (i < bytes) { 441 k1 = 442 ((key.charCodeAt(i) & 0xff)) | 443 ((key.charCodeAt(++i) & 0xff) << 8) | 444 ((key.charCodeAt(++i) & 0xff) << 16) | 445 ((key.charCodeAt(++i) & 0xff) << 24); 446 ++i; 447 448 k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff; 449 k1 = (k1 << 15) | (k1 >>> 17); 450 k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff; 451 452 h1 ^= k1; 453 h1 = (h1 << 13) | (h1 >>> 19); 454 h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff; 455 h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16)); 456 } 457 458 k1 = 0; 459 460 switch (remainder) { 461 case 3: k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16; 462 case 2: k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8; 463 case 1: k1 ^= (key.charCodeAt(i) & 0xff); 464 465 k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff; 466 k1 = (k1 << 15) | (k1 >>> 17); 467 k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff; 468 h1 ^= k1; 469 } 470 471 h1 ^= key.length; 472 473 h1 ^= h1 >>> 16; 474 h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff; 475 h1 ^= h1 >>> 13; 476 h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff; 477 h1 ^= h1 >>> 16; 478 479 return h1 >>> 0; 480 } 481 } 482 483 let fingerprint = new Fingerprint().get(); 484 485 function logStats(event,fingerprint,type='event') { 486 event = JSON.stringify( event ); 487 const data = new FormData(); 488 data.append( 'action', wpa.action ); 489 data.append( 'security', wpa.security ); 490 data.append( 'stats', event ); 491 data.append( 'post_id', wpa.post_id ); 492 data.append( 'title', fingerprint ); 493 data.append( 'type', type ); 494 fetch( wpa.ajaxurl, { 495 method: 'POST', 496 body: data 497 }); 498 } 499 let contrastButton = document.querySelector( '.toggle-contrast' ); 500 if ( contrastButton ) { 501 contrastButton.addEventListener('click', function () { 502 let event; 503 // This fires after the contrast change happens, and the ID is already changed. 504 if ( this.getAttribute( 'data-id' ) == "is_normal_contrast") { 505 // high contrast turned on. 506 event = {'contrast' : 'disabled'}; 507 } else { 508 // high contrast turned off. 509 event = {'contrast' : 'enabled'}; 510 } 511 logStats(event,fingerprint); 512 }); 513 } 514 515 let fontsizeButton = document.querySelector( '.toggle-fontsize' ); 516 if ( fontsizeButton ) { 517 fontsizeButton.addEventListener('click', function () { 518 let event; 519 // This fires after the fontsize change happens, and the ID is already changed. 520 if ( this.getAttribute( 'data-id' ) == "is_normal_fontsize") { 521 // fontsizes turned on. 522 event = {'fontsize' : 'disabled'}; 523 } else { 524 // fontsizes turned off. 525 event = {'fontsize' : 'enabled'}; 526 } 527 logStats(event,fingerprint); 528 }); 529 } 530 531 wpaWaitForElement('.wpa-ld button').then((el) => { 532 el.addEventListener( 'click', function(e) { 533 // For descriptions, we aren't concerned about state changes; just usage. 534 let visible = ( 'true' === el.getAttribute( 'aria-expanded' ) ) ? true : false; 535 if ( visible ) { 536 let img = el.closest( 'div' ); 537 let image_id = img.getAttribute( 'class' ).replace( 'wpa-ld wp-image-', '' ); 538 let event = { 'longdesc' : image_id }; 539 logStats(event,fingerprint); 540 } 541 }); 542 }); 543 544 let altButtons = document.querySelectorAll( '.wpa-alt button' ); 545 if ( altButtons.length > 0 ) { 546 altButtons.forEach( (el) => { 547 el.addEventListener( 'click', function(e) { 548 // For alt text, we aren't concerned about state changes; just usage. 549 let visible = ( 'true' === el.getAttribute( 'aria-expanded' ) ) ? true : false; 550 if ( visible ) { 551 let img = el.closest( 'div' ); 552 let image_id = img.getAttribute( 'class' ).replace( 'wpa-alt wp-image-', '' ); 553 let event = { 'alttext' : image_id }; 554 logStats(event,fingerprint); 555 } 556 }); 557 }); 558 } 559 if ( wpa.tracking && errors.length >= 1 ) { 560 logStats(errors,wpa.url,'view'); 561 } 562 563 } 564 if ( wpa.underline.enabled ) { 565 // Underline any link not inside a `nav` region. Using JS for this avoids problems with cascade precedence. 566 let targetEls = document.querySelectorAll( wpa.underline.target + ':not(nav ' + wpa.underline.target + '):not(#wpadminbar a), .wpa-focusable[role=link]' ); 567 if ( targetEls.length > 0 ) {
568 targetEls.forEach( (el) => { 569 let originalOutline = el.style.outlineWidth; 570 let originalOffset = el.style.outlineOffset; 571 let textColor = el.style.color; 572 let originalColor = el.style.outlineColor; 573 el.style.textDecoration = 'underline'; 574 el.addEventListener( 'mouseenter', function() { 575 this.style.textDecoration = 'none'; 576 }); 577 el.addEventListener( 'mouseleave', function() { 578 this.style.textDecoration = 'underline'; 579 }); 580 el.addEventListener( 'focusin', function() { 581 let newOutline = '2px'; 582 if ( originalOutline == '2px' ) { 583 newOutline = '4px'; 584 } 585 // Ensure there's a visible change of appearance on hover or focus. 586 this.style.outlineWidth = newOutline; 587 this.style.outlineColor = textColor; 588 this.style.outlineOffset = '2px'; 589 }); 590 el.addEventListener( 'focusout', function() { 591 // Reset visible appearance on exit. 592 this.style.outlineWidth = originalOutline; 593 this.style.outlineColor = originalColor; 594 this.style.outlineOffset = originalOffset; 595 }); 596 }); 597 } 598 } 599 600 if ( wpa.videos ) { 601 // Add a pause/play button to autoplaying videos without controls. 602 let motionQuery = matchMedia( '(prefers-reduced-motion)' ); 603 let initialState = 'false'; 604 let autoplayVideos = document.querySelectorAll( 'video[autoplay]:not([controls])' ); 605 if ( autoplayVideos.length > 0 ) { 606 autoplayVideos.forEach( (el) => { 607 if ( motionQuery.matches ) { 608 el.pause(); 609 initialState = 'true'; 610 } 611 let parentEl = el.parentElement; 612 let pauseButton = document.createElement( 'button' ); 613 let buttonIcon = document.createElement( 'span' ); 614 let buttonText = document.createElement( 'span' ); 615 pauseButton.setAttribute( 'type', 'button' ); 616 pauseButton.classList.add( 'wpa-video' ); 617 pauseButton.setAttribute( 'aria-pressed', initialState ); 618 buttonIcon.classList.add( 'dashicons-controls-pause', 'dashicons' ); 619 buttonIcon.setAttribute( 'aria-hidden', 'true' ); 620 buttonText.classList.add( 'screen-reader-text' ); 621 buttonText.innerText = wpa.pause; 622 pauseButton.append( buttonIcon, buttonText ); 623 parentEl.append( pauseButton ); 624 pauseButton.addEventListener( 'click', function() { 625 let pressed = this.getAttribute( 'aria-pressed' ); 626 console.log( pressed ); 627 if ( 'true' === pressed ) { 628 el.play(); 629 this.setAttribute( 'aria-pressed', 'false' ); 630 } else { 631 el.pause(); 632 this.setAttribute( 'aria-pressed', 'true' ); 633 } 634 }); 635 }); 636 } 637 } 638 639 if ( wpa.alt ) { 640 let selector = ( wpa.altSelector ) ? wpa.altSelector : '.hentry img[alt]:not([alt=""]), .comment-content img[alt]:not([alt=""]), #content img[alt]:not([alt=""]),.entry-content img[alt]:not([alt=""])'; 641 let collection = document.querySelectorAll( selector ); 642 let wrap = (elem, wrapper) => { 643 elem.parentElement.insertBefore(wrapper, elem); 644 wrapper.appendChild(elem); 645 }; 646 collection.forEach((el) => { 647 let img = el; 648 let closestLink = img.closest( 'a' ); 649 let closestButton = img.closest( 'button' ); 650 let inLink = ( closestLink ) ? true : false; 651 let inButton = ( closestButton ) ? true : false; 652 let width = img.width; 653 let height = img.height; 654 if ( width || height ) { 655 width = ( ! width ) ? 56 : width; // Enough width for button to be visible. 656 height = ( ! height ) ? 56 : height; // Enough height for button & first line to be visible. 657 let area = height * width; 658 if ( area < ( 150 * 300 ) ) { 659 // Small images won't get displayed alt text containers. 660 return; 661 } 662 if ( el.classList.contains( 'wpa-skip' ) || el.closest( '.wpa-skip' ) ) { 663 // Skip images that have been marked to skip. 664 return; 665 } 666 } 667 let alt = img.getAttribute('alt'); 668 669 let classes = [...img.classList]; 670 img.setAttribute('class', ''); 671 let elContainer = document.createElement( 'div' ); 672 elContainer.classList.add( 'wpa-alt' ); 673 if ( inLink || inButton ) { 674 let wrapper = ( inLink ) ? closestLink : closestButton; 675 wrap( wrapper, elContainer ); 676 } else { 677 wrap( img, elContainer ); 678 }
679 classes.forEach(className => { 680 elContainer.classList.add(className); 681 }); 682 683 elContainer.insertAdjacentHTML('afterbegin', '<button aria-expanded="false" type="button" class="wpa-toggle">alt</button>'); 684 elContainer.insertAdjacentHTML('beforeend', '<div class="wpa-alt-text"></div>'); 685 let container = elContainer.querySelector('.wpa-alt-text'); 686 container.style.display = 'none'; 687 container.innerText = alt; 688 let button = elContainer.querySelector('button'); 689 button.addEventListener( 'click', function(e) { 690 let visible = container.checkVisibility(); 691 if ( visible ) { 692 this.setAttribute( 'aria-expanded', 'false' ); 693 container.style.display = 'none'; 694 } else { 695 this.setAttribute( 'aria-expanded', 'true' ); 696 container.style.display = 'block'; 697 } 698 }); 699 }); 700 } 701 702 if ( wpa.ldType ) { 703 let wrap = (elem, wrapper) => { 704 elem.parentElement.insertBefore(wrapper, elem); 705 wrapper.appendChild(elem); 706 }; 707 708 let longDescImgs = document.querySelectorAll( 'img[longdesc]' ); 709 if ( 'link' === wpa.ldType ) { 710 // Links with an actual longdesc attribute. 711 if ( longDescImgs.length > 0 ) { 712 longDescImgs.forEach( (el) => { 713 let wrapper = document.createElement( 'div' ); 714 wrapper.classList.add( 'wpa-ld' ); 715 let longdesc = el.getAttribute('longdesc'); 716 let alt = el.getAttribute('alt'); 717 let classes = [...el.classList]; 718 wrap(el,wrapper); 719 classes.forEach(className => { 720 wrapper.classList.add(className); 721 }); 722 el.setAttribute('class', ''); 723 let newLink = document.createElement( 'a' ); 724 newLink.setAttribute( 'href', longdesc ); 725 newLink.classList.add( 'longdesc-link' ); 726 let span = document.createElement( 'span' ); 727 span.classList.add( 'screen-reader-text' ); 728 span.textContent = ' of ' + alt; 729 newLink.textContent = 'Description'; 730 newLink.appendChild( span ); 731 el.insertAdjacentElement( 'afterend', newLink ); 732 }); 733 } 734 // Links with the block style. 735 let hasStyleLongdesc = document.querySelectorAll( 'figure.is-style-longdesc' ); 736 if ( hasStyleLongdesc.length > 0 ) { 737 hasStyleLongdesc.forEach( (el) => { 738 let img = el.querySelector( 'img' ); 739 wpa_load_image_control( img ); 740 }); 741 } 742 743 } else { 744 // Handle longdescriptions with buttons. 745 if ( longDescImgs.length > 0 ) { 746 longDescImgs.forEach( (img) => { 747 wpa_load_image_control( img ); 748 }); 749 } 750 751 // Links with the block style. 752 let hasStyleLongdesc = document.querySelectorAll( 'figure.is-style-longdesc' ); 753 if ( hasStyleLongdesc.length > 0 ) { 754 hasStyleLongdesc.forEach( (el) => { 755 let img = el.querySelector( 'img' ); 756 wpa_load_image_control( img ); 757 }); 758 } 759 } 760 761 function wpa_load_image_control( img ) { 762 let classes = img.getAttribute( 'class' ); 763 let idAtt = img.getAttribute( 'id' ); 764 let id; 765 if ( idAtt && idAtt.includes( 'longdesc' ) ) { 766 id = idAtt.replace( 'longdesc-return-', '' ); 767 } else { 768 if ( null === classes || '' === classes ) { 769 parent = img.closest( '.wpa-alt' ); 770 classes = parent.getAttribute( 'class' ).replace( 'wpa-alt ', '' ); 771 } 772 773 id = classes.replace( 'wp-image-', '' ); 774 } 775 let api = wpa.restUrl + '/' + id; 776 fetch( api ) 777 .then( response => response.json()) 778 .then(data => { 779 let rawdesc = data.description.rendered; 780 rawdesc = rawdesc.replace(/(<([^>]+)>)/gi, '').trim(); 781 if ( '' !== rawdesc ) { 782 let url = new URL( wpa.ldHome ); 783 url.searchParams.set( 'longdesc', id ); 784 url.searchParams.set( 'p', id ); 785 url.toString(); 786 wpa_draw_longdesc( img, id, url, rawdesc ); 787 } 788 }) 789 .catch( error => { 790 console.log( error ); 791 }); 792 } 793 794 function wpa_draw_longdesc( img, image_id, longdesc, rawdesc ) { 795 let wrapper = document.createElement( 'div' ); 796 wrapper.classList.add( 'wpa-ld' ); 797 let alt = img.getAttribute('alt'); 798 if ( '' === alt ) { 799 alt = '<code>' + img.getAttribute('src').replace( wpa.ldHome, '' ) + '</code>'; 800 } 801 let post_classes = document.body.className.split(/\s+/); 802 let post_id = ''; 803
804 post_classes.forEach( (value) => { 805 if ( value.match( /postid-/gi ) ) { 806 post_id = value.replace( 'postid-', '', value ); 807 } 808 if ( value.match( /page-id-/gi ) ) { 809 post_id = value.replace( 'page-id-', '', value ); 810 } 811 }); 812 let url = new URL(longdesc); 813 url.searchParams.set( 'referrer', post_id ); 814 url.toString(); 815 let classes = [...img.classList]; 816 classes.forEach( (c) => { 817 wrapper.classList.add(c); 818 }); 819 wrap( img, wrapper ); 820 821 img.setAttribute('alt', ''); 822 img.setAttribute('class', ''); 823 let newLink = document.createElement( 'a' ); 824 newLink.setAttribute( 'href', url ); 825 newLink.classList.add( 'longdesc-link' ); 826 let span = document.createElement( 'span' ); 827 span.classList.add( 'screen-reader-text' ); 828 span.textContent = ' of ' + alt; 829 newLink.textContent = 'Description'; 830 newLink.appendChild( span ); 831 if ( 'link' === wpa.ldType ) { 832 wrapper.insertAdjacentElement( 'beforeend', newLink ); 833 } else { 834 wrapper.insertAdjacentHTML( 'beforeend', '<button aria-expanded="false" class="wpa-toggle">' + wpa.ldText + '</button>'); 835 wrapper.insertAdjacentHTML( 'beforeend', '<div class="longdesc"></div>'); 836 let container = wrapper.querySelector('.longdesc'); 837 container.style.display = 'none'; 838 839 fetch( longdesc ) 840 .then( response => { 841 if ( response.ok ) { 842 return response.text(); 843 } else { 844 container.insertAdjacentHTML( 'beforeend', rawdesc ); 845 } 846 }).then (html => { 847 const parser = new DOMParser(); 848 const doc = parser.parseFromString( html, "text/html" ); 849 container.insertAdjacentElement( 'beforeend', doc.querySelector( '#desc_' + image_id ) ); 850 }); 851 852 wrapper.querySelector('button').addEventListener( 'click', function(e) { 853 let visible = container.checkVisibility(); 854 if ( visible ) { 855 this.setAttribute( 'aria-expanded', 'false' ); 856 container.style.display = 'none'; 857 } else { 858 this.setAttribute( 'aria-expanded', 'true' ); 859 container.style.display = 'block'; 860 } 861 }); 862 } 863 } 864 } 865 866 // Listen for the font size change event. 867 document.addEventListener( 'wpa_fontsize_changed', function(e) { 868 fontSizeModify( e.detail.change, e.detail.factor ); 869 } ); 870 /** 871 * Modifies the font size of text nodes within the document body. 872 * 873 * @param {string} change increase or reset. 874 * @param {float} factor The factor by which to increase the font size. 875 */ 876 function fontSizeModify( change = 'increase', factor = 1.6 ) { 877 const adminBar = document.getElementById( 'wpadminbar' ); 878 const toolBar = document.querySelector( '.a11y-toolbar' ); 879 880 const ancestorResized = (el) => { 881 while (el) { 882 // Don't resize text inside other already resized elements or in the adminbar. 883 if ( el.hasAttribute( 'data-wpa-resized' ) ) { 884 return true; 885 } 886 el = el.parentElement; // Moves up to the next parent 887 } 888 return false; 889 }; 890 891 const resizeElement = (el) => { 892 // Extract the computed, rendered font-size.
893 const computedStyle = window.getComputedStyle( el ); 894 const fontSize = computedStyle.getPropertyValue( 'font-size' ).replace( 'px', '' ); 895 const cssString = 'font-size: ' + ( parseFloat(fontSize) * factor ) + 'px !important'; 896 if ( ! ancestorResized( el ) && 'increase' === change ) { 897 let inlineFontSize = el.style.fontSize; 898 // if element has a font-size set inline, store it in the data-wpa-resized attribute. 899 if ( inlineFontSize ) { 900 el.setAttribute( 'data-wpa-resized', inlineFontSize ); 901 } else { 902 el.setAttribute( 'data-wpa-resized', 'true' ); 903 } 904 el.style.cssText += cssString; 905 } 906 if ( change === 'reset' ) { 907 el.style.removeProperty( 'font-size' ); 908 // Restore the inline font-size if it was stored by the script. 909 if ( el.hasAttribute( 'data-wpa-resized' ) && el.getAttribute( 'data-wpa-resized' ) !== 'true' ) { 910 el.style.fontSize = el.getAttribute( 'data-wpa-resized' ); 911 } 912 el.removeAttribute( 'data-wpa-resized' ); 913 } 914 }; 915 916 const walker = document.createTreeWalker( 917 document.body, 918 NodeFilter.SHOW_TEXT, 919 { 920 acceptNode: function(node) { 921 // Ignore scripts, styles, nodes with only whitespace, and the adminbar. 922 const parentTag = node.parentElement ? node.parentElement.tagName : ''; 923 if (['SCRIPT', 'STYLE', 'NOSCRIPT', 'SVG'].includes( parentTag ) || ( adminBar && adminBar.contains(node) ) || ( toolBar && toolBar.contains(node) ) ) { 924 return NodeFilter.FILTER_REJECT; 925 } 926 return node.textContent.trim().length > 0 || node.nodeType === Node.ELEMENT_NODE ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; 927 } 928 } 929 ); 930 931 const seenElements = new Set(); 932 // 2. Iterate through the text nodes 933 while ( walker.nextNode() ) { 934 const textNode = walker.currentNode; 935 const parentElement = textNode.parentElement; 936 937 // Prevent checking the same element multiple times 938 if ( parentElement && ! seenElements.has( parentElement ) ) { 939 seenElements.add( parentElement ); 940 resizeElement( parentElement ); 941 } 942 } 943 944 // Form controls (e.g. input) may have no text node children, so they need to be handled separately. 945 const formElements = document.body.querySelectorAll( 'input, select, textarea' ); 946 formElements.forEach( (el) => { 947 if ( ( adminBar && adminBar.contains( el ) ) || ( toolBar && toolBar.contains( el ) ) || seenElements.has( el ) ) { 948 return; 949 } 950 seenElements.add( el ); 951 resizeElement( el ); 952 }); 953 } 954})(); 955 956/** 957 * Check whether an element contains text, including inspecting contained content for image alt attributes or aria-label attributes. 958 * 959 * @arg el DOM element to check. 960 * 961 * Based on work by Roger Johansson https://www.456bereastreet.com/archive/201105/get_element_text_including_alt_text_for_images_with_javascript/ 962 */ 963function wpaElementText(el) { 964 let text = ''; 965 // Text node (3) or CDATA node (4) - return its text 966 if ( (el.nodeType === 3) || (el.nodeType === 4) ) { 967 text = el.nodeValue; 968 // If node is an element (1) and an img, input[type=image], or area element, return its alt text 969 } else if ( (el.nodeType === 1) && ( 970 (el.tagName.toLowerCase() == 'img') || 971 (el.tagName.toLowerCase() == 'area') || 972 ((el.tagName.toLowerCase() == 'input') && el.getAttribute('type') && (el.getAttribute('type').toLowerCase() == 'image')) 973 ) ) { 974 text = el.getAttribute('alt') || ''; 975 // Traverse children unless this is a script or style element 976 } else if ( (el.nodeType === 1) && !el.tagName.match(/^(script|style)$/i) ) { 977 let children = el.childNodes; 978 for (let i = 0, l = children.length; i < l; i++) { 979 // If an element has an aria-label, that will override any other contained text. 980 let ariaLabel = el.getAttribute( 'aria-label' ); 981 text += ( ariaLabel ) ? ariaLabel : wpaElementText( children[i] ) + ' '; 982 } 983 } 984 985 return text; 986}; 987 988/** 989 * Wait to see whether an element becomes available. 990 * 991 * @param {string} selector 992 * @returns 993 */ 994function wpaWaitForElement(selector) { 995 return new Promise(resolve => { 996 if (document.querySelector(selector)) { 997 return resolve(document.querySelector(selector)); 998 } 999 1000 const observer = new MutationObserver(mutations => { 1001 if (document.querySelector(selector)) { 1002 observer.disconnect(); 1003 resolve(document.querySelector(selector)); 1004 } 1005 }); 1006 1007 observer.observe(document.body, { 1008 childList: true, 1009 subtree: true 1010 }); 1011 });
vendor: 1 bytes, line 1012
1012}
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.