1(function () { 2 'use strict'; 3 4 var __assign = (undefined && undefined.__assign) || function () { 5 __assign = Object.assign || function(t) { 6 for (var s, i = 1, n = arguments.length; i < n; i++) { 7 s = arguments[i]; 8 for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) 9 t[p] = s[p]; 10 } 11 return t; 12 }; 13 return __assign.apply(this, arguments); 14 }; 15 var defaults = { 16 lines: 12, 17 length: 7, 18 width: 5, 19 radius: 10, 20 scale: 1.0, 21 corners: 1, 22 color: '#000', 23 fadeColor: 'transparent', 24 animation: 'spinner-line-fade-default', 25 rotate: 0, 26 direction: 1, 27 speed: 1, 28 zIndex: 2e9, 29 className: 'spinner', 30 top: '50%', 31 left: '50%', 32 shadow: '0 0 1px transparent', 33 position: 'absolute', 34 }; 35 var Spinner = /** @class */ (function () { 36 function Spinner(opts) { 37 if (opts === void 0) { opts = {}; } 38 this.opts = __assign(__assign({}, defaults), opts); 39 } 40 /** 41 * Adds the spinner to the given target element. If this instance is already 42 * spinning, it is automatically removed from its previous target by calling 43 * stop() internally. 44 */ 45 Spinner.prototype.spin = function (target) { 46 this.stop(); 47 this.el = document.createElement('div'); 48 this.el.className = this.opts.className; 49 this.el.setAttribute('role', 'progressbar'); 50 css(this.el, { 51 position: this.opts.position, 52 width: 0, 53 zIndex: this.opts.zIndex, 54 left: this.opts.left, 55 top: this.opts.top, 56 transform: "scale(" + this.opts.scale + ")", 57 }); 58 if (target) { 59 target.insertBefore(this.el, target.firstChild || null); 60 } 61 drawLines(this.el, this.opts); 62 return this; 63 }; 64 /** 65 * Stops and removes the Spinner. 66 * Stopped spinners may be reused by calling spin() again. 67 */ 68 Spinner.prototype.stop = function () { 69 if (this.el) { 70 if (typeof requestAnimationFrame !== 'undefined') { 71 cancelAnimationFrame(this.animateId); 72 } 73 else { 74 clearTimeout(this.animateId); 75 } 76 if (this.el.parentNode) { 77 this.el.parentNode.removeChild(this.el); 78 } 79 this.el = undefined; 80 } 81 return this; 82 }; 83 return Spinner; 84 }()); 85 /** 86 * Sets multiple style properties at once. 87 */ 88 function css(el, props) { 89 for (var prop in props) { 90 el.style[prop] = props[prop]; 91 } 92 return el; 93 } 94 /** 95 * Returns the line color from the given string or array. 96 */ 97 function getColor(color, idx) { 98 return typeof color == 'string' ? color : color[idx % color.length]; 99 } 100 /** 101 * Internal method that draws the individual lines. 102 */ 103 function drawLines(el, opts) { 104 var borderRadius = (Math.round(opts.corners * opts.width * 500) / 1000) + 'px'; 105 var shadow = 'none'; 106 if (opts.shadow === true) { 107 shadow = '0 2px 4px #000'; // default shadow 108 } 109 else if (typeof opts.shadow === 'string') { 110 shadow = opts.shadow; 111 } 112 var shadows = parseBoxShadow(shadow); 113 for (var i = 0; i < opts.lines; i++) { 114 var degrees = ~~(360 / opts.lines * i + opts.rotate); 115 var backgroundLine = css(document.createElement('div'), { 116 position: 'absolute', 117 top: -opts.width / 2 + "px", 118 width: (opts.length + opts.width) + 'px', 119 height: opts.width + 'px', 120 background: getColor(opts.fadeColor, i), 121 borderRadius: borderRadius, 122 transformOrigin: 'left', 123 transform: "rotate(" + degrees + "deg) translateX("
123+ opts.radius + "px)", 124 }); 125 var delay = i * opts.direction / opts.lines / opts.speed; 126 delay -= 1 / opts.speed; // so initial animation state will include trail 127 var line = css(document.createElement('div'), { 128 width: '100%', 129 height: '100%', 130 background: getColor(opts.color, i), 131 borderRadius: borderRadius, 132 boxShadow: normalizeShadow(shadows, degrees), 133 animation: 1 / opts.speed + "s linear " + delay + "s infinite " + opts.animation, 134 }); 135 backgroundLine.appendChild(line); 136 el.appendChild(backgroundLine); 137 } 138 } 139 function parseBoxShadow(boxShadow) { 140 var regex = /^\s*([a-zA-Z]+\s+)?(-?\d+(\.\d+)?)([a-zA-Z]*)\s+(-?\d+(\.\d+)?)([a-zA-Z]*)(.*)$/; 141 var shadows = []; 142 for (var _i = 0, _a = boxShadow.split(','); _i < _a.length; _i++) { 143 var shadow = _a[_i]; 144 var matches = shadow.match(regex); 145 if (matches === null) { 146 continue; // invalid syntax 147 } 148 var x = +matches[2]; 149 var y = +matches[5]; 150 var xUnits = matches[4]; 151 var yUnits = matches[7]; 152 if (x === 0 && !xUnits) { 153 xUnits = yUnits; 154 } 155 if (y === 0 && !yUnits) { 156 yUnits = xUnits; 157 } 158 if (xUnits !== yUnits) { 159 continue; // units must match to use as coordinates 160 } 161 shadows.push({ 162 prefix: matches[1] || '', 163 x: x, 164 y: y, 165 xUnits: xUnits, 166 yUnits: yUnits, 167 end: matches[8], 168 }); 169 } 170 return shadows; 171 } 172 /** 173 * Modify box-shadow x/y offsets to counteract rotation 174 */ 175 function normalizeShadow(shadows, degrees) { 176 var normalized = []; 177 for (var _i = 0, shadows_1 = shadows; _i < shadows_1.length; _i++) { 178 var shadow = shadows_1[_i]; 179 var xy = convertOffset(shadow.x, shadow.y, degrees); 180 normalized.push(shadow.prefix + xy[0] + shadow.xUnits + ' ' + xy[1] + shadow.yUnits + shadow.end); 181 } 182 return normalized.join(', '); 183 } 184 function convertOffset(x, y, degrees) { 185 var radians = degrees * Math.PI / 180; 186 var sin = Math.sin(radians); 187 var cos = Math.cos(radians); 188 return [ 189 Math.round((x * cos + y * sin) * 1000) / 1000, 190 Math.round((-x * sin + y * cos) * 1000) / 1000, 191 ]; 192 } 193 194 (function(){ 195 var plugin_name = "searchio_live_search"; 196 197 function searchIOLiveSearch( element ) { 198 this.config = null; 199 // Internal properties. 200 this.input_el = element; // the input element itself 201 this.results_id = null; // the id attribute of the results wrapper for this search field 202 this.results_el = null; // the results wrapper element itself 203 this.parent_el = null; // allows results wrapper element to be injected into a custom parent element 204 this.results_showing = false; // whether the results are showing 205 this.form_el = null; // the search form element itself 206 this.timer = false; // powers the delay check 207 this.last_string = ''; // the last search string submitted 208 this.spinner = null; // the spinner 209 this.spinner_showing = false; // whether the spinner is showing 210 this.has_results = false; // whether results are showing 211 this.current_request = false; // the current request in progress 212 this.results_destroy_on_blur = true; // destroy the results 213 this.a11y_keys = [ 27, 40, 13, 38, 9 ]; // list of keyCode used for a11y 214 215 // Kick it off! 216 this.init(); 217 } 218 219 searchIOLiveSearch.prototype = { 220 221 // prep the field and form 222 init: function(){ 223 var self = this, 224 $input = this.input_el; 225 this.form_el = $input.parents('form:eq(0)'); 226 this.results_id = this.uniqid('searchio_live_search_results_'); 227 228 // establish our config (e.g. allow developers to override the config based on the value of the swpconfig data attribute)
229 var valid_config = false; 230 var config_template = $input.data('swpconfig'); 231 if(config_template && typeof config_template !== 'undefined') { 232 // loop through all available configs 233 for (var config_key in searchio_live_search_params.config) { 234 if( config_template === config_key ) { 235 valid_config = true; 236 this.config = searchio_live_search_params.config[config_key]; 237 } 238 } 239 }else { 240 // use the default 241 for (var default_key in searchio_live_search_params.config) { 242 if( 'default' === default_key ) { 243 valid_config = true; 244 this.config = searchio_live_search_params.config[default_key]; 245 } 246 } 247 } 248 249 // if there wasn't a valid config found, alert() it because everything will break 250 if(!valid_config){ 251 alert(searchio_live_search_params.msg_no_config_found); 252 }else { 253 // allow the swpengine data attribute to override the engine set in the config (prevents new configs just to change engine) 254 var engine = $input.data('swpengine'); 255 if( engine ) { 256 this.config.engine = engine; 257 } 258 259 $input.data('swpengine',this.config.engine); 260 261 // prevent autocomplete 262 $input.attr('autocomplete','off'); 263 264 // #a11y: ARIA attributes 265 $input.attr( 'aria-owns', this.results_id ); 266 $input.attr( 'aria-autocomplete', 'both' ); 267 $input.attr( 'aria-label', searchio_live_search_params.aria_instructions ); 268 269 // set up and position the results container 270 var results_el_html = '<div aria-expanded="false" class="searchio-live-search-results" id="' + this.results_id + '" role="listbox" tabindex="0"></div>'; 271 272 // if parent_el was specified, inject the results el into it instead of appending it to the body 273 var swpparentel = $input.data('swpparentel'); 274 if (swpparentel) { 275 // specified as a data property on the html input. 276 this.parent_el = jQuery(swpparentel); 277 this.parent_el.html(results_el_html); 278 } else if (this.config.parent_el) { 279 // specified by the config set in php 280 this.parent_el = jQuery(this.config.parent_el); 281 this.parent_el.html(results_el_html); 282 } else { 283 // no parent, just append to the body 284 jQuery('body').append(jQuery(results_el_html)); 285 } 286 287 this.results_el = jQuery('#'+this.results_id); 288 this.position_results(); 289 jQuery(window).resize(function(){ 290 self.position_results(); 291 }); 292 293 // prep the spinner 294 if(this.config.spinner){ 295 // Version 1.4 added some new configuration options that may not be included 296 // if the configuration was configured for an earlier version, so we need 297 // to check for these new values and re-set them if necessary 298 if(typeof this.config.spinner.scale === 'undefined'){ 299 this.config.spinner.scale = 1; 300 } 301 if(typeof this.config.spinner.fadeColor === 'undefined'){ 302 this.config.spinner.fadeColor = 'transparent'; 303 } 304 if(typeof this.config.spinner.animation === 'undefined'){ 305 this.config.spinner.animation = 'searchio-spinner-line-fade-quick'; 306 } 307 if(typeof this.config.spinner.position === 'undefined'){ 308 this.config.spinner.position = 'absolute'; 309 } 310 311 this.spinner = new Spinner(this.config.spinner); 312 } 313 314 if(typeof this.config.abort_on_enter === 'undefined'){ 315 this.config.abort_on_enter = true; 316 } 317 318 // bind to keyup 319 $input.keyup(function(e){ 320 if ( jQuery.inArray( e.keyCode, self.a11y_keys ) > -1 ) { 321 return; 322 } 323 // is there already a request active? 324 if( self.current_request && ( self.config.abort_on_enter && e.keyCode === 13 ) ){ 325 self.current_request.abort(); 326 } 327 if(!jQuery.trim(self.input_el.val()).length) { 328 self.destroy_results(); 329 } 330 // if the user typed, show the results wrapper and spinner 331 else if(!self.results_showing){ 332 self.position_results(); 333 self.results_el.addClass('searchio-live-search-results-showing'); 334 self.show_spinner(); 335 self.results_showing = true; 336 } 337 // if there are already results on display and the user is changing the search string 338 // remove the existing results and show the spinner 339 if(self.has_results && !self.spinner_showing && self.last_string !== jQuery.trim(self.input_el.val())){ 340 self.results_el.empty(); 341 self.show_spinner(); 342 } 343 344 // Capture whether minimum characters have been entered 345 if(e.currentTarget.value.length >= self.config.input.min_chars){ 346 self.results_el.removeClass('searchio-live-search-no-min-chars'); 347 } else { 348 self.results_el.addClass('searchio-live-search-no-min-chars'); 349 } 350 }).keyup(jQuery.proxy(this.maybe_search, this)); 351 352 // destroy the results when input focus is lost 353 if(this.config.results_destroy_on_blur||typeof this.config.results_destroy_on_blur === 'undefined'){ 354 jQuery('html').click(function(e){
355 // Only destroy the results if the click was placed outside the results element. 356 if (!jQuery(e.target).parents('.searchio-live-search-results').length) { 357 self.destroy_results(); 358 } 359 }); 360 } 361 $input.click(function(e){ 362 e.stopPropagation(); 363 }); 364 } 365 }, 366 367 keyboard_navigation: function(){ 368 var self = this, 369 $input = this.input_el, 370 $results = this.results_el, 371 focused_class = 'searchio-live-search-result--focused', 372 item_class = '.searchio-live-search-result', 373 a11y_keys = this.a11y_keys; 374 375 jQuery(document).off('keyup.searchio_a11y').on('keyup.searchio_a11y', function(e){ 376 377 // If results are not displayed, don't bind keypress. 378 if ( ! $results.hasClass('searchio-live-search-results-showing') ) { 379 jQuery(document).off('keyup.searchio_a11y'); 380 return; 381 } 382 383 // If key pressed doesn't match our a11y keys list do nothing. 384 if ( jQuery.inArray( e.keyCode, a11y_keys ) === -1 ) { 385 return; 386 } 387 388 e.preventDefault(); 389 390 // On `esc` keypress (only when input search is not focused). 391 if ( e.keyCode === 27 && ! $input.is(':focus') ) { 392 393 self.destroy_results(); 394 395 // Unbind keypress 396 jQuery(document).off('keyup.searchio_a11y'); 397 398 // Get back the focus on input search. 399 $input.focus(); 400 401 jQuery(document).trigger("searchio_live_escape_results"); 402 403 return; 404 } 405 406 // On `down` arrow keypress 407 if ( e.keyCode === 40 ) { 408 var $current = jQuery( $results[0] ).find( '.' + focused_class ); 409 if ( $current.length === 1 && $current.next().length === 1 ) { 410 $current.removeClass( focused_class ).attr('aria-selected', 'false') 411 .next().addClass( focused_class ).attr('aria-selected', 'true') 412 .find( 'a' ).focus(); 413 } else { 414 $current.removeClass( focused_class ).attr('aria-selected', 'false'); 415 $results.find( item_class + ':first' ).addClass( focused_class ).attr('aria-selected', 'true') 416 .find( 'a' ).focus(); 417 } 418 jQuery(document).trigger( "searchio_live_key_arrowdown_pressed" ); 419 } 420 421 // On `up` arrow keypress 422 if ( e.keyCode === 38 ) { 423 var $currentItem = jQuery( $results[0] ).find( '.' + focused_class ); 424 if ( $currentItem.length === 1 && $currentItem.prev().length === 1 ) { 425 $currentItem.removeClass( focused_class ).attr('aria-selected', 'false') 426 .prev().addClass( focused_class ).attr('aria-selected', 'true') 427 .find( 'a' ).focus(); 428 } else { 429 $currentItem.removeClass( focused_class ).attr('aria-selected', 'false'); 430 $results.find( item_class + ':last' ).addClass( focused_class ).attr('aria-selected', 'true') 431 .find( 'a' ).focus(); 432 } 433 jQuery(document).trigger( "searchio_live_key_arrowup_pressed" ); 434 } 435 436 // On 'enter' keypress 437 if ( e.keyCode === 13 ) { 438 jQuery(document).trigger( "searchio_live_key_enter_pressed" ); 439 } 440 441 // On 'tab' keypress 442 if ( e.keyCode === 9 ) { 443 jQuery(document).trigger( "searchio_live_key_tab_pressed" ); 444 } 445 446 });
447 448 jQuery(document).trigger( "searchio_live_keyboad_navigation" ); 449 }, 450 451 aria_expanded: function( is_expanded ) { 452 var $resultsEl = this.results_el; 453 454 if ( is_expanded ) { 455 $resultsEl.attr('aria-expanded', 'true'); 456 } else { 457 $resultsEl.attr('aria-expanded', 'false'); 458 } 459 460 jQuery(document).trigger( "searchio_live_aria_expanded" ); 461 }, 462 463 position_results: function(){ 464 var $input = this.input_el, 465 input_offset = $input.offset(), 466 $results = this.results_el, 467 results_top_offset = 0; 468 469 // don't try to position a results element when the input field is hidden 470 if ($input.is(":hidden")) { 471 return; 472 } 473 474 // check for an offset 475 input_offset.left += parseInt(this.config.results.offset.x,10); 476 input_offset.top += parseInt(this.config.results.offset.y,10); 477 478 // position the results container 479 switch(this.config.results.position){ 480 case 'top': 481 results_top_offset = 0 - $results.height(); 482 break; 483 default: 484 results_top_offset = $input.outerHeight(); 485 } 486 487 // apply the offset and finalize the position 488 $results.css('left',input_offset.left); 489 $results.css('top', ( input_offset.top + results_top_offset ) + 'px'); 490 let $input_width = $input.outerWidth()-parseInt($results.css('paddingRight').replace('px',''),10)-parseInt($results.css('paddingLeft').replace('px',''),10); 491 if(searchio_live_search_params.io_search_results_same_width === '1'){ 492 let $results_width = parseInt(searchio_live_search_params.io_search_results_width) 493 $results.width($results_width); 494 if(searchio_live_search_params.io_search_results_align == 'right'){ 495 if($input_width < $results_width) { 496 input_offset.left = Math.abs(input_offset.left - Math.abs($input_width-$results_width)); 497 $results.css('left',input_offset.left); 498 } else{ 499 input_offset.left = input_offset.left + Math.abs($input_width-$results_width); 500 $results.css('left',input_offset.left); 501 } 502 } 503 } 504 else{ 505 $results.width($input_width); 506 } 507 508 jQuery(document).trigger( "searchio_live_position_results", [ $results.css('left'), $results.css('top'), $results.width() ] ); 509 }, 510 511 destroy_results: function(e){ 512 this.hide_spinner(); 513 this.aria_expanded(false); 514 this.results_el.empty().removeClass('searchio-live-search-results-showing'); 515 this.results_showing = false; 516 this.has_results = false; 517 518 jQuery(document).trigger( "searchio_live_destroy_results" ); 519 }, 520 521 // if the search value changed, we've waited long enough, and we have at least the minimum characters: search! 522 maybe_search: function(e){ 523 // If key pressed doesn't match our a11y keys list do nothing. 524 if ( jQuery.inArray( e.keyCode, this.a11y_keys ) > -1 ) { 525 return; 526 } 527 clearTimeout(this.timer); 528 if(e.currentTarget.value.length >= this.config.input.min_chars){ 529 if (this.current_request) { 530 this.current_request.abort(); 531 } 532 this.timer = setTimeout( 533 jQuery.proxy(this.search, this, e), 534 this.config.input.delay 535 ); 536 } 537 }, 538 539 show_spinner: function(){ 540 if(this.config.spinner&&!this.spinner_showing){ 541 this.spinner.spin(document.getElementById(this.results_id)); 542 this.spinner_showing = true; 543 jQuery(document).trigger( "searchio_live_show_spinner" ); 544 } 545 }, 546 547 hide_spinner: function(){ 548 if(this.config.spinner){ 549 this.spinner.stop(); 550 this.spinner_showing = false; 551 jQuery(document).trigger( "searchio_live_hide_spinner" ); 552 } 553 }, 554 555 // perform the search 556 search: function(e){ 557 var self = this, 558 $form = this.form_el, 559 values = $form.serialize(), 560 action = $form.attr('action') ? $form.attr('action') : '', 561 $input = this.input_el, 562 $results = this.results_el; 563 564 jQuery(document).trigger( "searchio_live_search_start", [ $input, $results, $form, action, values ] ); 565 566 this.aria_expanded(false); 567 568 // append our action, engine, and (redundant) query (so as to save the trouble of finding it again server side) 569 values += '&action=searchio_live_search&swpengine=' + $input.data('swpengine') + '&swpquery=' + encodeURIComponent($input.val()) + '&origin_id=' + parseInt(searchio_live_search_params.origin_id,10); 570 571 if(action.indexOf('?') !== -1){ 572 action = action.split('?'); 573 values += '&' + action[1]; 574 } 575 576 this.last_string = $input.val(); 577 this.has_results = true; 578 // put the request into the current_request var 579 this.current_request = jQuery.ajax({ 580 url: searchio_live_search_params.ajaxurl, 581 type: "POST", 582 data: values, 583 complete: function(){ 584 jQuery(document).trigger( "searchio_live_search_complete", [ $input, $results, $form, action, values ] ); 585 self.spinner_showing = false; 586 self.hide_spinner(); 587 this.current_request = false; 588 jQuery(document).trigger( "searchio_live_search_shutdown", [ $input, $results, $form, action, values ] ); 589 }, 590 success: function(response){ 591 if(response === 0){ 592 response = ""; 593 } 594 jQuery(document).trigger( "searchio_live_search_success", [ $input, $results, $form, action, values ] ); 595 self.position_results(); 596 $results.html(response); 597 self.aria_expanded(true); 598 self.keyboard_navigation(); 599 jQuery(document).trigger( "searchio_live_search_shutdown", [ $input, $results, $form, action, values ] ); 600 } 601 });
602 }, 603 604 uniqid: function(prefix, more_entropy) { 605 // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) 606 // + revised by: Kankrelune (http://www.webfaktory.info/) 607 // % note 1: Uses an internal counter (in php_js global) to avoid collision 608 // * example 1: uniqid(); 609 // * returns 1: 'a30285b160c14' 610 // * example 2: uniqid('foo'); 611 // * returns 2: 'fooa30285b1cd361' 612 // * example 3: uniqid('bar', true); 613 // * returns 3: 'bara20285b23dfd1.31879087' 614 if (typeof prefix === 'undefined') { 615 prefix = ""; 616 } 617 618 var retId; 619 var formatSeed = function (seed, reqWidth) { 620 seed = parseInt(seed, 10).toString(16); // to hex str 621 if (reqWidth < seed.length) { // so long we split 622 return seed.slice(seed.length - reqWidth); 623 } 624 if (reqWidth > seed.length) { // so short we pad 625 return new Array(1 + (reqWidth - seed.length)).join('0') + seed; 626 } 627 return seed; 628 }; 629 630 // BEGIN REDUNDANT 631 if (!this.php_js) { 632 this.php_js = {}; 633 } 634 // END REDUNDANT 635 if (!this.php_js.uniqidSeed) { // init seed with big random int 636 this.php_js.uniqidSeed = Math.floor(Math.random() * 0x75bcd15); 637 } 638 this.php_js.uniqidSeed++; 639 640 retId = prefix; // start with prefix, add current milliseconds hex string 641 retId += formatSeed(parseInt(new Date().getTime() / 1000, 10), 8); 642 retId += formatSeed(this.php_js.uniqidSeed, 5); // add seed hex string 643 if (more_entropy) { 644 // for more entropy we add a float lower to 10 645 retId += (Math.random() * 10).toFixed(8).toString(); 646 } 647 648 return retId; 649 } 650 }; 651 652 jQuery.fn[plugin_name] = function(options){ 653 this.each(function(){ 654 if(!jQuery.data(this, "plugin_" + plugin_name)){ 655 jQuery.data(this, "plugin_" + plugin_name, new searchIOLiveSearch(jQuery(this), options)); 656 } 657 }); 658 659 // chain jQuery functions 660 return this; 661 }; 662 })(); 663 664 // find all applicable searchIO Live Search inputs and bind them 665 jQuery(document).ready(function(){ 666 if (typeof jQuery().searchio_live_search == 'function') { 667 jQuery('input[data-swplive="true"]').searchio_live_search(); 668 669 // Gutenberg integration is based on a body class addition because we don't have the 670 // ability to manipulate the markup as we do with get_search_form(). 671 if(typeof _searchIO_LIVE_AJAX_SEARCH_BLOCKS !== 'undefined' && _searchIO_LIVE_AJAX_SEARCH_BLOCKS) { 672 jQuery('input.wp-block-search__input').each(function() { 673 // Append data vars. 674 jQuery(this).attr('data-swpengine', _searchIO_LIVE_AJAX_SEARCH_ENGINE); 675 jQuery(this).attr('data-swpconfig', _searchIO_LIVE_AJAX_SEARCH_CONFIG); 676 677 // Init live search. 678 jQuery(this).searchio_live_search(); 679 });
680 } 681 } 682 }); 683 684}());
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.