1(()=>{const e=document.querySelectorAll(".coblocks-animate");if("IntersectionObserver"in window){const t=new IntersectionObserver((e=>{e.forEach((e=>{e.isIntersecting&&(e.target.classList.add(e.target.dataset.coblocksAnimation),t.unobserve(e.target))}))}),{threshold:[.15]});e.forEach((e=>{t.observe(e)}))}else e.forEach((e=>{e.classList.remove("coblocks-animate"),delete e.dataset.coblocksAnimation}))})();; 2/* global subscriptionData */ 3document.addEventListener( 'DOMContentLoaded', function () { 4 const modal = document.getElementsByClassName( 'jetpack-subscription-modal' )[ 0 ]; 5 6 if ( ! modal ) { 7 return; 8 } 9 10 const close = document.getElementsByClassName( 'jetpack-subscription-modal__close' )[ 0 ]; 11 12 let redirectUrl = ''; 13 let hasLoaded = false; 14 15 function reloadOnCloseSubscriptionModal( customUrl ) { 16 const destinationUrl = customUrl ? new URL( customUrl ) : new URL( redirectUrl ); 17 18 // Prevent redirect to external sites. 19 if ( destinationUrl.hostname !== window.location.hostname ) { 20 return; 21 } 22 23 try { 24 localStorage.setItem( 25 'jetpack-subscription-modal-on-comment-scroll-to', 26 destinationUrl.hash 27 ); 28 } catch { 29 // Ok if we can't set it. 30 } 31 32 // Add cache-busting parameter 33 destinationUrl.searchParams.set( '_ctn', Date.now() ); 34 window.location.href = destinationUrl.toString(); 35 } 36 37 function JetpackSubscriptionModalOnCommentMessageListener( event ) { 38 let message = event && event.data; 39 if ( typeof message === 'string' ) { 40 try { 41 message = JSON.parse( message ); 42 } catch { 43 return; 44 } 45 } 46 47 const type = message && message.type; 48 const data = message && message.data; 49 50 if ( type !== 'subscriptionModalShow' || typeof data.url === 'undefined' ) { 51 return; 52 } 53 54 if ( subscriptionData.homeUrl !== event.origin ) { 55 return; 56 } 57 58 if ( data.email ) { 59 const emailInput = document.querySelector( 60 '.jetpack-subscription-modal__modal-content input[type="email"]' 61 ); 62 if ( ! emailInput ) { 63 reloadOnCloseSubscriptionModal( data.url ); 64 return; 65 } 66 67 const appSource = document.querySelector( 68 '.jetpack-subscription-modal__modal-content input[name="app_source"]' 69 ); 70 if ( ! appSource ) { 71 reloadOnCloseSubscriptionModal( data.url ); 72 return; 73 } 74 75 emailInput.value = data.email; 76 if ( data.is_logged_in ) { 77 emailInput.setAttribute( 'readonly', 'readonly' ); 78 appSource.value = 'atomic-subscription-modal-li'; 79 } 80 } 81 82 if ( ! hasLoaded ) { 83 try { 84 const storedCount = parseInt( 85 sessionStorage.getItem( 'jetpack-subscription-modal-shown-count' ) 86 ); 87 const showCount = ( isNaN( storedCount ) ? 0 : storedCount ) + 1; 88 sessionStorage.setItem( 'jetpack-subscription-modal-shown-count', showCount ); 89 90 if ( showCount > 5 ) { 91 new Image().src = 92 document.location.protocol + 93 '//pixel.wp.com/g.gif?v=wpcom-no-pv&x_jetpack-subscribe-modal-comm=hidden_views_limit&r=' + 94 Math.random(); 95 96 reloadOnCloseSubscriptionModal( data.url ); 97 return; 98 } 99 } catch { 100 // Ignore any errors. 101 } 102 103 new Image().src = 104 document.location.protocol + 105 '//pixel.wp.com/g.gif?v=wpcom-no-pv&x_jetpack-subscribe-modal-comm=showed&r=' + 106 Math.random(); 107 108 modal.classList.toggle( 'open' ); 109 hasLoaded = true; 110 redirectUrl = data.url; 111 } 112 } 113 114 window.addEventListener( 'message', JetpackSubscriptionModalOnCommentMessageListener ); 115 116 if ( close ) { 117 close.onclick = function ( event ) { 118 event.preventDefault(); 119 modal.classList.toggle( 'open' ); 120 reloadOnCloseSubscriptionModal(); 121 }; 122 } 123 124 window.onclick = function ( event ) { 125 if ( event.target === modal ) { 126 modal.style.display = 'none'; 127 reloadOnCloseSubscriptionModal(); 128 } 129 }; 130 131 window.addEventListener( 'load', () => { 132 // Scroll to the last comment. 133 const subscriptionScroll = localStorage.getItem( 134 'jetpack-subscription-modal-on-comment-scroll-to' 135 ); 136 137 if ( subscriptionScroll ) { 138 window.location.hash = subscriptionScroll; 139 localStorage.removeItem( 'jetpack-subscription-modal-on-comment-scroll-to' ); 140 141 const comment = document.querySelector( subscriptionScroll ); 142 if ( comment ) { 143 comment.scrollIntoView( { block: 'center', behavior: 'smooth' } ); 144 } 145 } 146 } ); 147} ); 148; 149/* global wpcom_reblog */ 150 151var jetpackLikesWidgetBatch = []; 152var jetpackLikesMasterReady = false;
153 154// Due to performance problems on pages with a large number of widget iframes that need to be loaded, 155// we are limiting the processing at any instant to unloaded widgets that are currently in viewport, 156// plus this constant that will allow processing of widgets above and bellow the current fold. 157// This aim of it is to improve the UX and hide the transition from unloaded to loaded state from users. 158var jetpackLikesLookAhead = 2000; // pixels 159 160// Keeps track of loaded comment likes widget so we can unload them when they are scrolled out of view. 161var jetpackCommentLikesLoadedWidgets = []; 162 163var jetpackLikesDocReadyPromise = new Promise( resolve => { 164 if ( document.readyState !== 'loading' ) { 165 resolve(); 166 } else { 167 window.addEventListener( 'DOMContentLoaded', () => resolve() ); 168 } 169} ); 170 171function JetpackLikesPostMessage( message, target ) { 172 if ( typeof message === 'string' ) { 173 try { 174 message = JSON.parse( message ); 175 } catch { 176 return; 177 } 178 } 179 180 if ( target && typeof target.postMessage === 'function' ) { 181 try { 182 target.postMessage( 183 JSON.stringify( { 184 type: 'likesMessage', 185 data: message, 186 } ), 187 '*' 188 ); 189 } catch { 190 // Ignore error 191 } 192 } 193} 194 195function JetpackLikesBatchHandler() { 196 const requests = []; 197 document.querySelectorAll( 'div.jetpack-likes-widget-unloaded' ).forEach( widget => { 198 if ( jetpackLikesWidgetBatch.indexOf( widget.id ) > -1 ) { 199 return; 200 } 201 202 if ( ! jetpackIsScrolledIntoView( widget ) ) { 203 return; 204 } 205 206 jetpackLikesWidgetBatch.push( widget.id ); 207 208 var regex = /like-(post|comment)-wrapper-(\d+)-(\d+)-(\w+)/, 209 match = regex.exec( widget.id ), 210 info; 211 212 if ( ! match || match.length !== 5 ) { 213 return; 214 } 215 216 info = { 217 blog_id: match[ 2 ], 218 width: widget.width, 219 }; 220 221 if ( 'post' === match[ 1 ] ) { 222 info.post_id = match[ 3 ]; 223 } else if ( 'comment' === match[ 1 ] ) { 224 info.comment_id = match[ 3 ]; 225 } 226 227 info.obj_id = match[ 4 ]; 228 229 requests.push( info ); 230 } ); 231 232 if ( requests.length > 0 ) { 233 JetpackLikesPostMessage( 234 { event: 'initialBatch', requests: requests }, 235 window.frames[ 'likes-master' ] 236 ); 237 } 238} 239 240function JetpackLikesMessageListener( event ) { 241 let message = event && event.data; 242 if ( typeof message === 'string' ) { 243 try { 244 message = JSON.parse( message ); 245 } catch { 246 return; 247 } 248 } 249 250 const type = message && message.type; 251 const data = message && message.data; 252 253 if ( type !== 'likesMessage' || typeof data.event === 'undefined' ) { 254 return; 255 } 256 257 // We only allow messages from one origin 258 const allowedOrigin = 'https://widgets.wp.com'; 259 if ( allowedOrigin !== event.origin ) { 260 return; 261 } 262 263 switch ( data.event ) { 264 case 'masterReady': 265 jetpackLikesDocReadyPromise.then( () => { 266 jetpackLikesMasterReady = true; 267 268 const stylesData = { 269 event: 'injectStyles', 270 }; 271 const sdTextColor = document.querySelector( '.sd-text-color' ); 272 const sdLinkColor = document.querySelector( '.sd-link-color' ); 273 const sdTextColorStyles = ( sdTextColor && getComputedStyle( sdTextColor ) ) || {}; 274 const sdLinkColorStyles = ( sdLinkColor && getComputedStyle( sdLinkColor ) ) || {}; 275 276 // enable reblogs if we're on a single post page 277 if ( document.body.classList.contains( 'single' ) ) { 278 JetpackLikesPostMessage( { event: 'reblogsEnabled' }, window.frames[ 'likes-master' ] ); 279 } 280 281 stylesData.textStyles = { 282 color: sdTextColorStyles.color,
283 fontFamily: sdTextColorStyles[ 'font-family' ], 284 fontSize: sdTextColorStyles[ 'font-size' ], 285 direction: sdTextColorStyles.direction, 286 fontWeight: sdTextColorStyles[ 'font-weight' ], 287 fontStyle: sdTextColorStyles[ 'font-style' ], 288 textDecoration: sdTextColorStyles[ 'text-decoration' ], 289 }; 290 291 stylesData.linkStyles = { 292 color: sdLinkColorStyles.color, 293 fontFamily: sdLinkColorStyles[ 'font-family' ], 294 fontSize: sdLinkColorStyles[ 'font-size' ], 295 textDecoration: sdLinkColorStyles[ 'text-decoration' ], 296 fontWeight: sdLinkColorStyles[ 'font-weight' ], 297 fontStyle: sdLinkColorStyles[ 'font-style' ], 298 }; 299 300 JetpackLikesPostMessage( stylesData, window.frames[ 'likes-master' ] ); 301 302 JetpackLikesBatchHandler(); 303 } ); 304 305 break; 306 307 // We're keeping this for planned future follow ups. 308 // @see: https://github.com/Automattic/jetpack/pull/42361#discussion_r1995338815 309 case 'showLikeWidget': 310 break; 311 312 // We're keeping this for planned future follow ups. 313 // @see: https://github.com/Automattic/jetpack/pull/42361#discussion_r1995338815 314 case 'showCommentLikeWidget': 315 break; 316 317 case 'killCommentLikes': 318 // If kill switch for comment likes is enabled remove all widgets wrappers and `Loading...` placeholders. 319 document 320 .querySelectorAll( '.jetpack-comment-likes-widget-wrapper' ) 321 .forEach( wrapper => wrapper.remove() ); 322 break; 323 324 case 'clickReblogFlair': 325 if ( wpcom_reblog && typeof wpcom_reblog.toggle_reblog_box_flair === 'function' ) { 326 wpcom_reblog.toggle_reblog_box_flair( data.obj_id ); 327 } 328 break; 329 330 case 'hideOtherGravatars': { 331 hideLikersPopover(); 332 break; 333 } 334 335 case 'showOtherGravatars': { 336 const container = document.querySelector( '#likes-other-gravatars' ); 337 338 if ( ! container ) { 339 break; 340 } 341 342 const list = container.querySelector( 'ul' ); 343 344 container.style.display = 'none'; 345 list.innerHTML = ''; 346 347 container 348 .querySelectorAll( '.likes-text span' ) 349 .forEach( item => ( item.textContent = data.totalLikesLabel ) ); 350 351 ( data.likers || [] ).forEach( async ( liker, index ) => { 352 if ( liker.profile_URL.substr( 0, 4 ) !== 'http' ) { 353 // We only display gravatars with http or https schema 354 return; 355 } 356 357 const element = document.createElement( 'li' ); 358 list.append( element ); 359 360 const profileLink = encodeURI( liker.profile_URL ); 361 const avatarLink = encodeURI( liker.avatar_URL ); 362 element.innerHTML = `<a href="${ profileLink }" rel="nofollow" target="_parent" class="wpl-liker"> 363 <img src="${ avatarLink }" 364 alt="" 365 style="width: 28px; height: 28px;" /> 366 <span></span> 367 </a>`; 368 369 // Add some extra attributes through native methods, to ensure strings are sanitized. 370 element.classList.add( liker.css_class ); 371 element.querySelector( 'img' ).alt = data.avatarAltTitle.replace( '%s', liker.name ); 372 element.querySelector( 'span' ).innerText = liker.name; 373 374 if ( index === data.likers.length - 1 ) { 375 element.addEventListener( 'keydown', e => { 376 if ( e.key === 'Tab' && ! e.shiftKey ) { 377 e.preventDefault(); 378 hideLikersPopover(); 379 380 JetpackLikesPostMessage( 381 { event: 'focusLikesCount', parent: data.parent }, 382 window.frames[ 'likes-master' ] 383 ); 384 } 385 } ); 386 } 387 } ); 388 389 const positionPopup = function () { 390 const containerStyle = getComputedStyle( container ); 391 const isRtl = containerStyle.direction === 'rtl'; 392 393 const el = document.querySelector( `*[name='${ data.parent }']` ); 394 const rect = el.getBoundingClientRect(); 395 const win = el.ownerDocument.defaultView; 396 const offset = { 397 top: rect.top + win.pageYOffset, 398 left: rect.left + win.pageXOffset, 399 }; 400 401 let containerLeft; 402 container.style.top = offset.top + data.position.top - 1 + 'px'; 403 404 if ( isRtl ) { 405 const visibleAvatarsCount = data && data.likers ? Math.min( data.likers.length, 5 ) : 0; 406 // 24px is the width of the avatar + 4px is the padding between avatars 407 containerLeft = offset.left + data.position.left + 24 * visibleAvatarsCount + 4; 408 container.style.transform = 'translateX(-100%)'; 409 } else { 410 containerLeft = offset.left + data.position.left; 411 } 412 container.style.left = containerLeft + 'px'; 413 414 // If the popup overflows viewport width, we should show it on the next line. 415 // Push it offscreen to calculated rendered width. 416 container.style.left = '-9999px'; 417 container.style.display = 'block'; 418 419 // If the popup exceeds the viewport width, 420 // flip the position of the popup. 421 const containerWidth = container.offsetWidth; 422 const containerRight = containerLeft + containerWidth; 423 if ( containerRight > win.innerWidth ) { 424 containerLeft = rect.right - containerWidth; 425 } 426 427 // Set the container left 428 container.style.left = containerLeft + 'px'; 429 container.setAttribute( 'aria-hidden', 'false' ); 430 }; 431 432 positionPopup(); 433 container.focus(); 434 435 const debounce = function ( func, wait ) { 436 var timeout; 437 return function () { 438 var context = this; 439 var args = arguments; 440 clearTimeout( timeout ); 441 timeout = setTimeout( function () { 442 func.apply( context, args ); 443 }, wait ); 444 }; 445 }; 446 447 const debouncedPositionPopup = debounce( positionPopup, 100 ); 448 449 // Keep a reference of this function in the element itself 450 // so that we can destroy it later 451 container.__resizeHandler = debouncedPositionPopup; 452 453 // When window is resized, resize the popup. 454 window.addEventListener( 'resize', debouncedPositionPopup ); 455 456 container.focus(); 457 } 458 } 459} 460 461window.addEventListener( 'message', JetpackLikesMessageListener ); 462 463function hideLikersPopover() { 464 const container = document.querySelector( '#likes-other-gravatars' ); 465 466 if ( container ) { 467 container.style.display = 'none'; 468 container.setAttribute( 'aria-hidden', 'true' ); 469 470 // Remove the resize event listener and cleanup. 471 const resizeHandler = container.__resizeHandler; 472 if ( resizeHandler ) { 473 window.removeEventListener( 'resize', resizeHandler ); 474 delete container.__resizeHandler; 475 } 476 } 477} 478 479document.addEventListener( 'click', hideLikersPopover ); 480 481function JetpackLikesWidgetQueueHandler() { 482 var wrapperID; 483 484 if ( ! jetpackLikesMasterReady ) { 485 // The master iframe emits `masterReady` a single time, when it finishes loading. On 486 // script-heavy pages it can finish loading â and emit â before this script attaches its 487 // message listener above, so that one event is missed and the queue never starts. Ping the 488 // master iframe while we wait so it re-emits `masterReady` once both it and our listener 489 // are ready. 490 JetpackLikesPostMessage( { event: 'queryMasterReady' }, window.frames[ 'likes-master' ] ); 491 setTimeout( JetpackLikesWidgetQueueHandler, 500 ); 492 return; 493 } 494 495 // Restore widgets to initial unloaded state when they are scrolled out of view. 496 jetpackUnloadScrolledOutWidgets(); 497 498 jetpackObserveUnloadedWidgets(); 499 500 var unloadedWidgetsInView = jetpackGetUnloadedWidgetsInView(); 501 502 if ( unloadedWidgetsInView.length > 0 ) { 503 // Grab any unloaded widgets for a batch request 504 JetpackLikesBatchHandler(); 505 } 506 507 for ( var i = 0, length = unloadedWidgetsInView.length; i <= length - 1; i++ ) { 508 wrapperID = unloadedWidgetsInView[ i ].id; 509 510 if ( ! wrapperID ) { 511 continue; 512 } 513 514 jetpackLoadLikeWidgetIframe( wrapperID ); 515 } 516} 517 518function jetpackLoadLikeWidgetIframe( wrapperID ) { 519 if ( typeof wrapperID === 'undefined' ) { 520 return; 521 } 522 523 const wrapper = document.querySelector( '#' + wrapperID ); 524 wrapper.querySelectorAll( 'iframe' ).forEach( iFrame => iFrame.remove() ); 525 526 const placeholder = wrapper.querySelector( '.likes-widget-placeholder' ); 527 528 // Post like iframe 529 if ( placeholder && placeholder.classList.contains( 'post-likes-widget-placeholder' ) ) { 530 const postLikesFrame = document.createElement( 'iframe' ); 531 532 postLikesFrame.classList.add( 'post-likes-widget', 'jetpack-likes-widget' ); 533 postLikesFrame.name = wrapper.dataset.name; 534 postLikesFrame.src = wrapper.dataset.src; 535 postLikesFrame.height = '55px'; 536 postLikesFrame.width = '100%'; 537 postLikesFrame.frameBorder = '0'; 538 postLikesFrame.scrolling = 'no'; 539 postLikesFrame.title = wrapper.dataset.title; 540 541 placeholder.after( postLikesFrame ); 542 } 543 544 // Comment like iframe 545 if ( placeholder.classList.contains( 'comment-likes-widget-placeholder' ) ) { 546 const commentLikesFrame = document.createElement( 'iframe' ); 547 548 commentLikesFrame.class = 'comment-likes-widget-frame jetpack-likes-widget-frame'; 549 commentLikesFrame.name = wrapper.dataset.name; 550 commentLikesFrame.src = wrapper.dataset.src; 551 commentLikesFrame.height = '18px'; 552 commentLikesFrame.width = '100%'; 553 commentLikesFrame.frameBorder = '0'; 554 commentLikesFrame.scrolling = 'no'; 555 556 wrapper.querySelector( '.comment-like-feedback' ).after( commentLikesFrame ); 557 558 jetpackCommentLikesLoadedWidgets.push( commentLikesFrame ); 559 } 560 561 wrapper.classList.remove( 'jetpack-likes-widget-unloaded' ); 562 wrapper.classList.add( 'jetpack-likes-widget-loading' ); 563 564 wrapper.querySelector( 'iframe' ).addEventListener( 'load', e => { 565 // A widget scrolled out of view mid-load has its iframe dropped, and a reloaded one 566 // replaces it, either of which leaves this closure holding a detached iframe. Acting on 567 // it would hide a placeholder that is now the only thing the widget has left to show. 568 if ( ! wrapper.contains( e.target ) ) { 569 return; 570 } 571 572 JetpackLikesPostMessage( 573 { event: 'loadLikeWidget', name: e.target.name, width: e.target.width }, 574 window.frames[ 'likes-master' ] 575 ); 576 577 // The stylesheet is otherwise the only thing that hides the placeholder, so one that never 578 // applies leaves "Loadingâ¦" sitting over a widget that loaded fine. 579 if ( placeholder ) { 580 placeholder.style.display = 'none'; 581 } 582 583 wrapper.classList.remove( 'jetpack-likes-widget-loading' ); 584 wrapper.classList.add( 'jetpack-likes-widget-loaded' ); 585 } ); 586} 587 588function jetpackGetUnloadedWidgetsInView() { 589 const unloadedWidgets = document.querySelectorAll( 'div.jetpack-likes-widget-unloaded' ); 590 591 return [ ...unloadedWidgets ].filter( item => jetpackIsScrolledIntoView( item ) ); 592} 593 594function jetpackIsScrolledIntoView( element ) { 595 const top = element.getBoundingClientRect().top; 596 const bottom = element.getBoundingClientRect().bottom; 597
598 // Allow some slack above and bellow the fold with jetpackLikesLookAhead, 599 // with the aim of hiding the transition from unloaded to loaded widget from users. 600 return top + jetpackLikesLookAhead >= 0 && bottom <= window.innerHeight + jetpackLikesLookAhead; 601} 602 603function jetpackUnloadScrolledOutWidgets() { 604 for ( let i = jetpackCommentLikesLoadedWidgets.length - 1; i >= 0; i-- ) { 605 const currentWidgetIframe = jetpackCommentLikesLoadedWidgets[ i ]; 606 607 if ( ! jetpackIsScrolledIntoView( currentWidgetIframe ) ) { 608 const widgetWrapper = 609 currentWidgetIframe && 610 currentWidgetIframe.parentElement && 611 currentWidgetIframe.parentElement.parentElement; 612 613 // Restore parent class to 'unloaded' so this widget can be picked up by queue manager again if needed. 614 widgetWrapper.classList.remove( 'jetpack-likes-widget-loaded' ); 615 widgetWrapper.classList.remove( 'jetpack-likes-widget-loading' ); 616 widgetWrapper.classList.add( 'jetpack-likes-widget-unloaded' ); 617 618 // An empty string removes the inline declaration rather than setting one, handing the 619 // placeholder back to the stylesheet. The `display: none` set on load would otherwise 620 // outrank it, leaving an unloaded widget with neither an iframe nor a placeholder. 621 const placeholder = widgetWrapper.querySelector( '.likes-widget-placeholder' ); 622 if ( placeholder ) { 623 placeholder.style.display = ''; 624 } 625 626 // Remove it from the list of loaded widgets. 627 jetpackCommentLikesLoadedWidgets.splice( i, 1 ); 628 629 // Remove comment like widget iFrame. 630 currentWidgetIframe.remove(); 631 } 632 } 633} 634 635var jetpackWidgetsDelayedExec = function ( after, fn ) { 636 var timer; 637 return function () { 638 clearTimeout( timer ); 639 timer = setTimeout( fn, after ); 640 }; 641}; 642 643var jetpackOnScrollStopped = jetpackWidgetsDelayedExec( 250, JetpackLikesWidgetQueueHandler ); 644 645// Scrolling is not the only thing that brings a widget into range. A stylesheet that lands late 646// reflows the page without firing a scroll event, and the queue would never look at that widget 647// again, leaving it on "Loadingâ¦" until the reader happens to scroll. 648var jetpackLikesWidgetObserver = 649 typeof IntersectionObserver === 'function' 650 ? new IntersectionObserver( jetpackOnScrollStopped, { 651 rootMargin: `${ jetpackLikesLookAhead }px`, 652 // jetpackIsScrolledIntoView() wants the widget fully inside the band, so ask to be 653 // told when it gets there - crossing into partial overlap alone would not load it. 654 threshold: [ 0, 1 ], 655 } ) 656 : null; 657 658// Observing an element twice is a no-op, so every queue pass can call this to pick up widgets 659// added after load. 660function jetpackObserveUnloadedWidgets() { 661 if ( ! jetpackLikesWidgetObserver ) { 662 return; 663 } 664 665 document 666 .querySelectorAll( 'div.jetpack-likes-widget-unloaded' ) 667 .forEach( widget => jetpackLikesWidgetObserver.observe( widget ) ); 668} 669 670// Load initial batch of widgets, prior to any scrolling events. 671JetpackLikesWidgetQueueHandler(); 672 673// Add event listener to execute queue handler after scroll. 674window.addEventListener( 'scroll', jetpackOnScrollStopped, true ); 675;
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.