PageSourceSearch

https://www.designerpages.com/assets/backbone/views/search/main-se…3a9d23897c95a306356299c115140e7c12fe2edd.js

js designerpages.com collected 2026-10-02 05:03:06 UTC 32,771 bytes, 897 lines download raw bytes

1DP.Views.Search.MainSearchView = Backbone.View.extend({
2
3  events : {
4    'focus .main-search-input'            : 'recentSearches',
5    'keyup .main-search-input'            : 'autocomplete',
6    'click .js-main-search-ai-toggle'     : 'toggleAi',
7    'click .js-main-search-ai-upload-btn' : 'openAiFilePicker',
8    'change .js-main-search-ai-file-input': 'onAiFileSelected',
9    'click .js-main-search-ai-analyze-btn': 'analyzeAiImage',
10    'keydown .js-main-search-ai-url-input': 'onAiUrlKeydown',
11    'click .js-main-search-ai-clarification-option': 'onAiClarificationOption',
12    'click .js-main-search-ai-new-search' : 'startNewAiSearch',
13    'click .js-tab-products'              : 'productTab',
14    'click .js-tab-folders'               : 'foldersTab',
15    'click .js-tab-vendors'               : 'vendorsTab',
16    'click .vendor-login-small-icon'      : 'openVendorHub',
17    'mouseenter .search_collapse_wrapper' : 'resetSearchCollapse',
18    'mouseenter .vendor-login-small-icon' : 'displayVendorLabel',
19    'mouseleave .vendor-login-small-icon' : 'hideVendorLabel'
20  },
21
22  AI_PLACEHOLDER: 'Search by visual traits (e.g., chrome sled base, wood arms...)',
23
24  AI_IMAGE_MIME_TYPES: ['image/jpeg', 'image/png', 'image/webp'],
25
26  /** PixelSeek rejects decoded uploads over 5 MiB / 40 MP — downscale before sending. */
27  AI_IMAGE_MAX_DIMENSION: 1600,
28
29  initialize: function(options) {
30    var _this             = this;
31    var templateID        = $('#main-search-template').html();
32 
33    _this.isGlassHover    = options.isGlassHover;
34    _this.enableButtons   = false;
35    _this.enableLeftRight = false;
36    _this.useAi           = false;
37    _this.aiImageFile     = null;
38    _this.lastPixelSeekPayload = null;
39    _this.pixelSeekRequestSeq = 0;
40    _this.autocompleteRequestSeq = 0;
41    /** Debounce delay (ms) before GET /autocompletes — avoids one request per keystroke */
42    _this.autocompleteDebounceMs    = 800;
43    _this.autocompleteDebounceTimer = null;
44    _this.template        = _.template(templateID);
45  },
46
47  render: function() {
48    $(this.el).html( this.template() );
49    if (this.isGlassHover) {
50      $(this.el).addClass('main-search-glass-hover');
51    }
52
53    this.initEvents();
54    this.listen();
55  },
56
57  initEvents : function() {
58    var _this               = this;
59    var searchInput         = _this.$el.find('.main-search-input');
60    var searchWrapper       = _this.$el.find('.search_wrapper');
61    var recentSearchWrapper = _this.$el.find('.recent_search_wrapper');
62    var quickKeys           = _this.$el.find('.quick_keys_wrapper');
63    var fullSearchWrapper   = _this.$el.find('.full_search_wrapper');
64    var autoCompleteSection = document.getElementById("autocomplete-section");
65    
66    if( _this.isGlassHover ) {
67      _this.$el.find('.vendor-login-small-icon').addClass('short');
68      _this.$el.find('.vendor-login-small-label').addClass('short');
69      $('.stub-button').addClass('is-short');
70      $('.header.header-full').find('.bottom').find('ul.browse').css('left', '25%');
71      _this.$el.find('.search_collapse_wrapper').addClass('active');
72    } else {
73      searchWrapper.addClass('display');
74    }
75    
76    // Adding action to show or hide search extra options when the user uses keys cmd + k
77    document.addEventListener('keydown', (event) => {
78      let isKKey = event.key === "k" || event.key === "K";
79      let isMetaKey = event.metaKey;
80
81      if (isMetaKey && isKKey) {
82        $(autoCompleteSection).addClass('autocomplete-opacity-main-search');
83        document.body.style.setProperty('overflow', 'hidden'); 
84        if ( $(searchInput).hasClass('active') ) {
85          $(searchInput).blur();
86          $(searchInput).removeClass('active');
87          $(recentSearchWrapper).removeClass('active'); 
88          $(quickKeys).removeClass('active');
89          $(searchWrapper).removeClass('active');
90          $(fullSearchWrapper).removeClass('active');
91          $(searchInput).val('');
92          $(searchInput).css('height', 'auto');
93        } else {
94          $(searchInput).focus();
95          $(searchInput).css('height', 'auto');
96        };
97      };
98    });
99
100    // Hide all serach extra options when the user clicks outside the search wrapper
101    document.addEventListener('click', (e) => {
102      // Use the DOM node (or closest) — composedPath contains Elements, not jQuery objects.
103      var searchWrapperEl = searchWrapper.get(0);
104      var isClickedInsideDiv = (searchWrapperEl && e.composedPath().indexOf(searchWrapperEl) !== -1) ||
105        $(e.target).closest(searchWrapper).length > 0;
106
107      $(autoCompleteSection).addClass('autocomplete-opacity-main-search');
108      document.body.style.setProperty('overflow', 'hidden'); 
109
110      if (!$(e.target).hasClass('main-search-input') && !$(e.target).hasClass('js-search-tab-item') && !isClickedInsideDiv ) {
111        $(searchInput).val('');
112        $(searchInput).removeClass('active');
113        $(recentSearchWrapper).removeClass('active');
114        $(quickKeys).removeClass('active');
115        $(searchWrapper).removeClass('active');
116        $(fullSearchWrapper).removeClass('active');
117        $(searchWrapper).css('height', '10px');
118        $(fullSearchWrapper).html('');
119        $(autoCompleteSection).removeClass('autocomplete-opacity-main-search');
120        document.body.style.setProperty('overflow', 'visible');
121        _this.enableButtons = false;
122        _this.clearAutocompleteDebounce();
123        _this.resetAiMode();
124
125        if( _this.isGlassHover ) {
126          _this.$el.find('.vendor-login-small-icon').addClass('short');
127          _this.$el.find('.vendor-login-small-label').addClass('short');
128        }
129
130        if( !$(e.target).closest('.inner').length && _this.isGlassHover ) {
131          $('.stub-button').addClass('is-short');
132          $('.header.header-full').find('.bottom').find('ul.browse').css('left', '25%');
133          searchWrapper.removeClass('display').fadeOut(300);
134          _this.$el.find('.search_collapse_wrapper').addClass('active').fadeIn(0);
135        } else {
136          /* Clicks hit .browse-cat-button children (text, icon), not the button node — hasClass never matched. */
137          var clickedBrowseChrome = $(e.target).closest('.browse-cat-button, .browse-cat-list, .browse-cat-wrapper').length > 0;
138          if( _this.isGlassHover && clickedBrowseChrome ) {
139            /* Keep collapsed three-icon bar; categories open from Browse without expanding main search. */
140          } else {
141            searchWrapper.fadeIn(0).addClass('display');
142          }
143        }
144      };
145    });
146
147    const osIcon = _this.$el.find('.icon-os');
148    const currentOs = _this.operatingS();
149
150    $(osIcon).addClass(currentOs === "Windows" ? "win" : "mac");
151  },
152
153  clearAutocompleteDebounce : function() {
154    if (this.autocompleteDebounceTimer) {
155      clearTimeout(this.autocompleteDebounceTimer);
156      this.autocompleteDebounceTimer = null;
157    }
158  },
159
160  clearAiImageSelection : function() {
161    this.aiImageFile = null;
162    this.$el.find('.js-main-search-ai-upload-filename').text('');
163    this.$el.find('.js-main-search-ai-file-input').val('');
164  },
165
166  // Clear Visual Search toggle, placeholder, and any pending image when the panel closes.
167  resetAiMode : function() {
168    this.useAi = false;
169    this.clearAiImageSelection();
170    this.lastPixelSeekPayload = null;
171    this.applyAiChrome();
172  },
173
174  applyAiChrome : function() {
175    var searchInput = this.$el.find('.main-search-input');
176    var toggle = this.$el.find('.js-main-search-ai-toggle');
177
178    toggle.toggleClass('active', this.useAi);
179    toggle.attr('aria-pressed', this.useAi ? 'true' : 'false');
180    searchInput.toggleClass('ai-mode', this.useAi);
181
182    if (this.useAi) {
183      searchInput.attr('placeholder', this.AI_PLACEHOLDER);
184    } else {
185      searchInput.removeAttr('placeholder');
186    }
187  },
188
189  renderAiIdlePanel : function() {
190    var fullSearchWrapper = this.$el.find('.full_search_wrapper');
191    var searchWrapper = this.$el.find('.search_wrapper');
192    var quickKeys = this.$el.find('.quick_keys_wrapper');
193    var searchInput = this.$el.find('.main-search-input');
194    var templateHtml = $('#main-search-ai-idle-template').html();
195    if (!templateHtml) { return; }
196
197    fullSearchWrapper.html(_.template(templateHtml)());
198    fullSearchWrapper.addClass('active');
199    searchWrapper.addClass('active').css('height', 'auto');
200    quickKeys.addClass('active');
201    searchInput.addClass('active');
202  },
203
204  renderAiLoadingPanel : function() {
205    var fullSearchWrapper = this.$el.find('.full_search_wrapper');
206    var templateHtml = $('#main-search-ai-loading-template').html();
207    if (!templateHtml) { return; }
208    fullSearchWrapper.html(_.template(templateHtml)());
209    fullSearchWrapper.addClass('active');
210  },
211
212  renderAiResultsPanel : function(options) {
213    var fullSearchWrapper = this.$el.find('.full_search_wrapper');
214    var templateHtml = $('#main-search-ai-results-template').html();
215    if (!templateHtml) { return; }
216
217    options = options || {};
218    fullSearchWrapper.html(_.template(templateHtml)({
219      results: options.results || [],
220      results_url: options.results_url || 'https://pixelseek.com',
221      error: options.error || null,
222      clarification: options.clarification || null,
223      status: options.status || null,
224      message: options.message || null
225    }));
226    fullSearchWrapper.addClass('active');
227  },
228
229  runPixelSeekSearch : function(payload) {
230    var _this = this;
231    var searchWrapper = _this.$el.find('.search_wrapper');
232    var requestSeq = ++_this.pixelSeekRequestSeq;
233
234    // Keep the last image/query payload so a clarification pill can re-call with
235    // the chosen visual_type without re-uploading.
236    _this.lastPixelSeekPayload = $.extend({}, payload);
237    _this.renderAiLoadingPanel();
238
239    DP.Actions.Search.PixelSeek.search(payload, function(response) {
240      if (requestSeq !== _this.pixelSeekRequestSeq) { return; }
241      if (!_this.useAi || !$(searchWrapper).hasClass('active')) { return; }
242
243      if (response && response.error) {
244        // Prefer API `message` when present (e.g. invalid image URL guidance).
245        _this.renderAiResultsPanel({ error: response.message || response.error });
246        return;
247      }
248
249      var clarificationOptions =
250        (response && response.clarification && response.clarification.options) || [];
251      if (clarificationOptions.length > 0) {
252        _this.renderAiResultsPanel({ clarification: response.clarification });
253        return;
254      }
255
256      _this.renderAiResultsPanel({
257        results: (response && response.results) || [],
258        results_url: (response && response.results_url) || 'https://pixelseek.com',
259        status: response && response.status,
260        message: response && response.message
261      });
262    });
263  },
264
265  onAiClarificationOption : function(e) {
266    e.preventDefault();
267    e.stopPropagation();
268
269    var visualType = $(e.currentTarget).data('visual-type');
270    if (!visualType || !this.lastPixelSeekPayload) { return; }
271
272    var payload = $.extend({}, this.lastPixelSeekPayload, { visual_type: visualType });
273    this.runPixelSeekSearch(payload);
274  },
275
276  // Results "New search" — return to the idle upload / paste-URL panel (AI mode stays on).
277  startNewAiSearch : function(e) {
278    e.preventDefault();
279    e.stopPropagation();
280
281    var _this = this;
282    if (!_this.useAi) { return; }
283
284    _this.pixelSeekRequestSeq += 1;
285    _this.clearAiImageSelection();
286    _this.lastPixelSeekPayload = null;
287    _this.$el.find('.main-search-input').val('');
288    _this.renderAiIdlePanel();
289  },
290
291  toggleAi : function(e) {
292    e.preventDefault();
293    e.stopPropagation();
294
295    var _this = this;
296    _this.useAi = !_this.useAi;
297    _this.clearAutocompleteDebounce();
298    _this.clearAiImageSelection();
299    _this.lastPixelSeekPayload = null;
300    _this.applyAiChrome();
301
302    var fullSearchWrapper = _this.$el.find('.full_search_wrapper');
303
304    if (_this.useAi) {
305      _this.recentSearches();
306      _this.renderAiIdlePanel();
307    } else {
308      fullSearchWrapper.html('').removeClass('active');
309    }
310  },
311
312  openAiFilePicker : function(e) {
313    e.preventDefault();
314    e.stopPropagation();
315    this.$el.find('.js-main-search-ai-file-input').trigger('click');
316  },
317
318  onAiFileSelected : function(e) {
319    var file = e.target.files && e.target.files[0];
320    var _this = this;
321
322    if (!file) {
323      _this.clearAiImageSelection();
324      return;
325    }
326
327    if (_this.AI_IMAGE_MIME_TYPES.indexOf(file.type) === -1) {
328      _this.clearAiImageSelection();
329      _this.showAiError('Unsupported image type. Use JPEG, PNG, or WebP.');
330      return;
331    }
332
333    _this.aiImageFile = file;
334    _this.$el.find('.js-main-search-ai-upload-filename').text(file.name);
335  },
336
337  onAiUrlKeydown : function(e) {
338    if (e.key !== 'Enter' && e.keyCode !== 13) { return; }
339
340    e.preventDefault();
341    e.stopPropagation();
342    this.analyzeAiImage(e);
343  },
344
345  analyzeAiImage : function(e) {
346    if (e && e.preventDefault) { e.preventDefault(); }
347    if (e && e.stopPropagation) { e.stopPropagation(); }
348
349    var _this = this;
350    if (!_this.useAi) { return; }
351
352    var imageUrl = $.trim(_this.$el.find('.js-main-search-ai-url-input').val() || '');
353
354    // PixelSeek accepts exactly one of image_url / image_base64.
355    if (_this.aiImageFile && imageUrl) {
356      _this.showAiError('Use either an image URL or an uploaded image, not both.');
357      return;
358    }
359
360    if (_this.aiImageFile) {
361      _this.encodeAiImageFile(_this.aiImageFile, function(err, payload) {
362        if (err) {
363          _this.showAiError('Unable to read the selected image. Please try another file.');
364          return;
365        }
366        _this.runPixelSeekSearch(payload);
367      });
368      return;
369    }
370
371    if (imageUrl) {
372      _this.runPixelSeekSearch({ image_url: imageUrl });
373      return;
374    }
375
376    _this.showAiError('Upload an image or paste an image URL to analyze.');
377  },
378
379  /**
380   * Downscales the file to AI_IMAGE_MAX_DIMENSION and encodes it as raw base64
381   * (no "data:" prefix) — PixelSeek rejects decoded images over 5 MiB / 40 MP.
382   * callback(err, { image_base64, image_mime_type })
383   */
384  encodeAiImageFile : function(file, callback) {
385    var _this = this;
386    var objectUrl = URL.createObjectURL(file);
387    var img = new Image();
388
389    img.onload = function() {
390      URL.revokeObjectURL(objectUrl);
391
392      try {
393        var scale = Math.min(1, _this.AI_IMAGE_MAX_DIMENSION / Math.max(img.width, img.height));
394        var canvas = document.createElement('canvas');
395        canvas.width  = Math.max(1, Math.round(img.width * scale));
396        canvas.height = Math.max(1, Math.round(img.height * scale));
397        canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height);
398
399        // The browser falls back to image/png when it cannot encode the
400        // requested type (e.g. WebP on older Safari), so read the actual mime
401        // type back from the data URL instead of trusting file.type.
402        var dataUrl = canvas.toDataURL(file.type, 0.9);
403        var match = dataUrl.match(/^data:(image\/[a-z+.-]+);base64,(.+)$/);
404        if (!match) {
405          callback(new Error('encode failed'));
406          return;
407        }
408
409        callback(null, { image_base64: match[2], image_mime_type: match[1] });
410      } catch (err) {
411        callback(err);
412      }
413    };
414
415    img.onerror = function() {
416      URL.revokeObjectURL(objectUrl);
417      callback(new Error('image load failed'));
418    };
419
420    img.src = objectUrl;
421  },
422
423  showAiError : function(title) {
424    if (window.dp && dp.utilities && dp.utilities.setFlashNotice) {
425      dp.utilities.setFlashNotice({
426        type: 'error',
427        title: title,
428        content: '',
429        timeout: 4000
430      });
431    }
432  },
433
434  openVendorHub : function(e) {
435    e.stopPropagation();
436    e.preventDefault();
437
438    var _this = this;
439
440    if( !$(e.target).hasClass('public-disabled') ) {
441      if( $(e.target).hasClass('public-enabled') || (window.dp && window.dp.user && window.dp.user.type == "Supplier") ) {
442        window.location.href = "/hub/vendors/";	    
443      } else {
444        window.location.href = "/vendors/";
445      }
446    }
447  },
448
449  displayVendorLabel : function() {
450    var _this = this;
451   
452    _this.$el.find('.vendor-login-small-label').removeClass('hidden').show();
453  },
454
455  hideVendorLabel : function() {
456    var _this = this;
457   
458    _this.$el.find('.vendor-login-small-label').addClass('hidden');
459  },
460
461  resetSearchExpand : function () {
462    // There is call from main.js with mouseleave to execute this code
463    var _this               = this;
464    var searchInput         = _this.$el.find('.main-search-input');
465    var searchWrapper       = _this.$el.find('.search_wrapper');
466    var recentSearchWrapper = _this.$el.find('.recent_search_wrapper');
467    var quickKeys           = _this.$el.find('.quick_keys_wrapper');
468    var fullSearchWrapper   = _this.$el.find('.full_search_wrapper');
469    var autoCompleteSection = document.getElementById("autocomplete-section");
470
471    $('body').click();
472    $(searchInput).val('');
473    $(searchInput).removeClass('active');
474    $(recentSearchWrapper).removeClass('active');
475    $(quickKeys).removeClass('active');
476    $(searchWrapper).removeClass('active');
477    $(fullSearchWrapper).removeClass('active');
478    $(searchWrapper).css('height', '10px');
479    $(fullSearchWrapper).html('');
480    $(autoCompleteSection).removeClass('autocomplete-opacity-main-search');
481    document.body.style.setProperty('overflow', 'visible');
482    _this.enableButtons = false;
483    _this.clearAutocompleteDebounce();
484    _this.resetAiMode();
485
486    $('.stub-button').addClass('is-short');
487    $('.header.header-full').find('.bottom').find('ul.browse').css('left', '25%');
488    searchWrapper.removeClass('display').fadeOut(300);
489    _this.$el.find('.search_collapse_wrapper').addClass('active').fadeIn(0); 
490  },
491
492  resetSearchCollapse : function() {
493    var _this         = this;
494    var searchWrapper = _this.$el.find('.search_wrapper');
495
496    _this.$el.find('.vendor-login-small-icon').removeClass('short');
497    _this.$el.find('.vendor-login-small-label').removeClass('short');
498
499    $('.stub-button').removeClass('is-short');
500    $('.header.header-full').find('.bottom').find('ul.browse').css('left', '');
501    _this.$el.find('.search_collapse_wrapper').removeClass('active');
502    searchWrapper.fadeIn(500).addClass('display');
503  },
504
505  operatingS : function() {
506    let os = undefined
507    if (navigator.appVersion.indexOf('Win') != -1) os = 'Windows';
508    if (navigator.appVersion.indexOf('Mac') != -1) os = 'MacOS';
509    return os;
510  },
511
512  autocomplete : function(e) {
513    var _this               = this;
514    var searchInput         = _this.$el.find('.main-search-input');
515    var searchWrapper       = _this.$el.find('.search_wrapper');
516    var recentSearchWrapper = _this.$el.find('.recent_search_wrapper');
517    var quickKeys           = _this.$el.find('.quick_keys_wrapper');
518    var fullSearchWrapper   = _this.$el.find('.full_search_wrapper');
519
520    var term = $.trim($(searchInput).val());
521
522    if (_this.useAi) {
523      _this.clearAutocompleteDebounce();
524      _this.recentSearches();
525
526      if (e.key === 'Enter' || e.keyCode === 13) {
527        e.preventDefault();
528        if (term.length > 0) {
529          _this.clearAiImageSelection();
530          _this.runPixelSeekSearch({ query: term });
531        }
532        return;
533      }
534
535      // Keep idle panel visible while typing visual traits (unless already showing results/loading).
536      if (!fullSearchWrapper.find('.js-main-search-ai-panel').length) {
537        _this.renderAiIdlePanel();
538      }
539      return;
540    }
541
542    if (term.length <= 1) {
543      _this.clearAutocompleteDebounce();
544      return;
545    }
546
547    _this.recentSearches();
548    $(fullSearchWrapper).addClass('active');
549    $(quickKeys).addClass('active');
550    $(searchInput).addClass('active');
551
552    if (e.key === 'Enter' || e.keyCode === 13) {
553      _this.clearAutocompleteDebounce();
554      e.preventDefault();
555      if ($('.full_search_item.hover', $('.full_search_tab.active')).length > 0) {
556        var urlEnter = $('.full_search_item.hover', $('.full_search_tab.active')).attr('href');
557        window.location.href = urlEnter;
558      } else if ($('.recent_search_item.hover', $('.full_search_tab.active')).length) {
559        var urlRecent = $('.recent_search_item.hover', $('.full_search_tab.active')).attr('href');
560        window.location.href = urlRecent;
561      } else {
562        var urlFooter = $('.active_search_footer', $('.full_search_tab.active')).attr('href');
563        if (urlFooter == undefined) {
564          window.location.href = '/search/products?query=' + encodeURIComponent(term);
565        } else {
566          window.location.href = urlFooter;
567        }
568      }
569      return;
570    }
571
572    if (e.keyCode === 40 || e.keyCode === 38 || e.keyCode === 39 || e.keyCode === 37) {
573      return;
574    }
575
576    e.preventDefault();
577
578    _this.clearAutocompleteDebounce();
579    _this.autocompleteDebounceTimer = setTimeout(function() {
580      _this.autocompleteDebounceTimer = null;
581      var t = $.trim($(searchInput).val());
582      if (t.length <= 1) {
583        return;
584      }
585      if (!$(searchWrapper).hasClass('active')) {
586        return;
587      }
588      if (_this.useAi) {
589        return;
590      }
591      var requestSeq = ++_this.autocompleteRequestSeq;
592      _this.enableButtons = false;
593      // Show immediate feedback while the server request is in flight.
594      $(fullSearchWrapper).html("<div class='main-search-loading'>Searching results...</div>");
595      DP.Actions.Autocompletes.index(t, function(response) {
596        // Ignore stale responses if a newer request was started.
597        if (requestSeq !== _this.autocompleteRequestSeq) { return; }
598        if ($(searchWrapper).hasClass('active') && !_this.useAi) {
599          var resultsTemplate = _.template($('#main-search-results-template').html(), { results: response, term: $(searchInput).val() });
600          $(fullSearchWrapper).html(resultsTemplate);
601          $(fullSearchWrapper).addClass('active');
602          $(quickKeys).addClass('active');
603          $(searchInput).addClass('active');
604          _this.enableButtons = true;
605          $('.js-tooltip-trigger').dpTooltipTrigger();
606        }
607      });
608    }, _this.autocompleteDebounceMs);
609  },
610
611  removeKeyboardState : function(e) {
612     $(".full_search_item").removeClass('hover');
613     $('.active_search_footer').removeClass('hover');
614     $(".recent_search_category").removeClass('hover');
615     $(".recent_search_brand").removeClass('hover');
616  },
617
618  resetSearch : function(e) {
619    var _this               = this;
620    var searchInput         = _this.$el.find('.main-search-input');
621    var searchWrapper       = _this.$el.find('.search_wrapper');
622    var recentSearchWrapper = _this.$el.find('.recent_search_wrapper');
623    var quickKeys           = _this.$el.find('.quick_keys_wrapper');
624    var fullSearchWrapper   = _this.$el.find('.full_search_wrapper');
625    var autoCompleteSection = document.getElementById("autocomplete-section");
626
627    $(searchInput).val('');
628    $(searchInput).removeClass('active');
629    $(recentSearchWrapper).removeClass('active');
630    $(quickKeys).removeClass('active');
631    $(searchWrapper).removeClass('active');
632    $(fullSearchWrapper).removeClass('active');
633    $(searchWrapper).css('height', '10px');
634    $(fullSearchWrapper).html('');
635    $(autoCompleteSection).removeClass('autocomplete-opacity-main-search');
636    document.body.style.setProperty('overflow', 'visible');
637    _this.enableButtons = false;
638    _this.clearAutocompleteDebounce();
639    _this.resetAiMode();
640  },
641
642  recentSearches : function(e) {
643    var _this               = this;
644    var searchInput         = _this.$el.find('.main-search-input');
645    var searchWrapper       = _this.$el.find('.search_wrapper');
646    var recentSearchWrapper = _this.$el.find('.recent_search_wrapper');
647    var quickKeys           = _this.$el.find('.quick_keys_wrapper');
648    var fullSearchWrapper   = _this.$el.find('.full_search_wrapper');
649      
650    $(searchInput).addClass('active');
651    $(searchWrapper).css("height", "auto");
652    
653    if( !$(fullSearchWrapper).hasClass("active") ) {
654      //$(recentSearchWrapper).addClass('active');
655    }
656
657    $(quickKeys).addClass('active');
658    $(searchWrapper).addClass('active');
659
660    if (_this.useAi && !$.trim(fullSearchWrapper.html())) {
661      _this.renderAiIdlePanel();
662    }
663
664    if( e != undefined ) { 
665      e.preventDefault();
666      e.stopPropagation();
667    }
668  },
669
670  productTab : function(e) {
671    e.preventDefault();
672
673    var _this = this;
674
675    _this.$el.find('.js-tab-folders').removeClass('active');
676    _this.$el.find('.js-tab-vendors').removeClass('active');
677    
678    _this.$el.find('.js-tab-products').addClass('active');
679
680    _this.$el.find('.full_search_tab').removeClass('active');
681
682    _this.$el.find('.product_tab').addClass('active');
683
684    _this.$el.find('.js-full-search-extra').addClass('active');
685  },
686
687  foldersTab : function(e) {
688    e.preventDefault();
689    
690    var _this = this;
691    
692    _this.$el.find('.js-tab-products').removeClass('active');
693    _this.$el.find('.js-tab-vendors').removeClass('active');
694    
695    _this.$el.find('.js-tab-folders').addClass('active');
696    
697    _this.$el.find('.full_search_tab').removeClass('active');
698    
699    _this.$el.find('.folders_tab').addClass('active');
700
701    _this.$el.find('.js-full-search-extra').removeClass('active');
702  },
703 
704  vendorsTab : function(e) {
705    e.preventDefault();
706    
707    var _this = this;
708    
709    _this.$el.find('.js-tab-folders').removeClass('active');
710    _this.$el.find('.js-tab-products').removeClass('active');
711    
712    _this.$el.find('.js-tab-vendors').addClass('active');
713    
714    _this.$el.find('.full_search_tab').removeClass('active');
715    
716    _this.$el.find('.vendors_tab').addClass('active');
717
718    _this.$el.find('.js-full-search-extra').removeClass('active');
719  },
720
721  "listen" : function() {
722    var autocomplete = this;
723
724    // Add event listeners
725    this.$el.find('.main-search-input').on("keydown", function(ev) {
726      if (autocomplete.useAi) {
727        if (ev.keyCode === 27) {
728          autocomplete.enableLeftRight = false;
729          autocomplete.enableButtons   = false;
730          autocomplete.resetSearch();
731          ev.preventDefault();
732        }
733        if (ev.keyCode === 13 || ev.keyCode === 9) {
734          ev.preventDefault();
735        }
736        ev.stopPropagation();
737        return;
738      }
739
740      if( autocomplete.enableButtons ) {
741        switch(ev.keyCode) {
742          case 8: // Delete arrow
743            autocomplete.enableLeftRight = false;
744            autocomplete.enableButtons   = false;
745            break;
746          case 46: // Delete arrow
747            autocomplete.enableLeftRight = false;
748            autocomplete.enableButtons   = false;
749            ev.preventDefault();
750            break;
751          case 40: // Down arrow
752            autocomplete.next();
753            autocomplete.enableLeftRight = true;
754            ev.preventDefault();
755            break;
756          case 38: // Up arrow
757            if( autocomplete.enableLeftRight ) {
758              autocomplete.prev();
759              ev.preventDefault();
760            }
761            break;
762          case 39: // Right arrow
763            if( autocomplete.enableLeftRight ) {
764              autocomplete.right();
765              ev.preventDefault();
766            }
767            break;
768          case 37: // Left arrow
769            if( autocomplete.enableLeftRight ) {
770              autocomplete.left();
771              ev.preventDefault();
772            }
773            break;
774          case 27: // Escape
775            autocomplete.enableLeftRight = false;
776            autocomplete.enableButtons   = false;
777            autocomplete.resetSearch();
778            ev.preventDefault();
779            break;
780          case 13: // Return
781          case 9: // Tab
782            ev.preventDefault(); // Allow these keys to be ignored until the keyup
783            break;
784        } // End switch
785      }
786      ev.stopPropagation();
787    });
788
789    return this;
790  },
791
792  "next" : function(ev) { // Get the next element relative to the current hovered element (hovered via keyboard or mouse)
793    var $next = $(".full_search_item.hover", $('.full_search_tab.active'));
794
795    if($next.length) {
796      var temporalNext = $next.removeClass("hover").next();
797      if(!temporalNext.length && $next.hasClass('full_search_item')) {
798        $next = $(".active_search_footer", $('.full_search_tab.active')).first().addClass("hover");  
799      } else {
800        $next = temporalNext.addClass("hover");
801      }
802    } else if( $(".active_search_footer", $('.full_search_tab.active')).first().hasClass('hover')) {
803        $(".active_search_footer", $('.full_search_tab.active')).first().removeClass('hover');
804        $next = $(".recent_search_brand", $('.full_search_tab.active')).first().addClass("hover");
805        if(!$next.length) {
806          $next = $(".recent_search_category", $('.full_search_tab.active')).first().addClass("hover");
807          if(!$next.length) {
808            $next = $(".full_search_item", $('.full_search_tab.active')).first().addClass("hover");
809          }
810        }
811    } else if( $(".recent_search_brand.hover", $('.full_search_tab.active')).length ) {
812      $next = $(".recent_search_brand.hover", $('.full_search_tab.active')).removeClass("hover").next();
813      if(!$next.length) {
814        $next = $(".recent_search_category", $('.full_search_tab.active')).first().addClass("hover");
815        if(!$next.length) {
816          $next = $(".full_search_item", $('.full_search_tab.active')).first().addClass("hover");
817        }
818      } else {
819        $next.addClass("hover");
820      }
821    } else if( $(".recent_search_category.hover", $('.full_search_tab.active')).length ) {
822      $next = $(".recent_search_category.hover", $('.full_search_tab.active')).removeClass("hover").next();
823      if(!$next.length) {
824        $next = $(".full_search_item", $('.full_search_tab.active')).first().addClass("hover"); 
825      } else {
826        $next.addClass("hover");
827      }
828    } else if (!$next.length) {
829      $next = $(".full_search_item", $('.full_search_tab.active')).first().addClass("hover");
830    } else {
831      $next.addClass("hover");
832    }
833  },
834
835  "prev" : function(ev) {
836    var $prev = $(".full_search_item.hover", $('.full_search_tab.active'));
837
838    if($prev.length) { 
839      var temporalNext = $prev.removeClass("hover").prev();
840      if(!temporalNext.length && $prev.hasClass('full_search_item')) {
841        $prev = $(".recent_search_item", $('.full_search_tab.active')).last().addClass("hover"); 
842      } else {
843        $prev = temporalNext.addClass("hover");
844      }
845    } else if( $(".recent_search_item.hover", $('.full_search_tab.active')).length ) {
846      $prev = $(".recent_search_item.hover", $('.full_search_tab.active')).removeClass("hover").prev();
847      if($prev.hasClass('brands_search_title') ) {
848        $prev = ""; 
849      }
850      if(!$prev.length) {
851        $prev = $(".active_search_footer", $('.full_search_tab.active')).first().addClass('hover');
852      } else {
853        $prev.addClass("hover");
854      }
855    } else if( $(".active_search_footer", $('.full_search_tab.active')).first().hasClass('hover')) {
856      $(".active_search_footer", $('.full_search_tab.active')).first().removeClass('hover');
857      $prev = $(".full_search_item", $('.full_search_tab.active')).last().addClass("hover");
858    } else if (!$prev.length) {
859      $prev = $(".recent_search_item", $('.full_search_tab.active')).last().addClass("hover"); 
860    } else {
861      $prev.addClass("hover");
862    }
863  },
864
865  "left" : function(ev) { // Get the next element relative to the current hovered element (hovered via keyboard or mouse)
866    var index            = 0;
867    var currentContainer = $(".full_search_tab.active", $('.full_search_wrapper')).removeClass('active');
868     
869    if( currentContainer.data("main-search-index") == 0 ) {
870      index = 2;
871    } else {
872      index = parseInt(currentContainer.data("main-search-index")) - 1;
873    }
874
875    $('.tab_item').removeClass('active');
876
877    var currentTab       = $('.tab_item[data-main-search-tab-index="' + index + '"]').addClass('active');
878    var $next = $('.full_search_tab[data-main-search-index="' + index + '"]').addClass('active');
879  },
880
881  "right" : function(ev) {
882    var index            = 0;
883    var currentContainer = $(".full_search_tab.active", $('.full_search_wrapper')).removeClass('active');
884     
885    if( currentContainer.data("main-search-index") == 2 ) {
886      index = 0;
887    } else {
888      index = parseInt(currentContainer.data("main-search-index")) + 1;
889    }
890
891    $('.tab_item').removeClass('active');
892
893    var currentTab       = $('.tab_item[data-main-search-tab-index="' + index + '"]').addClass('active');
894    var $next = $('.full_search_tab[data-main-search-index="' + index + '"]').addClass('active');
895  }
896
897});

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.