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