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.