1/** 2 * Featherlight - ultra slim jQuery lightbox 3 * Version 1.7.14-UMD - http://noelboss.github.io/featherlight/ 4 * 5 * Copyright 2019, Noël Raoul Bossart (http://www.noelboss.com) 6 * MIT Licensed. 7**/ 8(function (factory) { 9 if (typeof define === 'function' && define.amd) { 10 // AMD. Register as an anonymous module. 11 define(['jquery'], factory); 12 } else if (typeof module === 'object' && module.exports) { 13 // Node/CommonJS 14 module.exports = function (root, jQuery) { 15 if (jQuery === undefined) { 16 // require('jQuery') returns a factory that requires window to 17 // build a jQuery instance, we normalize how we use modules 18 // that require this pattern but the window provided is a noop 19 // if it's defined (how jquery works) 20 if (typeof window !== 'undefined') { 21 jQuery = require('jquery'); 22 } else { 23 jQuery = require('jquery')(root); 24 } 25 } 26 factory(jQuery); 27 return jQuery; 28 }; 29 } else { 30 // Browser globals 31 factory(jQuery); 32 } 33})(function ($) { 34 "use strict"; 35 36 if ('undefined' === typeof $) { 37 if ('console' in window) { window.console.info('Too much lightness, Featherlight needs jQuery.'); } 38 return; 39 } 40 if ($.fn.jquery.match(/-ajax/)) { 41 if ('console' in window) { window.console.info('Featherlight needs regular jQuery, not the slim version.'); } 42 return; 43 } 44 /* Featherlight is exported as $.featherlight. 45 It is a function used to open a featherlight lightbox. 46 [tech] 47 Featherlight uses prototype inheritance. 48 Each opened lightbox will have a corresponding object. 49 That object may have some attributes that override the 50 prototype's. 51 Extensions created with Featherlight.extend will have their 52 own prototype that inherits from Featherlight's prototype, 53 thus attributes can be overriden either at the object level, 54 or at the extension level. 55 To create callbacks that chain themselves instead of overriding, 56 use chainCallbacks. 57 For those familiar with CoffeeScript, this correspond to 58 Featherlight being a class and the Gallery being a class 59 extending Featherlight. 60 The chainCallbacks is used since we don't have access to 61 CoffeeScript's `super`. 62 */ 63 64 function Featherlight($content, config) { 65 if (this instanceof Featherlight) { /* called with new */ 66 this.id = Featherlight.id++; 67 this.setup($content, config); 68 this.chainCallbacks(Featherlight._callbackChain); 69 } else { 70 var fl = new Featherlight($content, config); 71 fl.open(); 72 return fl; 73 } 74 } 75 76 var opened = [], 77 pruneOpened = function (remove) { 78 opened = $.grep(opened, function (fl) { 79 return fl !== remove && fl.$instance.closest('body').length > 0; 80 }); 81 return opened; 82 }; 83 84 // Removes keys of `set` from `obj` and returns the removed key/values. 85 function slice(obj, set) { 86 var r = {}; 87 for (var key in obj) { 88 if (key in set) { 89 r[key] = obj[key]; 90 delete obj[key]; 91 } 92 } 93 return r; 94 } 95 96 // NOTE: List of available [iframe attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe). 97 var iFrameAttributeSet = { 98 allow: 1, allowfullscreen: 1, frameborder: 1, height: 1, longdesc: 1, marginheight: 1, marginwidth: 1, 99 mozallowfullscreen: 1, name: 1, referrerpolicy: 1, sandbox: 1, scrolling: 1, src: 1, srcdoc: 1, style: 1, 100 webkitallowfullscreen: 1, width: 1 101 }; 102 103 // Converts camelCased attributes to dasherized versions for given prefix: 104 // parseAttrs({hello: 1, hellFrozeOver: 2}, 'hell') => {froze-over: 2} 105 function parseAttrs(obj, prefix) { 106 var attrs = {}, 107 regex = new RegExp('^' + prefix + '([A-Z])(.*)'); 108 for (var key in obj) { 109 var match = key.match(regex); 110 if (match) { 111 var dasherized = (match[1] + match[2].replace(/([A-Z])/g, '-$1')).toLowerCase(); 112 attrs[dasherized] = obj[key]; 113 } 114 } 115 return attrs; 116 } 117 118 /* document wide key handler */ 119 var eventMap = { keyup: 'onKeyUp', resize: 'onResize' }; 120 121 var globalEventHandler = function (event) { 122 $.each(Featherlight.opened().reverse(), function () { 123 if (!event.isDefaultPrevented()) { 124 if (false === this[eventMap[event.type]](event)) { 125 event.preventDefault(); event.stopPropagation(); return false;
126 } 127 } 128 }); 129 }; 130 131 var toggleGlobalEvents = function (set) { 132 if (set !== Featherlight._globalHandlerInstalled) { 133 Featherlight._globalHandlerInstalled = set; 134 var events = $.map(eventMap, function (_, name) { return name + '.' + Featherlight.prototype.namespace; }).join(' '); 135 $(window)[set ? 'on' : 'off'](events, globalEventHandler); 136 } 137 }; 138 139 Featherlight.prototype = { 140 constructor: Featherlight, 141 /*** defaults ***/ 142 /* extend featherlight with defaults and methods */ 143 namespace: 'featherlight', /* Name of the events and css class prefix */ 144 targetAttr: 'data-featherlight', /* Attribute of the triggered element that contains the selector to the lightbox content */ 145 variant: null, /* Class that will be added to change look of the lightbox */ 146 resetCss: false, /* Reset all css */ 147 background: null, /* Custom DOM for the background, wrapper and the closebutton */ 148 openTrigger: 'click', /* Event that triggers the lightbox */ 149 closeTrigger: 'click', /* Event that triggers the closing of the lightbox */ 150 filter: null, /* Selector to filter events. Think $(...).on('click', filter, eventHandler) */ 151 root: 'body', /* Where to append featherlights */ 152 openSpeed: 250, /* Duration of opening animation */ 153 closeSpeed: 250, /* Duration of closing animation */ 154 closeOnClick: 'background', /* Close lightbox on click ('background', 'anywhere' or false) */ 155 closeOnEsc: true, /* Close lightbox when pressing esc */ 156 closeIcon: '✕', /* Close icon */ 157 loading: '', /* Content to show while initial content is loading */ 158 persist: false, /* If set, the content will persist and will be shown again when opened again. 'shared' is a special value when binding multiple elements for them to share the same content */ 159 otherClose: null, /* Selector for alternate close buttons (e.g. "a.close") */ 160 beforeOpen: $.noop, /* Called before open. can return false to prevent opening of lightbox. Gets event as parameter, this contains all data */ 161 beforeContent: $.noop, /* Called when content is loaded. Gets event as parameter, this contains all data */ 162 beforeClose: $.noop, /* Called before close. can return false to prevent closing of lightbox. Gets event as parameter, this contains all data */ 163 afterOpen: $.noop, /* Called after open. Gets event as parameter, this contains all data */ 164 afterContent: $.noop, /* Called after content is ready and has been set. Gets event as parameter, this contains all data */ 165 afterClose: $.noop, /* Called after close. Gets event as parameter, this contains all data */ 166 onKeyUp: $.noop, /* Called on key up for the frontmost featherlight */ 167 onResize: $.noop, /* Called after new content and when a window is resized */ 168 type: null, /* Specify type of lightbox. If unset, it will check for the targetAttrs value. */ 169 contentFilters: ['jquery', 'image', 'html', 'ajax', 'iframe', 'text'], /* List of content filters to use to determine the content */ 170 171 /*** methods ***/ 172 /* setup iterates over a single instance of featherlight and prepares the background and binds the events */ 173 setup: function (target, config) { 174 /* all arguments are optional */ 175 if (typeof target === 'object' && target instanceof $ === false && !config) { 176 config = target; 177 target = undefined; 178 } 179 180 var self = $.extend(this, config, { target: target }), 181 css = !self.resetCss ? self.namespace : self.namespace + '-reset', /* by adding -reset to the classname, we reset all the default css */ 182 $background = $(self.background || [ 183 '<div class="' + css + '-loading ' + css + '">', 184 '<div class="' + css + '-content">', 185 '<button class="' + css + '-close-icon ' + self.namespace + '-close" aria-label="Close">', 186 self.closeIcon, 187 '</button>', 188 '<div class="' + self.namespace + '-inner">' + self.loading + '</div>', 189 '</div>', 190 '</div>'].join('')), 191 closeButtonSelector = '.' + self.namespace + '-close' + (self.otherClose ? ',' + self.otherClose : ''); 192 193 self.$instance = $background.clone().addClass(self.variant); /* clone DOM for the background, wrapper and the close button */ 194 195 /* close when click on background/anywhere/null or closebox */ 196 self.$instance.on(self.closeTrigger + '.' + self.namespace, function (event) { 197 if (event.isDefaultPrevented()) { 198 return; 199 } 200 var $target = $(event.target); 201 if (('background' === self.closeOnClick && $target.is('.' + self.namespace)) 202 || 'anywhere' === self.closeOnClick 203 || $target.closest(closeButtonSelector).length) { 204 self.close(event); 205 event.preventDefault(); 206 } 207 }); 208 209 return this; 210 }, 211 212 /* this method prepares the content and converts it into a
212jQuery object or a promise */ 213 getContent: function () { 214 if (this.persist !== false && this.$content) { 215 return this.$content; 216 } 217 var self = this, 218 filters = this.constructor.contentFilters, 219 readTargetAttr = function (name) { return self.$currentTarget && self.$currentTarget.attr(name); }, 220 targetValue = readTargetAttr(self.targetAttr), 221 data = self.target || targetValue || ''; 222 223 /* Find which filter applies */ 224 var filter = filters[self.type]; /* check explicit type like {type: 'image'} */ 225 226 /* check explicit type like data-featherlight="image" */ 227 if (!filter && data in filters) { 228 filter = filters[data]; 229 data = self.target && targetValue; 230 } 231 data = data || readTargetAttr('href') || ''; 232 233 /* check explicity type & content like {image: 'photo.jpg'} */ 234 if (!filter) { 235 for (var filterName in filters) { 236 if (self[filterName]) { 237 filter = filters[filterName]; 238 data = self[filterName]; 239 } 240 } 241 } 242 243 /* otherwise it's implicit, run checks */ 244 if (!filter) { 245 var target = data; 246 data = null; 247 $.each(self.contentFilters, function () { 248 filter = filters[this]; 249 if (filter.test) { 250 data = filter.test(target); 251 } 252 if (!data && filter.regex && target.match && target.match(filter.regex)) { 253 data = target; 254 } 255 return !data; 256 }); 257 if (!data) { 258 if ('console' in window) { window.console.error('Featherlight: no content filter found ' + (target ? ' for "' + target + '"' : ' (no target specified)')); } 259 return false; 260 } 261 } 262 /* Process it */ 263 return filter.process.call(self, data); 264 }, 265 266 /* sets the content of $instance to $content */ 267 setContent: function ($content) { 268 this.$instance.removeClass(this.namespace + '-loading'); 269 270 /* we need a special class for the iframe */ 271 this.$instance.toggleClass(this.namespace + '-iframe', $content.is('iframe')); 272 273 /* replace content by appending to existing one before it is removed 274 this insures that featherlight-inner remain at the same relative 275 position to any other items added to featherlight-content */ 276 this.$instance.find('.' + this.namespace + '-inner') 277 .not($content) /* excluded new content, important if persisted */ 278 .slice(1).remove().end() /* In the unexpected event where there are many inner elements, remove all but the first one */ 279 .replaceWith($.contains(this.$instance[0], $content[0]) ? '' : $content); 280 281 this.$content = $content.addClass(this.namespace + '-inner'); 282 283 return this; 284 }, 285 286 /* opens the lightbox. "this" contains $instance with the lightbox, and with the config. 287 Returns a promise that is resolved after is successfully opened. */ 288 open: function (event) { 289 var self = this; 290 self.$instance.hide().appendTo(self.root); 291 if ((!event || !event.isDefaultPrevented()) 292 && self.beforeOpen(event) !== false) { 293 294 if (event) { 295 event.preventDefault(); 296 } 297 var $content = self.getContent(); 298 299 if ($content) { 300 opened.push(self); 301 302 toggleGlobalEvents(true); 303 304 self.$instance.fadeIn(self.openSpeed); 305 self.beforeContent(event); 306 307 /* Set content and show */ 308 return $.when($content) 309 .always(function ($openendContent) { 310 if ($openendContent) { 311 self.setContent($openendContent); 312 self.afterContent(event); 313 } 314 }) 315 .then(self.$instance.promise()) 316 /* Call afterOpen after fadeIn is done */ 317 .done(function () { self.afterOpen(event); }); 318 } 319 } 320 self.$instance.detach(); 321 return $.Deferred().reject().promise(); 322 }, 323 324 /* closes the lightbox. "this" contains $instance with the lightbox, and with the config 325 returns a promise, resolved after the lightbox is successfully closed. */ 326 close: function (event) { 327 var self = this, 328 deferred = $.Deferred(); 329 330 if (self.beforeClose(event) === false) { 331 deferred.reject(); 332 } else { 333 334 if (0 === pruneOpened(self).length) { 335 toggleGlobalEvents(false); 336 } 337 338 self.$instance.fadeOut(self.closeSpeed, function () { 339 self.$instance.detach(); 340 self.afterClose(event); 341 deferred.resolve(); 342 }); 343 } 344 return deferred.promise(); 345 }, 346 347 /* resizes the content so it fits in visible area and keeps the same aspect ratio. 348 Does nothing if either the width or the height is not specified. 349 Called automatically on window resize. 350 Override if you want different behavior. */ 351 resize: function (w, h) { 352 if (w && h) { 353 /* Reset apparent image size first so container grows */ 354 this.$content.css('width', '').css('height', ''); 355 /* Calculate the worst ratio so that dimensions fit */ 356 /* Note: -1 to avoid rounding errors */ 357 var ratio = Math.max( 358 w / (this.$content.parent().width() - 1), 359 h / (this.$content.parent().height() - 1)); 360 /* Resize content */ 361 if (ratio > 1) { 362 ratio = h / Math.floor(h / ratio); /* Round ratio down so height calc works */ 363 this.$content.css('width', '' + w / ratio + 'px').css('height', '' + h / ratio + 'px'); 364 } 365 } 366 }, 367 368 /* Utility function to chain callbacks 369 [Warning: guru-level] 370 Used be extensions that want to let users specify callbacks but 371 also need themselves to use the callbacks. 372 The argument 'chain' has callback names as keys and function(super, event) 373 as values. That function is meant to call `super` at some point. 374 */ 375 chainCallbacks: function (chain) { 376 for (var name in chain) { 377 this[name] = $.proxy(chain[name], this, $.proxy(this[name], this)); 378 } 379 } 380 }; 381 382 $.extend(Featherlight, { 383 id: 0, /* Used to id single featherlight instances */ 384 autoBind: '[data-featherlight]', /* Will automatically bind elements matching this selector. Clear or set before onReady */ 385 defaults: Featherlight.prototype, /* You can access and override all defaults using $.featherlight.defaults, which is just a synonym for $.featherlight.prototype */ 386 /* Contains the logic to determine content */ 387 contentFilters: { 388 jquery: { 389 regex: /^[#.]\w/, /* Anything that starts with a class name or identifiers */ 390 test: function (elem) { return elem instanceof $ && elem; }, 391 process: function (elem) { return this.persist !== false ? $(elem) : $(elem).clone(true); } 392 }, 393 image: { 394 regex: /\.(png|jpg|jpeg|gif|tiff?|bmp|svg)(\?\S*)?$/i, 395 process: function (url) { 396 var self = this, 397 deferred = $.Deferred(), 398 img = new Image(), 399 $img = $('<img src="' + url + '" alt="" class="' + self.namespace + '-image" />'); 400 img.onload = function () {
401 /* Store naturalWidth & height for IE8 */ 402 $img.naturalWidth = img.width; $img.naturalHeight = img.height; 403 deferred.resolve($img); 404 }; 405 img.onerror = function () { deferred.reject($img); }; 406 img.src = url; 407 return deferred.promise(); 408 } 409 }, 410 html: { 411 regex: /^\s*<[\w!][^<]*>/, /* Anything that starts with some kind of valid tag */ 412 process: function (html) { return $(html); } 413 }, 414 ajax: { 415 regex: /./, /* At this point, any content is assumed to be an URL */ 416 process: function (url) { 417 var self = this, 418 deferred = $.Deferred(); 419 /* we are using load so one can specify a target with: url.html #targetelement */ 420 var $container = $('<div></div>').load(url, function (response, status) { 421 if (status !== "error") { 422 deferred.resolve($container.contents()); 423 } 424 deferred.reject(); 425 }); 426 return deferred.promise(); 427 } 428 }, 429 iframe: { 430 process: function (url) { 431 var deferred = new $.Deferred(); 432 var $content = $('<iframe/>'); 433 var css = parseAttrs(this, 'iframe'); 434 var attrs = slice(css, iFrameAttributeSet); 435 $content.hide() 436 .attr('src', url) 437 .attr(attrs) 438 .css(css) 439 .on('load', function () { deferred.resolve($content.show()); }) 440 // We can't move an <iframe> and avoid reloading it, 441 // so let's put it in place ourselves right now: 442 .appendTo(this.$instance.find('.' + this.namespace + '-content')); 443 return deferred.promise(); 444 } 445 }, 446 text: { 447 process: function (text) { return $('<div>', { text: text }); } 448 } 449 }, 450 451 functionAttributes: ['beforeOpen', 'afterOpen', 'beforeContent', 'afterContent', 'beforeClose', 'afterClose'], 452 453 /*** class methods ***/ 454 /* read element's attributes starting with data-featherlight- */ 455 readElementConfig: function (element, namespace) { 456 var Klass = this, 457 regexp = new RegExp('^data-' + namespace + '-(.*)'), 458 config = {}; 459 if (element && element.attributes) { 460 $.each(element.attributes, function () { 461 var match = this.name.match(regexp); 462 if (match) { 463 var val = this.value, 464 name = $.camelCase(match[1]); 465 if ($.inArray(name, Klass.functionAttributes) >= 0) { /* jshint -W054 */ 466 val = new Function(val); /* jshint +W054 */ 467 } else { 468 try { val = JSON.parse(val); } 469 catch (e) { } 470 } 471 config[name] = val; 472 } 473 }); 474 } 475 return config; 476 }, 477 478 /* Used to create a Featherlight extension 479 [Warning: guru-level] 480 Creates the extension's prototype that in turn 481 inherits Featherlight's prototype. 482 Could be used to extend an extension too... 483 This is pretty high level wizardy, it comes pretty much straight 484 from CoffeeScript and won't teach you anything about Featherlight 485 as it's not really specific to this library. 486 My suggestion: move along and keep your sanity. 487 */ 488 extend: function (child, defaults) { 489 /* Setup class hierarchy, adapted from CoffeeScript */ 490 var Ctor = function () { this.constructor = child; }; 491 Ctor.prototype = this.prototype; 492 child.prototype = new Ctor(); 493 child.__super__ = this.prototype; 494 /* Copy class methods & attributes */ 495 $.extend(child, this, defaults); 496 child.defaults = child.prototype; 497 return child; 498 }, 499 500 attach: function ($source, $content, config) { 501 var Klass = this; 502 if (typeof $content === 'object' && $content instanceof $ === false && !config) { 503 config = $content; 504 $content = undefined; 505 } 506 /* make a copy */ 507 config = $.extend({}, config); 508 509 /* Only for openTrigger, filter & namespace... */ 510 var namespace = config.namespace || Klass.defaults.namespace, 511 tempConfig = $.extend({}, Klass.defaults, Klass.readElementConfig($source[0], namespace), c
511onfig), 512 sharedPersist; 513 var handler = function (event) { 514 var $target = $(event.currentTarget); 515 /* ... since we might as well compute the config on the actual target */ 516 var elemConfig = $.extend( 517 { $source: $source, $currentTarget: $target }, 518 Klass.readElementConfig($source[0], tempConfig.namespace), 519 Klass.readElementConfig(event.currentTarget, tempConfig.namespace), 520 config); 521 var fl = sharedPersist || $target.data('featherlight-persisted') || new Klass($content, elemConfig); 522 if (fl.persist === 'shared') { 523 sharedPersist = fl; 524 } else if (fl.persist !== false) { 525 $target.data('featherlight-persisted', fl); 526 } 527 if (elemConfig.$currentTarget.blur) { 528 elemConfig.$currentTarget.blur(); // Otherwise 'enter' key might trigger the dialog again 529 } 530 fl.open(event); 531 }; 532 533 $source.on(tempConfig.openTrigger + '.' + tempConfig.namespace, tempConfig.filter, handler); 534 535 return { filter: tempConfig.filter, handler: handler }; 536 }, 537 538 current: function () { 539 var all = this.opened(); 540 return all[all.length - 1] || null; 541 }, 542 543 opened: function () { 544 var klass = this; 545 pruneOpened(); 546 return $.grep(opened, function (fl) { return fl instanceof klass; }); 547 }, 548 549 close: function (event) { 550 var cur = this.current(); 551 if (cur) { return cur.close(event); } 552 }, 553 554 /* Does the auto binding on startup. 555 Meant only to be used by Featherlight and its extensions 556 */ 557 _onReady: function () { 558 var Klass = this; 559 if (Klass.autoBind) { 560 var $autobound = $(Klass.autoBind); 561 /* Bind existing elements */ 562 $autobound.each(function () { 563 Klass.attach($(this)); 564 }); 565 /* If a click propagates to the document level, then we have an item that was added later on */ 566 $(document).on('click', Klass.autoBind, function (evt) { 567 if (evt.isDefaultPrevented()) { 568 return; 569 } 570 var $cur = $(evt.currentTarget); 571 var len = $autobound.length; 572 $autobound = $autobound.add($cur); 573 if (len === $autobound.length) { 574 return; /* already bound */ 575 } 576 /* Bind featherlight */ 577 var data = Klass.attach($cur); 578 /* Dispatch event directly */ 579 if (!data.filter || $(evt.target).parentsUntil($cur, data.filter).length > 0) { 580 data.handler(evt); 581 } 582 }); 583 } 584 }, 585 586 /* Featherlight uses the onKeyUp callback to intercept the escape key. 587 Private to Featherlight. 588 */ 589 _callbackChain: { 590 onKeyUp: function (_super, event) { 591 if (27 === event.keyCode) { 592 if (this.closeOnEsc) { 593 $.featherlight.close(event); 594 } 595 return false; 596 } else { 597 return _super(event); 598 } 599 }, 600 601 beforeOpen: function (_super, event) { 602 // Used to disable scrolling 603 $(document.documentElement).addClass('with-featherlight'); 604 605 // Remember focus: 606 this._previouslyActive = document.activeElement; 607 608 // Disable tabbing: 609 // See http://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus 610 this._$previouslyTabbable = $("a, input, select, textarea, iframe, button, iframe, [contentEditable=true]") 611 .not('[tabindex]') 612 .not(this.$instance.find('button')); 613 614 this._$previouslyWithTabIndex = $('[tabindex]').not('[tabindex="-1"]'); 615 this._previousWithTabIndices = this._$previouslyWithTabIndex.map(function (_i, elem) { 616 return $(elem).attr('tabindex'); 617 }); 618 619 this._$previouslyWithTabIndex.add(this._$previouslyTabbable).attr('tabindex', -1); 620 621 if (document.activeElement.blur) { 622 document.activeElement.blur(); 623 } 624 return _super(event); 625 }, 626 627 afterClose: function (_super, event) { 628 var r = _super(event); 629 // Restore focus 630 var self = this; 631 this._$previouslyTabbable.removeAttr('tabindex'); 632 this._$previouslyWithTabIndex.each(function (i, elem) { 633 $(elem).attr('tabindex', self._previousWithTabIndices[i]); 634 }); 635 this._previouslyActive.focus(); 636 // Restore scroll 637 if (Featherlight.opened().length === 0) { 638 $(document.documentElement).removeClass('with-featherlight'); 639 } 640 return r; 641 }, 642 643 onResize: function (_super, event) {
644 this.resize(this.$content.naturalWidth, this.$content.naturalHeight); 645 return _super(event); 646 }, 647 648 afterContent: function (_super, event) { 649 var r = _super(event); 650 this.$instance.find('[autofocus]:not([disabled])').focus(); 651 this.onResize(event); 652 return r; 653 } 654 } 655 }); 656 657 $.featherlight = Featherlight; 658 659 /* bind jQuery elements to trigger featherlight */ 660 $.fn.featherlight = function ($content, config) { 661 Featherlight.attach(this, $content, config); 662 return this; 663 }; 664 665 /* bind featherlight on ready if config autoBind is set */ 666 $(document).ready(function () { Featherlight._onReady(); }); 667});
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.