PageSourceSearch

https://www.label-emmaus.co/static/js/app.js?v=204e6a99b7

js label-emmaus.co collected 2026-10-02 05:00:39 UTC 37,021 bytes, 1,136 lines download raw bytes

1const IS_MOBILE = window.innerWidth < 769;
2
3// Initialize library to lazy load images
4function loadLazyImages() {
5  var observer = lozad('.lazy', {
6    rootMargin: '200px 0px',
7    threshold: 0.1,
8    load: function (el) {
9      if (el.getAttribute('data-src')) {
10        el.src = el.getAttribute('data-src');
11      }
12      el.onload = function () {
13        $(el).removeClass('lazy-wait');
14        $(el).removeAttr('data-src');
15        $(el).removeAttr('width');
16        $(el).removeAttr('height');
17      };
18    },
19  });
20  observer.observe();
21}
22
23// Touches à ne pas prendre en compte lors du keyup des champs de recherche
24const UNRELEVANT_KEYUPS = ['ArrowDown', 'ArrowUp', 'Enter'];
25
26/****************************  START Messages  ****************************/
27function openMessageDialog(content, className = 'info', timeoutRemove = undefined, cta = null) {
28  const dialogTemplate = $('#new-message-dialog .robert-dialog');
29  const newDialog = dialogTemplate.clone();
30  newDialog.attr('x-data', `dialog(${timeoutRemove === undefined ? 'true' : 'false'}, ${timeoutRemove ? timeoutRemove : 4000}, true)`);
31
32  newDialog.addClass(className);
33  newDialog.find('p').html(content);
34
35  if (cta && cta.url && cta.label) {
36    newDialog.find('.js-dialog-cta').attr('href', cta.url).text(cta.label).removeAttr('hidden');
37  } else {
38    newDialog.find('.js-dialog-cta').remove();
39  }
40
41  // If the dialog is not closable, we remove the close button
42  if (timeoutRemove !== undefined) {
43    newDialog.find('.js-dialog-close').remove();
44  }
45
46  // If theres more than 2 dialogs, we remove until we have only 2
47  const dialogs = document.querySelectorAll('.robert-dialog');
48  const dialogArray = [];
49  for (let i = 0; i < dialogs.length; i++) {
50    if (dialogs[i].parentElement.id !== 'new-message-dialog') {
51      dialogArray.push(dialogs[i]);
52    }
53  }
54
55  if (dialogArray.length > 2) {
56    const event = new CustomEvent('disappear');
57    dialogArray.slice(0, dialogArray.length - 2).forEach((dialog) => {
58      dialog.dispatchEvent(event);
59    });
60  }
61
62  $('#message-dialog-wrapper').append(newDialog);
63}
64
65function closeMessageDialog(elem, jqueryElem = true) {
66  const event = new CustomEvent('disappear');
67  if (!elem || !elem.length) {
68    return;
69  }
70  if (jqueryElem) {
71    elem[0].dispatchEvent(event);
72  } else {
73    elem.dispatchEvent(event);
74  }
75}
76
77// Display messages from the backend, htmx and axios
78
79// Django messages.constants level tags.
80const DJANGO_LEVEL_TAGS = ['info', 'warning', 'error', 'success'];
81
82function openMessagesDialog(messages) {
83  messages.forEach((message) => {
84    const tags = (message.tags || '').split(' ');
85
86    const intentToken = tags.find((t) => t.startsWith('intent.'));
87    const intent = intentToken ? intentToken.slice('intent.'.length) : tags.find((t) => DJANGO_LEVEL_TAGS.includes(t)) || 'info';
88
89    const classes = [intent];
90    if (tags.includes('dark')) classes.push('dark');
91    if (tags.includes('vertical')) classes.push('vertical');
92    const className = classes.join(' ');
93
94    const isClosable = tags.includes('closable');
95    const duration = tags.find((tag) => tag.includes('duration'));
96    const durationValue = duration ? parseInt(duration.split('.')[1]) : 4000;
97
98    let content = message.message;
99    let cta = null;
100    if (tags.includes('cta')) {
101      try {
102        const payload = JSON.parse(message.message);
103        content = payload.text || '';
104        cta = payload.cta || null;
105      } catch (e) {
106        /* keep raw content */
107      }
108    }
109
110    if (isClosable) {
111      openMessageDialog(content, className, undefined, cta);
112    } else {
113      openMessageDialog(content, className, durationValue, cta);
114    }
115  });
116}
117
118htmx.on('messages', function (e) {
119  if (e.type == 'messages' && e?.detail?.value && e?.detail?.value?.length > 0) {
120    openMessagesDialog(e.detail.value);
121  }
122});
123
124htmx.on('htmx:responseError', function (evt) {
125  // Skip when the server intentionally handles the error itself:
126  // HX-Retarget/HX-Reswap = server provided a swap target,
127  // X-Messages (axios) / HX-Trigger.messages (htmx) = server already
128  // queued user-facing messages (handled by the `messages` event / axios
129  // interceptor below).
130  const xhr = evt?.detail?.xhr;
131  if (xhr?.getResponseHeader('HX-Retarget') || xhr?.getResponseHeader('HX-Reswap') || xhr?.getResponseHeader('X-Messages')) {
132    return;
133  }
134  const hxTrigger = xhr?.getResponseHeader('HX-Trigger');
135  if (hxTrigger) {
136    try {
137      const parsed = JSON.parse(hxTrigger);
138      if (parsed && parsed.messages) return;
139    } catch (_e) {
140      /* HX-Trigger may be a bare event name; fall through */
141    }
142  }
143  openMessageDialog('Une erreur est survenue. Veuillez réessayer.', 'error', 5000);
144});
145
146htmx.on('htmx:sendError', function () {
147  openMessageDialog('Impossible de contacter le serveur. Vérifiez votre connexion.', 'error', 5000);
148});
149
150axios.interceptors.response.use(
151  function (response) {
152    if (response.headers['x-messages']) {
153      const messages = JSON.parse(response.headers['x-messages']);
154      openMessagesDialog(messages);
155    }
156    return response;
157  },
158  function (error) {
159    if (error.response.headers['x-messages']) {
160      const messages = JSON.parse(error.response.headers['x-messages']);
161      openMessagesDialog(messages);
162    }
163    return Promise.reject(error);
164  }
165);
166
167/******************************  END Messages ******************************/
168
169/****************************  START HTMX global listeners  ****************************/
170htmx.on('htmx:beforeSwap', function (evt) {
171  // Allow 422 responses to swap
172  // We treat these as form validation errors
173  if (evt.detail.xhr.status === 422) {
174    evt.detail.shouldSwap = true;
175  }
176});
177/****************************  END HTMX  ****************************/
178
179jQuery(document).ready(function () {
180  if ($('.js-bool-errors').length && window.innerWidth < 992) {
181    const element = document.getElementsByClassName('js-bool-errors')[0];
182    const y = element.getBoundingClientRect().top + window.scrollY - 145;
183    window.scrollTo({ top: y, behavior: 'smooth' });
184  }
185
186  function listenToFooterToggle() {
187    $('.footer-column .column-title.js-toggle-column').on('click', function () {
188      $(this).find('.btn-icon').toggleClass('up');
189      $(this).parents('.footer-column').find('.column-content').slideToggle();
190    });
191  }
192
193  if (IS_MOBILE) {
194    listenToFooterToggle();
195  }
196
197  setSwipers();
198
199  $('.js-read-more-wrapper').each(function () {
200    let content = $(this).find('.js-read-more-content');
201    let readMoreButton = $(this).find('.js-read-more-button');
202    let minHeight = parseFloat($(this).data('min-height')) || 150;
203
204    if (content.height() > minHeight) {
205      content.css('height', minHeight + 'px');
206      content.css('overflow', 'hidden');
207      readMoreButton.show();
208    }
209  });
210
211  $('.js-read-more-button').on('click', function () {
212    let minHeight = parseFloat($(this).parents('.js-read-more-wrapper').data('min-height')) || 150;
213    var content = $(this).parents('.js-read-more-wrapper').find('.js-read-more-content');
214    if ($(this).hasClass('down')) {
215      $(content).animate(
216        {
217          height: minHeight,
218        },
219        300
220      );
221      $(this).removeClass('down').find('.js-label').text('Voir plus');
222    } else {
223      $(content).animate(
224        {
225          height: content.get(0).scrollHeight,
226        },
227        300,
228        function () {
229          $(this).height('auto');
230        }
231      );
232      $(this).addClass('down').find('.js-label').text('Voir moins');
233    }
234  });
235
236  loadLazyImages();
237
238  initProductClickListener();
239
240  /* autocomplete suggestion */
241  var searchInputs = $('.js-completable-search-input');
242  var currentFocus = -1;
243  var currentQuery = null;
244
245  function hideOrShowSuggestionsArea(suggestionArea) {
246    if (suggestionArea.find('.js-search-suggestion').length > 0) {
247      suggestionArea.show();
248    } else {
249      suggestionArea.hide();
250    }
251  }
252
253  var displayDistantQuerySuggestions = debounce(function (suggestionArea, query, page_merchant) {
254    currentQuery = query;
255    if (query.length < 2) {
256      suggestionArea.find('.js-distant-suggestions').html('');
257      return;
258    }
259
260    var url = page_merchant ? URL_AUTOCOMPLETE_MERCHANT_SUGGESTION : URL_AUTOCOMPLETE_SUGGESTION;
261
262    $.ajax({
263      url: url + '?q=' + encodeURIComponent(query),
264      dataType: 'html',
265      type: 'GET',
266      success: function (htmlSuggestions) {
267        if (query == currentQuery && htmlSuggestions) {
268          suggestionArea.find('.js-distant-suggestions').html(htmlSuggestions);
269        }
270      },
271      complete: function (data, status) {
272        hideOrShowSuggestionsArea(suggestionArea);
273        initSearchBarLinkListeners();
274      },
275    });
276  }, 100);
277
278  function displayLocalQuerySuggestions(suggestionArea, alreadyTypedQuery = '') {
279    let localSuggestionArea = suggestionArea.find('.js-local-suggestions');
280    localSuggestionArea.html('');
281
282    let querySuggestions = getQuerySuggestions();
283    const regex = new RegExp(alreadyTypedQuery);
284    let matchingQuerySuggestions = querySuggestions.filter((querySuggestion) => regex.test(querySuggestion.query));
285    if (!matchingQuerySuggestions) return;
286
287    matchingQuerySuggestions.slice(0, 5).forEach((suggestion) => {
288      let parentNamesDom = '';
289      suggestion.categories.forEach((catName, i) => {
290        if (i + 1 == suggestion.categories.length) {
291          parentNamesDom += `<span class="last">${catName}</span>`;
292        } else {
293          parentNamesDom += `<span>${catName}</span=>`;
294        }
295        if (i + 1 < suggestion.categories.length) {
296          parentNamesDom += ' > ';
297        }
298      });
299
300      suggestionQueryEmphasised = suggestion.query.replace(regex, '<b>$&</b>');
301      var urlSuggested = '/fr/catalogue/';
302      var categorySlug = '';
303      if (suggestion.categorySlug) {
304        categorySlug = `&category=${suggestion.categorySlug}`;
305      }
306      let domSugg = $(
307        `<a href="${urlSuggested}?q=${suggestion.query}${categorySlug}" class="js-removable-suggestion-link">
308          <li class="search-suggestion js-search-suggestion">
309            <svg class="svg-previous-search" width="24" height="24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
310              <path fill-rule="evenodd" clip-rule="evenodd" d="M12.5 1.96c-4.12 0-7.8 2.4-9.52 6.03l-.4-1.3a.8.8 0 1 0-1.54.47l.99 3.26a.81.81 0 0 0 .98.55l3.2-.88a.81.81 0 0 0-.43-1.56l-1.45.4a8.9 8.9 0 1 1-.44 5.84.8.8 0 0 0-1.56.4 10.54 10.54 0 0 0 20.7-2.7c0-5.8-4.72-10.51-10.53-10.51Zm-.53 5.07a.8.8 0 0 0-.8.81v5.96a.8.8 0 0 0 .4.7l3.8 2.17a.8.8 0 1 0 .8-1.41l-3.39-1.93V7.84a.8.8 0 0 0-.81-.8Z" fill="currentColor"/>
311            </svg>
312            <div class="content">
313              <div class="query">
314                "${suggestionQueryEmphasised}"
315              </div>
316              <div class="categories">
317                ${parentNamesDom}
318              </div>
319            </div>
320            <div class="js-clear-search-suggestion icon-close"></div>
321          </li>
322        </a>`
323      );
324
325      domSugg.on('click', function (event) {
326        if ($(event.target).hasClass('js-clear-search-suggestion')) {
327          event.preventDefault();
328        } else {
329          let searchEvent = {
330            search_term: suggestion.query,
331            search_category: suggestion.categories[suggestion.categories.length - 1],
332          };
333          data_layer_push.search(searchEvent);
334        }
335      });
336      domSugg.find('.js-clear-search-suggestion').on('click', function () {
337        removeSuggestionInLocalStorage(suggestion);
338        setTimeout(function () {
339          displayLocalQuerySuggestions(suggestionArea, alreadyTypedQuery);
340          hideOrShowSuggestionsArea(suggestionArea);
341        }, 10);
342      });
343
344      localSuggestionArea.append(domSugg);
345    });
346  }
347
348  var displayQuerySuggestions = function (searchContainer) {
349    let page_merchant = searchContainer.data('merchant');
350    let suggestionArea = searchContainer.find('.js-suggestions');
351    let alreadyTypedQuery = searchContainer.find('.js-completable-search-input').val();
352
353    // Le repli mobile vit dans le menu du storefront, que la coquille du tunnel ne rend jamais,
354    // alors que sa barre de recherche s'ouvre dès 992px : sans cette garde les suggestions du
355    // tunnel partiraient dans un conteneur absent entre 992 et 1200px.
356    const mobileSuggestionArea = $('#mobile-search-js-results');
357    if (window.innerWidth <= 1200 && mobileSuggestionArea.length) {
358      suggestionArea = mobileSuggestionArea;
359    }
360
361    if (!page_merchant) {
362      displayLocalQuerySuggestions(suggestionArea, alreadyTypedQuery);
363    }
364    displayDistantQuerySuggestions(suggestionArea, alreadyTypedQuery, page_merchant);
365
366    hideOrShowSuggestionsArea(suggestionArea);
367  };
368
369  searchInputs.on('keyup', function (event) {
370    if (!UNRELEVANT_KEYUPS.includes(event.key)) {
371      let searchContainer = $(this).parents('.js-completable-search-form');
372      displayQuerySuggestions(searchContainer);
373    }
374  });
375
376  searchInputs.on('focus', function (event) {
377    let searchContainer = $(this).parents('.js-completable-search-form');
378    displayQuerySuggestions(searchContainer);
379  });
380
381  function getQuerySuggestions() {
382    let querySuggestions = [];
383    if (localStorage.getItem('query_suggestions')) {
384      querySuggestions = JSON.parse(localStorage.getItem('query_suggestions'));
385    }
386    return querySuggestions;
387  }
388
389  function suggestionsAreEqual(sugg1, sugg2) {
390    if (sugg1.query != sugg2.query) return false;
391    if (typeof sugg1.categories != typeof sugg2.categories) return false;
392    if (sugg1.categories.join('>') != sugg2.categories.join('>')) return false;
393    return true;
394  }
395
396  function removeSuggestionInLocalStorage(suggestion) {
397    let querySuggestions = getQuerySuggestions();
398    let newQuerySuggestions = querySuggestions.filter((querySugg) => !suggestionsAreEqual(querySugg, suggestion));
399    localStorage.setItem('query_suggestions', JSON.stringify(newQuerySuggestions));
400  }
401
402  function saveQueryInLocalStorage(query, categories, categorySlug = null) {
403    let querySuggestions = getQuerySuggestions();
404    let newSugg = {
405      query: query,
406      categories: categories,
407      categorySlug: categorySlug,
408    };
409    let alreadySaved = false;
410    if (querySuggestions.filter((querySugg) => suggestionsAreEqual(querySugg, newSugg)).length == 1) {
411      alreadySaved = true;
412    }
413
414    if (!alreadySaved) {
415      localStorage.setItem('query_suggestions', JSON.stringify([newSugg, ...querySuggestions]));
416    }
417  }
418
419  $('.js-completable-search-form').on('submit', function () {
420    let query = $(this).find('.js-completable-search-input').val();
421
422    let searchEvent = {
423      search_term: query,
424    };
425    data_layer_push.search(searchEvent);
426
427    saveQueryInLocalStorage(query, []);
428  });
429
430  function initFeedbackForm() {
431    // search feedback form display and submission
432    $('.feedback-numbers .number').on('click', function () {
433      $('.feedback-numbers .number').removeClass('clicked');
434      $(this).addClass('clicked');
435      $('.js-feedback-content').css('display', 'flex');
436      $('.js-feedback-rating').css('display', 'flex');
437      $('.js-feedback-comment').css('display', 'flex');
438      $('.js-submit-search-feedback').css('display', 'flex');
439    });
440
441    if (window.innerWidth < 768) {
442      $('.js-user-feedback-form-content').on('click', function () {
443        $(this).addClass('active');
444      });
445    }
446
447    $(document).on('click', function (event) {
448      if (!$(event.target).closest('.js-user-feedback-form').length) {
449        $('.js-feedback-rating').hide();
450        $('.js-feedback-comment').hide();
451        $('.js-submit-search-feedback').hide();
452
453        if ($('.js-user-feedback-form-content').hasClass('active')) {
454          $('.js-user-feedback-form-content').removeClass('active');
455        }
456      }
457    });
458
459    $('.js-feedback-close').on('click', function () {
460      $('.js-user-feedback-form').fadeOut();
461    });
462
463    $('.js-feedback-number').on('click', function () {
464      let selectedNumber = $(this).data('number');
465      $('input[name="rating"]').val(selectedNumber);
466    });
467
468    $('.js-user-feedback-form').on('submit', function (event) {
469      event.preventDefault();
470
471      let comment = $(this).find('textarea[name="comment"]').val();
472      let rating = $(this).find('input[name="rating"]').val();
473
474      $.ajax({
475        url: $(this).attr('action'),
476        type: $(this).attr('method'),
477        data: {
478          comment: comment,
479          rating: rating,
480        },
481        success: function (data) {
482          if (data.status == 'ko') {
483            $('.js-user-feedback-form-content').hide();
484            $('.js-feedback-error').show();
485            setTimeout(function () {
486              $('.js-feedback-error').fadeOut();
487            }, 2000);
488          } else if (data.status == 'ok') {
489            $('.js-user-feedback-form-content').hide();
490            $('.js-feedback-thanks').show();
491            setTimeout(function () {
492              $('.js-user-feedback-form').fadeOut();
493            }, 2000);
494          }
495        },
496        error: function (error) {
497          $('.js-feedback-error').show();
498          setTimeout(function () {
499            $('.js-feedback-error').fadeOut();
500          }, 2000);
501        },
502      });
503    });
504  }
505
506  initFeedbackForm();
507
508  document.body.addEventListener('htmx:afterRequest', function (event) {
509    initFeedbackForm();
510  });
511
512  function initSearchBarLinkListeners() {
513    $('.js-completable-search-form .js-search-suggestion-link').on('click', function () {
514      let query = $(this).data('query');
515      let suggestionType = $(this).data('suggestion-type');
516      let parentNames = $(this).data('category-parents-names');
517      let categorySlug = $(this).data('category-slug');
518      if (suggestionType != 'merchant') {
519        saveQueryInLocalStorage(query, parentNames, categorySlug);
520      }
521
522      let eventAction;
523
524      if (suggestionType == 'merchant') {
525        eventAction = 'Click Merchant Dropdown Proposal';
526      } else if (suggestionType == 'category') {
527        eventAction = 'Query in Category Using Dropdown Proposal';
528      } else {
529        eventAction = 'Query full Catalogue Using Dropdown Proposal';
530      }
531
532      let searchEvent = {
533        search_term: query,
534      };
535
536      if ($(this).data('category-name')) {
537        searchEvent.search_category = $(this).data('category-name');
538      }
539
540      if ($(this).data('merchant-name')) {
541        searchEvent.search_merchant = $(this).data('merchant-name');
542      }
543      data_layer_push.search(searchEvent);
544    });
545  }
546
547  /* keyboard navigation on autocomplete, inspired by https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_autocomplete */
548  searchInputs.on('keydown', function (event) {
549    let searchContainer = $(this).parents('.js-completable-search-form');
550    let suggestionArea = searchContainer.find('.js-suggestions');
551    suggestions = suggestionArea.find('.js-search-suggestion');
552    if (event.key == 'ArrowDown') {
553      if (suggestions) {
554        currentFocus++;
555        addActive(suggestionArea);
556      }
557    } else if (event.key == 'ArrowUp') {
558      currentFocus--;
559      addActive(suggestionArea);
560    } else if (event.key == 'Enter') {
561      if (currentFocus > -1) {
562        event.preventDefault();
563        if (suggestions) suggestions[currentFocus].click();
564      }
565    } else {
566      currentFocus = -1;
567    }
568  });
569
570  function addActive(suggestionArea) {
571    suggestions = suggestionArea.find('.js-search-suggestion');
572    if (!suggestions || suggestions.length === 0) return false;
573    suggestions.removeClass('active');
574
575    if (currentFocus >= suggestions.length) currentFocus = 0;
576    if (currentFocus < 0) currentFocus = suggestions.length - 1;
577    $(suggestions[currentFocus]).addClass('active');
578  }
579
580  initSearchInputListeners();
581
582  $(document).on('click', function (e) {
583    if ($(e.target).parents('.js-completable-search-form').length === 0 && !$(e.target).hasClass('.js-completable-search-form')) {
584      $('.js-completable-search-form').find('.js-suggestions').hide();
585    }
586  });
587
588  $('a.js-delete-alert').on('click', function () {
589    var element = $(this);
590    var url = element.data('url');
591    var alert_id = element.data('id');
592    $.get(url, function () {
593      $('li[data-id=' + alert_id + ']').fadeOut();
594    });
595    return false;
596  });
597
598  // order > register cb
599  $('input[name=register_cb-register_card]').change(function () {
600    $('form.js-form-register-cb').submit();
601  });
602
603  $('form.js-form-register-cb').submit(function (e) {
604    e.preventDefault();
605    var url = $(this).attr('action') + '?' + $(this).serialize();
606    $.get(url);
607    return false;
608  });
609
610  if ($('.js-delete-cb').length) {
611    $('.js-delete-cb').each(function () {
612      var url = $(this).attr('href');
613      $(this).click(function () {
614        $.get(url, function (data) {
615          window.location.reload();
616        });
617        return false;
618      });
619    });
620  }
621
622  // Waiting on slow buttons of filled forms
623  $('.js_waiting_form').click(function () {
624    var form = $(this).closest('form').eq(0)[0];
625    if (form.checkValidity()) {
626      $(this).addClass('waiting');
627    }
628  });
629
630  var clearBtn = $('.js-clear-btn');
631  var addImage = $('.js-add-image');
632
633  function compressImage(file, maxSize) {
634    return new Promise((resolve, reject) => {
635      let inputImage = new Image();
636
637      inputImage.src = URL.createObjectURL(file);
638      inputImage.onload = () => {
639        let c = document.createElement('canvas'),
640          ctx = c.getContext('2d'),
641          compressionValue = 1,
642          compressionStep = 0.1;
643
644        URL.revokeObjectURL(inputImage.src);
645
646        c.width = inputImage.width;
647        c.height = inputImage.height;
648        ctx.drawImage(inputImage, 0, 0);
649
650        function compressionLoop(canvas, currentCompressionValue) {
651          let nextCompressionValue = currentCompressionValue - compressionStep;
652          return function (blob) {
653            if (blob !== null && blob.size >= maxSize && nextCompressionValue > 0) {
654              canvas.toBlob(compressionLoop(canvas, nextCompressionValue), 'image/jpeg', nextCompressionValue);
655            } else if (nextCompressionValue <= 0) {
656              reject();
657            } else {
658              resolve(
659                new File([blob], file.name, {
660                  lastModified: new Date().getTime(),
661                  type: blob.type,
662                })
663              );
664            }
665          };
666        }
667
668        c.toBlob(compressionLoop(c, compressionValue), 'image/jpeg', compressionValue);
669      };
670    });
671  }
672
673  function clearInput(input) {
674    input.replaceWith(input.val('').clone(true));
675  }
676
677  $('.js-file-error').hide();
678
679  $('.js-image-input').bind('change', function () {
680    const file = this.files[0];
681    const oneMo = Math.pow(1024, 2);
682    const maxFileSize = oneMo * 12;
683    const maxCompressedSize = oneMo * 2;
684
685    const errorMessages = {
686      maxSize: 'Le fichier ' + file.name + ' est trop volumineux, il ne doit pas dépasser 12 Mo',
687      badFormat: 'Le fichier ' + file.name + " n'est pas au bon format, il faut un fichier au format .jpg, .jpeg, .png ou .bmp",
688    };
689
690    if (file.size > maxFileSize) {
691      $('.js-file-error').text(errorMessages.maxSize);
692      $('.js-file-error').show();
693      clearInput($(this));
694    } else if (!['jpg', 'jpeg', 'png', 'bmp'].includes(file.name.split('.').pop().toLowerCase())) {
695      $('.js-file-error').text(errorMessages.badFormat);
696      $('.js-file-error').show();
697      clearInput($(this));
698    } else {
699      compressImage(file, maxCompressedSize)
700        .then((compressedImage) => {
701          $('.js-file-error').text('');
702          $('.js-file-error').hide();
703
704          // Create DataTransfer object to change the input value
705          const dataTransfer = new DataTransfer();
706          dataTransfer.items.add(compressedImage);
707          this.files = dataTransfer.files;
708        })
709        .catch(() => {
710          $('.js-file-error').text(errorMessages.maxSize);
711          $('.js-file-error').show();
712          clearInput($(this));
713        });
714    }
715  });
716
717  clearBtn.on('click', function () {
718    imageInput = $(this).siblings('.js-image-input').eq(0);
719    clearInput(imageInput);
720    $(this).parent().addClass('hidden');
721    if ($('.broken-form-images').find('.hidden').length > 0) {
722      addImage.removeClass('hidden');
723    }
724  });
725
726  addImage.on('click', function () {
727    $('.broken-form-images').find('.hidden').first().removeClass('hidden');
728    if ($('.broken-form-images').find('.hidden').length == 0) {
729      $(this).addClass('hidden');
730    }
731  });
732
733  // END Page article cassé
734
735  // Page réclamation
736  function checkIfOneSelected() {
737    atLeastOneSelected = $('input[name="is_item_selected"]:checked').length > 0;
738    if (atLeastOneSelected) {
739      $('#js-confirm-complaint').prop('disabled', false);
740      $('#js-confirm-complaint').removeClass('disabled');
741    } else {
742      $('#js-confirm-complaint').prop('disabled', true);
743      $('#js-confirm-complaint').addClass('disabled');
744    }
745  }
746
747  $('.js-whole-order').on('click', function () {
748    if (!$(this).hasClass('checked')) {
749      $(this).addClass('checked');
750      $('input[type=checkbox][name=is_item_selected]').prop('checked', true);
751      $('.js-selectable-item').addClass('selected');
752      $('select[name=selected_items]').prop('disabled', false);
753      $('select[name=selected_items] :last-child').prop('selected', true);
754      $('select[name=selected_items] :nth-child(1)').prop('hidden', true);
755    } else {
756      $(this).removeClass('checked');
757      $('.js-selectable-item').removeClass('selected');
758      $('input[type=checkbox][name=is_item_selected]').prop('checked', false);
759      $('select[name=selected_items]').prop('disabled', true);
760      $('select[name=selected_items] :nth-child(1)').prop('hidden', false);
761      $('select[name=selected_items] :nth-child(1)').prop('selected', true);
762    }
763    checkIfOneSelected();
764  });
765
766  $('.js-quantity-selector').on('click', function (event) {
767    event.stopPropagation();
768  });
769
770  $('.js-selectable-item').on('click', function () {
771    linkedCheckbox = $(this).find('input[type=checkbox]');
772    isChecked = linkedCheckbox.prop('checked');
773
774    linkedSelect = $(this).find('select[name=selected_items]');
775
776    if (isChecked) {
777      linkedCheckbox.prop('checked', false);
778      $(this).removeClass('selected');
779
780      linkedSelect.prop('disabled', true);
781      linkedSelect.find(':nth-child(1)').prop('selected', true);
782      linkedSelect.find(':nth-child(1)').prop('hidden', false);
783    } else {
784      linkedCheckbox.prop('checked', true);
785      $(this).addClass('selected');
786
787      linkedSelect.find(':nth-child(1)').prop('hidden', true);
788      linkedSelect.find(':nth-child(2)').prop('selected', true);
789      linkedSelect.prop('disabled', false);
790    }
791    checkIfOneSelected();
792  });
793
794  $('input[type=checkbox][name=is_item_selected]').change(function () {
795    checkIfOneSelected();
796  });
797
798  $('input[type=radio][name=complaint-type]').change(function () {
799    if (['retractation', 'casse', 'nonconforme'].includes(this.value)) {
800      $('.js-item-selection').show();
801      checkIfOneSelected();
802    } else {
803      $('.js-item-selection').hide();
804      $('#js-confirm-complaint').prop('disabled', false);
805      $('#js-confirm-complaint').removeClass('disabled');
806    }
807  });
808  // END Page réclamation
809
810  initToggleContentListener();
811});
812
813function readCookie(name) {
814  var nameEQ = name + '=';
815  var ca = document.cookie.split(';');
816  for (var i = 0; i < ca.length; i++) {
817    var c = ca[i];
818    while (c.charAt(0) == ' ') c = c.substring(1, c.length);
819    if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
820  }
821  return null;
822}
823
824let fade = {
825  opacity: 0,
826  transition: 'opacity 400ms',
827};
828
829function debounce(func, wait, immediate) {
830  var timeout;
831  return function () {
832    var context = this,
833      args = arguments;
834    var later = function () {
835      timeout = null;
836      if (!immediate) func.apply(context, args);
837    };
838    var callNow = immediate && !timeout;
839    clearTimeout(timeout);
840    timeout = setTimeout(later, wait);
841    if (callNow) func.apply(context, args);
842  };
843}
844
845const slugify = (str) => {
846  return str
847    .toLowerCase()
848    .trim()
849    .replace(/[^\w\s-]/g, '')
850    .replace(/[\s_-]+/g, '-')
851    .replace(/^-+|-+$/g, '');
852};
853
854function initNetWaveProductSwipers(scope) {
855  let zonesToInitiate = $('.wsbPublishingZone[data-wsb-used!=true]', scope);
856  zonesToInitiate.addClass('waiting-netwave-reco');
857
858  // on donne 1.5 secondes aux recos dont on veut qu'elles apparaissent toujours vite (visibles en haut de page)
859  zonesToInitiate.filter('.max-wait-1500').each(function (index) {
860    setTimeout(() => {
861      stopLoadingZone($(this));
862    }, 1500);
863  });
864}
865
866function setSwipers() {
867  const initalizedSwipers = [];
868  const netwaveZoneIdsAsString = {
869    ZH1: 'Homepage - recommandé pour vous',
870    ZHP3: "Homepage - c'est bradé",
871    ZPU1: 'Univers - recommandé pour vous',
872    ZPU2: "Univers - c'est bradé",
873    ZPU3: 'Univers High-tech - recommandé pour vous',
874    ZPU4: "Univers High-tech - c'est bradé",
875    ZPL2: 'Catalogue - recommandations dans la catégorie',
876    ZP3: 'Fiche produit - sélection de produits similaires',
877    ZP2: 'Fiche produit - derniers articles consultés',
878    ZCL2: 'Layer Panier - recommandé après ajout au panier',
879    ZC1: "Panier - pour compléter l'achat",
880    ZSEV1: 'Catalogue - absence de résultat de recherche',
881    ZERR4: 'Erreur - derniers articles consultés',
882  };
883  netwaveSwiperCount = 0;
884  for (zone in netwaveZoneIdsAsString) {
885    let zoneName = '#wsbPZ_' + zone;
886    if ($(zoneName).length > 0) {
887      netwaveSwiperCount++;
888    }
889  }
890  if (netwaveSwiperCount > 0) {
891    initNetWaveProductSwipers();
892  }
893  if (typeof Swiper != 'undefined') {
894    // Page categ
895    if ($('.categories-swiper').length > 0) {
896      const swiperCategory = new Swiper('.categories-swiper', {
897        slidesPerView: 'auto',
898        spaceBetween: 16,
899        breakpoints: {
900          576: {
901            slidesPerView: 3,
902            slidesPerGroup: 3,
903          },
904          992: {
905            slidesPerView: 4,
906            slidesPerGroup: 4,
907          },
908          1200: {
909            slidesPerView: 5,
910            slidesPerGroup: 5,
911          },
912          1400: {
913            slidesPerView: 6,
914            slidesPerGroup: 6,
915          },
916          1600: {
917            slidesPerView: 7,
918            slidesPerGroup: 7,
919          },
920          1760: {
921            slidesPerView: 8,
922            slidesPerGroup: 8,
923          },
924          1920: {
925            slidesPerView: 9,
926            slidesPerGroup: 9,
927          },
928        },
929        navigation: {
930          nextEl: '.swiper-next-arrow',
931          prevEl: '.swiper-prev-arrow',
932        },
933      });
934      initalizedSwipers.push(swiperCategory);
935    }
936    // Page univers : Inspiration
937    if ($('.inspirations-swiper').length > 0) {
938      const swiperInspiration = new Swiper('.inspirations-swiper', {
939        slidesPerView: 'auto',
940        spaceBetween: 16,
941        centeredSlides: false,
942        slidesPerViewFit: true,
943        breakpoints: {
944          576: {
945            centeredSlides: false,
946            slidesPerView: 3,
947          },
948        },
949        scrollbar: {
950          el: '.swiper-scrollbar',
951          draggable: true,
952          dragSize: 75,
953        },
954      });
955      initalizedSwipers.push(swiperInspiration);
956    }
957
958    /* Wizville swipers */
959    if ($('.desktop-wizville-swiper').length > 0) {
960      const desktopWizvilleSwiper = new Swiper('.desktop-wizville-swiper', {
961        spaceBetween: 32,
962        slidesPerView: 1,
963        slidesPerGroup: 1,
964        navigation: {
965          nextEl: '.swiper-next',
966          prevEl: '.swiper-prev',
967        },
968        scrollbar: {
969          el: '.swiper-scrollbar',
970          draggable: true,
971        },
972        breakpoints: {
973          992: {
974            spaceBetween: 0,
975            slidesPerView: 3,
976            slidesPerGroup: 3,
977          },
978        },
979      });
980      initalizedSwipers.push(desktopWizvilleSwiper);
981    }
982
983    /* Wizville : fiche boutique */
984
985    if ($('.wizville-swiper').length > 0) {
986      const swiperWizville = new Swiper('.wizville-swiper', {
987        spaceBetween: 30,
988        loop: false,
989        centeredSlides: true,
990        scrollbar: {
991          el: '.swiper-scrollbar',
992          draggable: true,
993        },
994        breakpoints: {
995          320: {
996            slidesPerView: 1,
997            spaceBetween: 20,
998          },
999          576: {
1000            slidesPerView: 2,
1001          },
1002        },
1003      });
1004      initalizedSwipers.push(swiperWizville);
1005    }
1006
1007    /* Page institutionnelle : notre impact */
1008    if ($('.impact-page-swiper').length > 0) {
1009      const swiperImpactPage = new Swiper('.impact-page-swiper', {
1010        spaceBetween: 30,
1011        navigation: {
1012          nextEl: '.swiper-next',
1013          prevEl: '.swiper-prev',
1014        },
1015        scrollbar: {
1016          el: '.swiper-scrollbar',
1017          draggable: true,
1018        },
1019      });
1020      initalizedSwipers.push(swiperImpactPage);
1021    }
1022    /* Page institutionnelle : vendre */
1023    if ($('.seller-swiper').length > 0) {
1024      const swiperSeller = new Swiper('.seller-swiper', {
1025        spaceBetween: 30,
1026        navigation: {
1027          nextEl: '.swiper-next',
1028          prevEl: '.swiper-prev',
1029        },
1030        scrollbar: {
1031          el: '.swiper-scrollbar',
1032          draggable: true,
1033        },
1034      });
1035      initalizedSwipers.push(swiperSeller);
1036    }
1037    /* Page institutionnelle : Donner des objets */
1038    if ($('.give-swiper').length > 0) {
1039      const swiperGive = new Swiper('.give-swiper', {
1040        spaceBetween: 30,
1041        navigation: {
1042          nextEl: '.swiper-next',
1043          prevEl: '.swiper-prev',
1044        },
1045        scrollbar: {
1046          el: '.swiper-scrollbar',
1047          draggable: true,
1048        },
1049      });
1050      initalizedSwipers.push(swiperGive);
1051    }
1052    /* Page institutionnelle : Presse */
1053    if ($('.media-swiper').length > 0) {
1054      const swiperMedia = new Swiper('.media-swiper', {
1055        spaceBetween: 30,
1056        navigation: {
1057          nextEl: '.swiper-next',
1058          prevEl: '.swiper-prev',
1059        },
1060      });
1061      initalizedSwipers.push(swiperMedia);
1062    }
1063
1064    /* Page institutionnelle : notre histoire */
1065    if ($('.history-swiper').length > 0) {
1066      const swiperHistory = new Swiper('.history-swiper', {
1067        spaceBetween: 30,
1068        navigation: {
1069          nextEl: '.swiper-next',
1070          prevEl: '.swiper-prev',
1071        },
1072        scrollbar: {
1073          el: '.swiper-scrollbar',
1074          draggable: true,
1075        },
1076      });
1077      initalizedSwipers.push(swiperHistory);
1078    }
1079
1080    /* Swiper marchants à proximité */
1081    if ($('.close-merchants-swiper').length > 0) {
1082      const swiperCloseMerchants = new Swiper('.close-merchants-swiper', {
1083        spaceBetween: 16,
1084        slidesPerView: 'auto',
1085        slidesPerGroupAuto: true,
1086
1087        pagination: {
1088          el: '.swiper-pagination-custom',
1089          clickable: true,
1090          bulletClass: 'swiper-pagination-bullet swiper-pagination-bullet-custom',
1091          bulletActiveClass: 'swiper-pagination-bullet-active bullet-active-custom',
1092        },
1093      });
1094      initalizedSwipers.push(swiperCloseMerchants);
1095    }
1096
1097    /* Similar product load swiper without netwave  */
1098    const similarProductsSwiperDom = $('.similar-products-swiper-wrapper');
1099    if (similarProductsSwiperDom.length > 0 && similarProductsSwiperDom.find('.products-swiper').length > 0) {
1100      initProductSwiper(similarProductsSwiperDom.find('.products-swiper')[0], 'Fiche produit - sélection de produits similaires');
1101    }
1102
1103    // Swiper pour les livres de la même saga
1104    const sameSagaProductsSwiperDom = $('.same-saga-products-swiper-wrapper');
1105    if (sameSagaProductsSwiperDom.length > 0 && sameSagaProductsSwiperDom.find('.products-swiper').length > 0) {
1106      initProductSwiper(sameSagaProductsSwiperDom.find('.products-swiper')[0], 'Fiche produit - livres de la même saga');
1107    }
1108
1109    // Carousel Homepage swiper
1110    const carouselHomepageSwiperDom = $('#carousel-swiper');
1111    if (carouselHomepageSwiperDom.length > 0 && carouselHomepageSwiperDom.find('.products-swiper').length > 0) {
1112      initProductSwiper(carouselHomepageSwiperDom.find('.products-swiper')[0], 'Homepage - Selection de produits');
1113    }
1114  } else {
1115    setTimeout(setSwipers, 300); // try again in 300 milliseconds
1116  }
1117}
1118
1119const copy = () => {
1120  let url_for_list = $('.js-copy-icon').attr('copylink');
1121  if (url_for_list != '') {
1122    let textArea = document.createElement('textarea');
1123    textArea.value = url_for_list;
1124    document.body.appendChild(textArea);
1125    textArea.select();
1126
1127    let successful = document.execCommand('copy');
1128    setTimeout(() => {
1129      openMessageDialog('Lien copié !', 'success', 4000);
1130    }, 100);
1131
1132    document.body.removeChild(textArea);
1133  }
1134};
1135
1136$('.js-copy-icon').on('click', copy);

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.