PageSourceSearch

https://www.gerritys.com/wp-content/plugins/shoptocook-recipes/js/wookmark.js?ver=7.1.2

js gerritys.com collected 2026-10-07 23:23:07 UTC 24,004 bytes, 764 lines download raw bytes

1/*!
2  jQuery Wookmark plugin
3  @name wookmark.js
4  @author Christoph Ono ([email protected] or @gbks)
5  @author Sebastian Helzle ([email protected] or @sebobo)
6  @version 2.0.0
7  @date 02/15/2015
8  @category jQuery plugin
9  @copyright (c) 2009-2015 Christoph Ono (www.wookmark.com)
10  @license Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
11*/
12/*global define, window, jQuery*/
13/*jslint plusplus: true, bitwise: true */
14(function (factory) {
15  if (typeof define === 'function' && define.amd) {
16    define(['window', 'document'], factory);
17  } else {
18    factory(window, document);
19  }
20}(function (window, document) {
21
22  // Wookmark default options
23  // ------------------------
24  var defaultOptions = {
25    align: 'center',
26    autoResize: true,
27    comparator: null,
28    direction: undefined,
29    ignoreInactiveItems: true,
30    inactiveClass: 'wookmark-inactive',
31    itemSelector: undefined,
32    itemWidth: 0,
33    fillEmptySpace: false,
34    flexibleWidth: 0,
35    offset: 5,
36    outerOffset: 0,
37    onLayoutChanged: undefined,
38    placeholderClass: 'wookmark-placeholder',
39    possibleFilters: [],
40    resizeDelay: 50,
41    verticalOffset: undefined
42  };
43
44  // Helper functions
45  // ----------------
46
47  // Bind function to set the context for the Wookmark instance function
48  function __bind(fn, me) {
49    return function () {
50      return fn.apply(me, arguments);
51    };
52  }
53
54  // Function for executing css writes to dom on the next animation frame if supported
55  var executeNextFrame = window.requestAnimationFrame || function (callback) { callback(); };
56
57  // Update multiple css values on an object
58  function setCSS(el, properties) {
59    var key;
60    for (key in properties) {
61      if (properties.hasOwnProperty(key)) {
62        el.style[key] = properties[key];
63      }
64    }
65  }
66
67  // Update the css properties of multiple elements at the same time
68  // befor the browsers next animation frame.
69  // The parameter `data` has to be an array containing objects, each
70  // with the element and the desired css properties.
71  function bulkUpdateCSS(data) {
72    executeNextFrame(function () {
73      var i, item;
74      for (i = 0; i < data.length; i++) {
75        item = data[i];
76        setCSS(item.el, item.css);
77      }
78    });
79  }
80
81  // Remove whitespace around filter names
82  function cleanFilterName(filterName) {
83    return filterName.replace(/^\s+|\s+$/g, '').toLowerCase();
84  }
85
86  // Remove listener from an element (IE8 compatible)
87  function removeEventListener(el, eventName, handler) {
88    if (el.removeEventListener) {
89      el.removeEventListener(eventName, handler);
90    } else {
91      el.detachEvent('on' + eventName, handler);
92    }
93  }
94
95  // Add listener to an element (IE8 compatible)
96  function addEventListener(el, eventName, handler) {
97    removeEventListener(el, eventName, handler);
98    if (el.addEventListener) {
99      el.addEventListener(eventName, handler);
100    } else {
101      el.attachEvent('on' + eventName, function () {
102        handler.call(el);
103      });
104    }
105  }
106
107  // Checks if element `el` is not visible in the browser
108  function isHidden(el) {
109    return el.offsetParent === null;
110  }
111
112  // Returns the elements height without margin
113  function getHeight(el) {
114    return el.offsetHeight;
115  }
116
117  // Returns the elements width without margin
118  function getWidth(el) {
119    return el.offsetWidth;
120  }
121
122  // Return true if element has class
123  function hasClass(el, className) {
124    if (el.classList) {
125      return el.classList.contains(className);
126    }
127    return new RegExp('(^| )' + className + '( |$)', 'gi').test(el.className);
128  }
129
130  // Add class to element (IE8+)
131  function addClass(el, className) {
132    if (el.classList) {
133      el.classList.add(className);
134    } else {
135      el.className += ' ' + className;
136    }
137  }
138
139  // Remove class from element (IE8+)
140  function removeClass(el, className) {
141    if (el.classList) {
142      el.classList.remove(className);
143    } else {
144      el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
145    }
146  }
147
148  // Get value of specified data attribute
149  function getData(el, attr, isInt, prefix) {
150    if (prefix === undefined) {
151      prefix = 'wookmark-';
152    }
153    var val = el.getAttribute('data-' + prefix + attr);
154    if (isInt === true) {
155      return parseInt(val, 10);
156    }
157    return val;
158  }
159
160  // Set value of specified data attribute
161  function setData(el, attr, val, prefix) {
162    if (prefix === undefined) {
163      prefix = 'wookmark-';
164    }
165    el.setAttribute('data-' + prefix + attr, val);
166  }
167
168  // Remove duplicates from given array
169  function removeDuplicates(items) {
170    var temp = {}, result = [], x, i = items.length;
171    while (i--) {
172      x = getData(items[i], 'id', true);
173      if (!temp.hasOwnProperty(x)) {
174        temp[x] = 1;
175        result.push(items[i]);
176      }
177    }
178    return result;
179  }
180
181  // Get the computed style from an element (IE 8 compatible)
182  function getStyle(el, prop) {
183    return window.getComputedStyle !== undefined ? window.getComputedStyle(el, null).getPropertyValue(prop) : el.currentStyle[prop];
184  }
185
186
187  // IE 8 compatible indexOf
188  function indexOf(items, item) {
189    var len = items.length, i;
190    for (i = 0; i < len; i++) {
191      if (items[i] === item) {
192        return i;
193      }
194    }
195    return -1;
196  }
197
198  // Main wookmark plugin class
199  // --------------------------
200  function Wookmark(container, options) {
201    options = options || {};
202
203    if (typeof container === 'string') {
204      container = document.querySelector(container);
205    }
206
207    // Instance variables.
208    this.container = container;
209    this.columns = this.containerWidth = this.resizeTimer = null;
210    this.activeItemCount = 0;
211    this.placeholders = [];
212    this.itemHeightsInitialized = false;
213    this.itemHeightsDirty = false;
214    this.elementTag = 'div';
215
216    // Bind instance methods
217    this.initItems = __bind(this.initItems, this);
218    this.updateOptions = __bind(this.updateOptions, this);
219    this.onResize = __bind(this.onResize, this);
220    this.onRefresh = __bind(this.onRefresh, this);
221    this.getItemWidth = __bind(this.getItemWidth, this);
222    this.layout = __bind(this.layout, this);
223    this.layoutFull = __bind(this.layoutFull, this);
224    this.layoutColumns = __bind(this.layoutColumns, this);
225    this.filter = __bind(this.filter, this);
226    this.clear = __bind(this.clear, this);
227    this.getActiveItems = __bind(this.getActiveItems, this);
228    this.refreshPlaceholders = __bind(this.refreshPlaceholders, this);
229    this.sortElements = __bind(this.sortElements, this);
230    this.updateFilterClasses = __bind(this.updateFilterClasses, this);
231
232    // Initialize children of the container
233    this.initItems();
234
235    // Initial update and layout
236    this.container.style.display = 'block';
237    this.updateOptions(options);
238
239    // Collect filter classes after items have been initialized
240    this.updateFilterClasses();
241
242    // Listen to resize event of the browser if enabled
243    if (this.autoResize) {
244      addEventListener(window, 'resize', this.onResize);
245    }
246
247    // Listen to external refresh event
248    addEventListener(this.container, 'refreshWookmark', this.onRefresh);
249  }
250
251  // Get all valid children of the container object and store them
252  Wookmark.prototype.initItems = function () {
253    // By select all children of the container if no selector is specified
254    if (this.itemSelector === undefined) {
255      var items = [], child, children = this.container.children,
256        i = children.length;
257      while (i--) {
258        child = children[i];
259        // Skip comment nodes on IE8
260        if (child.nodeType !== 8) {
261          // Show item
262          child.style.display = '';
263          setData(child, 'id', i);
264          items.unshift(child);
265        }
266      }
267      this.items = items;
268    } else {
269      this.items = this.container.querySelectorAll(this.itemSelector);
270    }
271
272    if (this.items.length) {
273      this.elementTag = this.items[0].tagName;
274    }
275    this.itemHeightsDirty = true;
276  };
277
278  // Reload all filter classes from all items and cache them
279  Wookmark.prototype.updateFilterClasses = function () {
280    // Collect filter data
281    var i = this.items.length, j, filterClasses = {}, itemFilterClasses,
282      item, filterClass, possibleFilters = this.possibleFilters,
283      k = possibleFilters.length, possibleFilter;
284
285    while (i--) {
286      item = this.items[i];
287
288      // Read filter classes and globally store each filter class as object and the fitting items in the array
289      itemFilterClasses = JSON.parse(getData(item, 'filter-class', false, ''));
290      if (itemFilterClasses && typeof itemFilterClasses === 'object') {
291        j = itemFilterClasses.length;
292        while (j--) {
293          filterClass = cleanFilterName(itemFilterClasses[j]);
294          if (!filterClasses.hasOwnProperty(filterClass)) {
295            filterClasses[filterClass] = [];
296          }
297          filterClasses[filterClass].push(item);
298        }
299      }
300    }
301
302    while (k--) {
303      possibleFilter = cleanFilterName(possibleFilters[k]);
304      if (!filterClasses.hasOwnProperty(possibleFilter)) {
305        filterClasses[possibleFilter] = [];
306      }
307    }
308
309    this.filterClasses = filterClasses;
310  };
311
312  // Method for updating the plugins options
313  Wookmark.prototype.updateOptions = function (options) {
314    var key;
315    this.itemHeightsDirty = true;
316    options = options || {};
317
318    // Overwrite non existing instance variables with the ones from options or the defaults
319    for (key in defaultOptions) {
320      if (defaultOptions.hasOwnProperty(key)) {
321        if (options.hasOwnProperty(key)) {
322          this[key] = options[key];
323        } else if (!this.hasOwnProperty(key)) {
324          this[key] = defaultOptions[key];
325        }
326      }
327    }
328
329    // Vertical offset uses a fallback to offset
330    this.verticalOffset = this.verticalOffset || this.offset;
331
332    // Update layout so changes apply
333    this.layout(true);
334  };
335
336  // This timer ensures that layout is not continuously called as window is being dragged.
337  Wookmark.prototype.onResize = function () {
338    clearTimeout(this.resizeTimer);
339    this.itemHeightsDirty = this.flexibleWidth !== 0;
340    this.resizeTimer = setTimeout(this.layout, this.resizeDelay);
341  };
342
343  // Marks the items heights as dirty and does a relayout
344  Wookmark.prototype.onRefresh = function () {
345    this.itemHeightsDirty = true;
346    this.layout();
347  };
348
349  // Filters the active items with the given string filters.
350  // @param filters array of string
351  // @param mode 'or' or 'and'
352  Wookmark.prototype.filter = function (filters, mode, dryRun) {
353    var activeFilters = [], activeFiltersLength, activeItems = [],
354      i, j, k, filter;
355
356    filters = filters || [];
357    mode = mode || 'or';
358    dryRun = dryRun || false;
359
360    if (filters.length) {
361      // Collect active filters
362      for (i = 0; i < filters.length; i++) {
363        filter = cleanFilterName(filters[i]);
364        if (this.filterClasses.hasOwnProperty(filter)) {
365          activeFilters.push(this.filterClasses[filter]);
366        }
367      }
368
369      // Get items for active filters with the selected mode
370      i = activeFiltersLength = activeFilters.length;
371      if (mode === 'or' || activeFiltersLength === 1) {
372        // Set all items in all active filters active
373        while (i--) {
374          activeItems = activeItems.concat(activeFilters[i]);
375        }
376      } else if (mode === 'and') {
377        var shortestFilter = activeFilters[0], itemValid = true,
378          foundInFilter, currentItem, currentFilter;
379
380        // Find shortest filter class
381        while (i--) {
382          if (activeFilters[i].length < shortestFilter.length) {
383            shortestFilter = activeFilters[i];
384          }
385        }
386
387        // Iterate over shortest filter and find elements in other filter classes
388        shortestFilter = shortestFilter || [];
389        i = shortestFilter.length;
390        while (i--) {
391          currentItem = shortestFilter[i];
392          j = activeFiltersLength;
393          itemValid = true;
394
395          while (j-- && itemValid) {
396            currentFilter = activeFilters[j];
397            if (shortestFilter !== currentFilter) {
398              // Search for current item in each active filter class
399              foundInFilter = false;
400              k = currentFilter.length;
401              while (k-- && !foundInFilter) {
402                foundInFilter = currentFilter[k] === currentItem;
403              }
404              itemValid &= foundInFilter;
405            }
406          }
407
408          if (itemValid) {
409            activeItems = activeItems.concat(shortestFilter[i]);
410          }
411        }
412      }
413
414      // Remove duplicates from active items
415      if (activeFiltersLength > 1) {
416        activeItems = removeDuplicates(activeItems);
417      }
418
419      // Hide inactive items
420      if (!dryRun) {
421        i = this.items.length;
422        while (i--) {
423          if (indexOf(activeItems, this.items[i]) === -1) {
424            addClass(this.items[i], this.inactiveClass);
425          }
426        }
427      }
428    } else {
429      // Show all items if no filter is selected
430      activeItems = this.items;
431    }
432
433    // Show active items
434    if (!dryRun) {
435      i = activeItems.length;
436      while (i--) {
437        removeClass(activeItems[i], this.inactiveClass);
438      }
439      // Unset columns and refresh grid for a full layout
440      this.columns = null;
441      this.layout();
442    }
443    return activeItems;
444  };
445
446  // Creates or updates existing placeholders to create columns of even height
447  Wookmark.prototype.refreshPlaceholders = function (columnWidth, sideOffset) {
448    var i,
449      containerHeight = getHeight(this.container),
450      columnsLength = this.columns.length,
451      column,
452      height,
453      innerOffset,
454      lastColumnItem,
455      placeholdersHtml = '',
456      placeholder,
457      top;
458
459    // Add more placeholders if necessary
460    if (this.placeholders.length < columnsLength) {
461      for (i = 0; i < columnsLength - this.placeholders.length; i++) {
462        placeholdersHtml += '<' + this.elementTag + ' class="' + this.placeholderClass + '"/>';
463      }
464      this.container.insertAdjacentHTML('beforeend', placeholdersHtml);
465      this.placeholders = this.container.querySelectorAll('.' + this.placeholderClass);
466    }
467
468    innerOffset = (this.offset + parseInt(getStyle(this.placeholders[0], 'border-left-width'), 10) * 2) || 0;
469    innerOffset += (parseInt(getStyle(this.placeholders[0], 'padding-left'), 10) * 2)  || 0;
470
471    // Update each placeholder
472    for (i = 0; i < this.placeholders.length; i++) {
473      placeholder = this.placeholders[i];
474      column = this.columns[i];
475
476      if (i >= columnsLength || column.length === 0) {
477        placeholder.style.display = 'none';
478      } else {
479        lastColumnItem = column[column.length - 1];
480        top = getData(lastColumnItem, 'top', true) + getData(lastColumnItem, 'height', true) + this.verticalOffset;
481        height = Math.max(0, containerHeight - top - innerOffset);
482
483        setCSS(placeholder, {
484          position: 'absolute',
485          display: height > 0 ? 'block' : 'none',
486          left: (i * columnWidth + sideOffset) + 'px',
487          top: top + 'px',
488          width: (columnWidth - innerOffset) + 'px',
489          height: height + 'px'
490        });
491      }
492    }
493  };
494
495  // Method the get active items which are not disabled and visible
496  Wookmark.prototype.getActiveItems = function () {
497    var inactiveClass = this.inactiveClass,
498      i,
499      result = [],
500      item,
501      items = this.items;
502
503    if (this.ignoreInactiveItems) {
504      for (i = 0; i < items.length; i++) {
505        item = items[i];
506        if (!hasClass(item, inactiveClass)) {
507          result.push(item);
508        }
509      }
510    }
511    return result;
512  };
513
514  // Method to get the standard item width
515  Wookmark.prototype.getItemWidth = function () {
516    var itemWidth = this.itemWidth,
517      innerWidth = getWidth(this.container) - 2 * this.outerOffset,
518      flexibleWidth = this.flexibleWidth;
519
520    if (typeof itemWidth === 'function') {
521      itemWidth = this.itemWidth();
522    }
523
524    if (this.items.length > 0 && (itemWidth === undefined || (itemWidth === 0 && !this.flexibleWidth))) {
525      itemWidth = getWidth(this.items[0]);
526    } else if (typeof itemWidth === 'string' && itemWidth.indexOf('%') >= 0) {
527      itemWidth = parseFloat(this.itemWidth) / 100 * innerWidth;
528    }
529
530    // Calculate flexible item width if option is set
531    if (flexibleWidth) {
532      if (typeof flexibleWidth === 'function') {
533        flexibleWidth = flexibleWidth();
534      }
535
536      if (typeof flexibleWidth === 'string' && flexibleWidth.indexOf('%') >= 0) {
537        flexibleWidth = parseFloat(flexibleWidth) / 100 * innerWidth;
538      }
539
540      // Find highest column count
541      var paddedInnerWidth = (innerWidth + this.offset),
542        flexibleColumns = Math.floor(0.5 + paddedInnerWidth / (flexibleWidth + this.offset)),
543        fixedColumns = Math.floor(paddedInnerWidth / (itemWidth + this.offset)),
544        columns = Math.max(flexibleColumns, fixedColumns),
545        columnWidth = Math.min(flexibleWidth, Math.floor((innerWidth - (columns - 1) * this.offset) / columns));
546
547      itemWidth = Math.max(itemWidth, columnWidth);
548    }
549
550    return itemWidth;
551  };
552
553  // Main layout method.
554  Wookmark.prototype.layout = function (force, callback) {
555    // Do nothing if container isn't visible
556    if (isHidden(this.container)) { return; }
557
558    // Calculate basic layout parameters.
559    var calculatedItemWidth = this.getItemWidth(),
560      columnWidth = calculatedItemWidth + this.offset,
561      containerWidth = getWidth(this.container),
562      innerWidth = containerWidth - 2 * this.outerOffset,
563      columns = Math.floor((innerWidth + this.offset) / columnWidth),
564      offset = 0,
565      maxHeight = 0,
566      activeItems = this.getActiveItems(),
567      activeItemsLength = activeItems.length,
568      item,
569      i = activeItemsLength;
570
571    // Cache item heights
572    if (this.itemHeightsDirty || !this.itemHeightsInitialized) {
573      while (i--) {
574        item = activeItems[i];
575
576        if (this.flexibleWidth) {
577          // Stretch items to fill calculated width
578          item.style.width = calculatedItemWidth + 'px';
579        }
580        setData(item, 'height', item.offsetHeight);
581      }
582      this.itemHeightsDirty = false;
583      this.itemHeightsInitialized = true;
584    }
585
586    // Use less columns if there are to few items
587    columns = Math.max(1, Math.min(columns, activeItemsLength));
588
589    // Calculate the offset based on the alignment of columns to the parent container
590    offset = this.outerOffset;
591    if (this.align === 'center') {
592      offset += Math.floor(0.5 + (innerWidth - (columns * columnWidth - this.offset)) >> 1);
593    }
594
595    // Get direction for positioning
596    this.direction = this.direction || (this.align === 'right' ? 'right' : 'left');
597
598    // If container and column count hasn't changed, we can only update the columns.
599    if (!force && this.columns !== null && this.columns.length === columns && this.activeItemCount === activeItemsLength) {
600      maxHeight = this.layoutColumns(columnWidth, offset);
601    } else {
602      maxHeight = this.layoutFull(columnWidth, columns, offset);
603    }
604    this.activeItemCount = activeItemsLength;
605
606    // Set container height to height of the grid.
607    this.container.style.height = maxHeight + 'px';
608
609    // Update placeholders
610    if (this.fillEmptySpace) {
611      this.refreshPlaceholders(columnWidth, offset);
612    }
613
614    if (this.onLayoutChanged !== undefined && typeof this.onLayoutChanged === 'function') {
615      this.onLayoutChanged();
616    }
617
618    // Run optional callback
619    if (typeof callback === 'function') {
620      callback();
621    }
622  };
623
624  // Sort elements with configurable comparator
625  Wookmark.prototype.sortElements = function (elements) {
626    return typeof this.comparator === 'function' ? elements.sort(this.comparator) : elements;
627  };
628
629  // Perform a full layout update.
630  Wookmark.prototype.layoutFull = function (columnWidth, columns, offset) {
631    var item, k = 0, i = 0, activeItems, activeItemCount, shortest = null, shortestIndex = null,
632      sideOffset, heights = [], itemBulkCSS = [], leftAligned = this.align === 'left';
633
634    this.columns = [];
635
636    // Sort elements before layouting
637    activeItems = this.sortElements(this.getActiveItems());
638    activeItemCount = activeItems.length;
639
640    // Prepare arrays to store height of columns and items.
641    while (heights.length < columns) {
642      heights.push(this.outerOffset);
643      this.columns.push([]);
644    }
645
646    // Loop over items.
647    while (i < activeItemCount) {
648      item = activeItems[i];
649
650      // Find the shortest column.
651      shortest = heights[0];
652      shortestIndex = 0;
653      for (k = 0; k < columns; k++) {
654        if (heights[k] < shortest) {
655          shortest = heights[k];
656          shortestIndex = k;
657        }
658      }
659      setData(item, 'top', shortest);
660
661      // stick to left side if alignment is left and this is the first column
662      sideOffset = offset;
663      if (shortestIndex > 0 || !leftAligned) {
664        sideOffset += shortestIndex * columnWidth;
665      }
666
667      // Position the item.
668      itemBulkCSS[i] = {
669        el: item,
670        css: {
671          position: 'absolute',
672          top: shortest + 'px'
673        }
674      };
675      itemBulkCSS[i].css[this.direction] = sideOffset + 'px';
676
677      // Update column height and store item in shortest column
678      heights[shortestIndex] += getData(item, 'height', true) + this.verticalOffset;
679      this.columns[shortestIndex].push(item);
680      i++;
681    }
682
683    bulkUpdateCSS(itemBulkCSS);
684
685    // Return longest column
686    return Math.max.apply(Math, heights);
687  };
688
689  // This layout method only updates the vertical position of the
690  // existing column assignments.
691  Wookmark.prototype.layoutColumns = function (columnWidth, offset) {
692    var heights = [], itemBulkCSS = [], k = 0, j = 0,
693      i = this.columns.length, currentHeight,
694      column, item, sideOffset;
695
696    while (i--) {
697      currentHeight = this.outerOffset;
698      heights.push(currentHeight);
699      column = this.columns[i];
700      sideOffset = i * columnWidth + offset;
701
702      for (k = 0; k < column.length; k++, j++) {
703        item = column[k];
704        setData(item, 'top', currentHeight);
705        itemBulkCSS[j] = {
706          el: item,
707          css: {
708            top: currentHeight + 'px'
709          }
710        };
711        itemBulkCSS[j].css[this.direction] = sideOffset + 'px';
712
713        currentHeight += getData(item, 'height', true) + this.verticalOffset;
714      }
715      heights[i] = currentHeight;
716    }
717
718    bulkUpdateCSS(itemBulkCSS);
719
720    // Return longest column
721    return Math.max.apply(Math, heights);
722  };
723
724  // Clear event listeners and time outs and the instance itself
725  Wookmark.prototype.clear = function () {
726    clearTimeout(this.resizeTimer);
727    var i = this.placeholders.length;
728    while (i--) {
729      this.container.removeChild(this.placeholders[i]);
730    }
731    removeEventListener(window, 'resize', this.onResize);
732    removeEventListener(this.container, 'refreshWookmark', this.onRefresh);
733  };
734
735  // Register as jQuery plugin if jQuery is loaded
736  if (window.jQuery !== undefined) {
737    jQuery.fn.wookmark = function (options) {
738      var i = this.length;
739
740      // Use first element if container is an jQuery object
741      if (options !== undefined && options.container instanceof jQuery) {
742        options.container = options.container[0];
743      }
744
745      // Call plugin multiple times if there are multiple elements selected
746      if (i > 1) {
747        while (i--) {
748          this[i].wookmark(options);
749        }
750      } else if (i === 1) {
751        // Create a wookmark instance or update an existing one
752        if (!this.wookmarkInstance) {
753          this.wookmarkInstance = new Wookmark(this[0], options || {});
754        } else {
755          this.wookmarkInstance.update(options || {});
756        }
757      }
758      return this;
759    };
760  }
761
762  window.Wookmark = Wookmark;
763  return Wookmark;
764}));

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.