@bebranded/bb-contents 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bb-contents.js +360 -310
- package/package.json +1 -1
package/bb-contents.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* BeBranded Contents
|
|
3
3
|
* Contenus additionnels français pour Webflow
|
|
4
|
-
* @version 1.1.
|
|
4
|
+
* @version 1.1.2
|
|
5
5
|
* @author BeBranded
|
|
6
6
|
* @license MIT
|
|
7
7
|
* @website https://www.bebranded.xyz
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
(function() {
|
|
10
10
|
'use strict';
|
|
11
11
|
|
|
12
|
+
// Version du script
|
|
13
|
+
const BB_CONTENTS_VERSION = '1.1.2';
|
|
14
|
+
|
|
12
15
|
// Créer l'objet temporaire pour la configuration si il n'existe pas
|
|
13
16
|
if (!window._bbContentsConfig) {
|
|
14
17
|
window._bbContentsConfig = {};
|
|
@@ -31,12 +34,12 @@
|
|
|
31
34
|
}
|
|
32
35
|
window._bbContentsInitialized = true;
|
|
33
36
|
|
|
34
|
-
// Log de démarrage
|
|
35
|
-
console.log('bb-contents |
|
|
37
|
+
// Log de démarrage
|
|
38
|
+
console.log('bb-contents | v' + BB_CONTENTS_VERSION);
|
|
36
39
|
|
|
37
40
|
// Configuration
|
|
38
41
|
const config = {
|
|
39
|
-
version:
|
|
42
|
+
version: BB_CONTENTS_VERSION,
|
|
40
43
|
debug: false, // Debug désactivé pour rendu propre
|
|
41
44
|
prefix: 'bb-', // utilisé pour générer les sélecteurs (data-bb-*)
|
|
42
45
|
youtubeEndpoint: null, // URL du worker YouTube (à définir par l'utilisateur)
|
|
@@ -178,7 +181,7 @@
|
|
|
178
181
|
this.checkAndReinitFailedElements();
|
|
179
182
|
},
|
|
180
183
|
|
|
181
|
-
//
|
|
184
|
+
// Vérifier et réinitialiser les éléments échoués
|
|
182
185
|
checkAndReinitFailedElements: function() {
|
|
183
186
|
const scope = document.querySelector('[data-bb-scope]') || document;
|
|
184
187
|
let needsReinit = false;
|
|
@@ -280,14 +283,14 @@
|
|
|
280
283
|
|
|
281
284
|
// Modules
|
|
282
285
|
bbContents.modules = {
|
|
283
|
-
// Module Marquee
|
|
286
|
+
// Module Marquee
|
|
284
287
|
marquee: {
|
|
285
|
-
|
|
286
|
-
|
|
288
|
+
detect: function(scope) {
|
|
289
|
+
const s = scope || document;
|
|
287
290
|
return s.querySelector(bbContents._attrSelector('marquee')) !== null;
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
init: function(root) {
|
|
291
294
|
const scope = root || document;
|
|
292
295
|
if (scope.closest && scope.closest('[data-bb-disable]')) return;
|
|
293
296
|
const elements = scope.querySelectorAll(bbContents._attrSelector('marquee'));
|
|
@@ -320,20 +323,15 @@
|
|
|
320
323
|
const isVertical = orientation === 'vertical';
|
|
321
324
|
const useAutoHeight = isVertical && height === 'auto';
|
|
322
325
|
|
|
323
|
-
//
|
|
324
|
-
// Si le parent a overflow: visible, on laisse passer
|
|
325
|
-
// Sinon (hidden, clip, auto, scroll), on contient les logos avec overflow: hidden
|
|
326
|
+
// Respecter overflow du parent
|
|
326
327
|
const parentComputedStyle = getComputedStyle(element);
|
|
327
328
|
const parentOverflow = parentComputedStyle.overflow;
|
|
328
329
|
const parentOverflowX = parentComputedStyle.overflowX;
|
|
329
330
|
const parentOverflowY = parentComputedStyle.overflowY;
|
|
330
331
|
|
|
331
|
-
// Vérifier si le parent a explicitement overflow: visible (ou les deux axes)
|
|
332
332
|
const isParentOverflowVisible = (parentOverflow === 'visible' || parentOverflow === '') &&
|
|
333
333
|
(parentOverflowX === 'visible' || parentOverflowX === '') &&
|
|
334
334
|
(parentOverflowY === 'visible' || parentOverflowY === '');
|
|
335
|
-
|
|
336
|
-
// Si le parent a overflow: visible, on laisse passer, sinon on contient avec hidden
|
|
337
335
|
const mainContainerOverflow = isParentOverflowVisible ? 'visible' : 'hidden';
|
|
338
336
|
|
|
339
337
|
mainContainer.style.cssText = `
|
|
@@ -346,7 +344,7 @@
|
|
|
346
344
|
`;
|
|
347
345
|
|
|
348
346
|
const scrollContainer = document.createElement('div');
|
|
349
|
-
//
|
|
347
|
+
// Position relative pour horizontal
|
|
350
348
|
const useRelativeForHorizontal = !isVertical;
|
|
351
349
|
scrollContainer.style.cssText = `
|
|
352
350
|
${useAutoHeight || useRelativeForHorizontal ? 'position: relative;' : 'position: absolute;'}
|
|
@@ -363,8 +361,7 @@
|
|
|
363
361
|
const mainBlock = document.createElement('div');
|
|
364
362
|
mainBlock.innerHTML = originalHTML;
|
|
365
363
|
|
|
366
|
-
//
|
|
367
|
-
// Cela garantit que les images sont dans le cache du navigateur avant le clonage
|
|
364
|
+
// Précharger toutes les images avant clonage
|
|
368
365
|
const preloadAllImagesFirst = function(block) {
|
|
369
366
|
return new Promise(function(resolve) {
|
|
370
367
|
const images = block.querySelectorAll('img');
|
|
@@ -451,14 +448,12 @@
|
|
|
451
448
|
});
|
|
452
449
|
};
|
|
453
450
|
|
|
454
|
-
// Permettre le retour à la ligne
|
|
455
|
-
// Le white-space: nowrap sur le conteneur flex empêche les items de se retourner,
|
|
456
|
-
// mais ne doit pas empêcher le texte à l'intérieur des items de faire plusieurs lignes
|
|
451
|
+
// Permettre le retour à la ligne du texte dans les items
|
|
457
452
|
if (!isVertical) {
|
|
458
453
|
setTimeout(() => {
|
|
459
454
|
const marqueeItems = mainBlock.querySelectorAll('.bb-marquee_item, [role="listitem"]');
|
|
460
455
|
marqueeItems.forEach(item => {
|
|
461
|
-
// Préserver la largeur de l'item
|
|
456
|
+
// Préserver la largeur de l'item
|
|
462
457
|
const computedStyle = getComputedStyle(item);
|
|
463
458
|
const itemWidth = computedStyle.width;
|
|
464
459
|
if (itemWidth && itemWidth !== 'auto' && itemWidth !== '0px') {
|
|
@@ -466,11 +461,10 @@
|
|
|
466
461
|
item.style.width = itemWidth;
|
|
467
462
|
}
|
|
468
463
|
|
|
469
|
-
// Permettre le retour à la ligne
|
|
470
|
-
// Ne pas toucher aux éléments qui doivent garder leur taille auto (comme .tag-m)
|
|
464
|
+
// Permettre le retour à la ligne du texte
|
|
471
465
|
const textContainers = item.querySelectorAll('.use-case_client, .testimonial_client-info, [class*="text"], p, span');
|
|
472
466
|
textContainers.forEach(container => {
|
|
473
|
-
// Exclure les
|
|
467
|
+
// Exclure les tags/badges (taille auto)
|
|
474
468
|
const containerStyle = container.getAttribute('style');
|
|
475
469
|
const shouldPreserveAuto = container.classList.contains('tag-m') ||
|
|
476
470
|
container.classList.contains('tag') ||
|
|
@@ -478,33 +472,23 @@
|
|
|
478
472
|
(containerStyle && containerStyle.includes('width'));
|
|
479
473
|
|
|
480
474
|
if (shouldPreserveAuto) {
|
|
481
|
-
// Ne pas toucher à ces éléments, ils gardent leur taille auto
|
|
482
475
|
return;
|
|
483
476
|
}
|
|
484
477
|
|
|
485
478
|
const containerComputed = getComputedStyle(container);
|
|
486
|
-
// Vérifier si l'élément a un style inline width défini
|
|
487
479
|
const hasInlineWidth = container.style.width && container.style.width !== '';
|
|
488
480
|
|
|
489
|
-
// Si l'élément a une largeur inline définie, la préserver
|
|
490
481
|
if (hasInlineWidth) {
|
|
491
|
-
// Garder la largeur inline
|
|
492
482
|
return;
|
|
493
483
|
}
|
|
494
|
-
|
|
495
|
-
// Si l'élément a une largeur calculée qui n'est pas auto, vérifier si c'est une valeur fixe
|
|
496
|
-
// Sinon, appliquer width: 100% seulement aux conteneurs de texte qui doivent wrapper
|
|
497
484
|
const isTextContainer = container.classList.contains('use-case_client') ||
|
|
498
485
|
container.classList.contains('testimonial_client-info') ||
|
|
499
486
|
container.tagName === 'P' && !container.classList.contains('tag');
|
|
500
487
|
|
|
501
488
|
if (isTextContainer) {
|
|
502
|
-
// Pour les conteneurs de texte principaux, permettre le wrapping
|
|
503
|
-
// Ne pas forcer width si déjà défini
|
|
504
489
|
if (!containerComputed.width || containerComputed.width === 'auto' || containerComputed.width === '0px') {
|
|
505
490
|
container.style.width = '100%';
|
|
506
491
|
}
|
|
507
|
-
// Forcer le retour à la ligne
|
|
508
492
|
container.style.whiteSpace = 'normal';
|
|
509
493
|
container.style.wordWrap = 'break-word';
|
|
510
494
|
container.style.overflowWrap = 'break-word';
|
|
@@ -524,27 +508,22 @@
|
|
|
524
508
|
${isVertical ? 'min-height: 100px;' : ''}
|
|
525
509
|
`;
|
|
526
510
|
|
|
527
|
-
// NOUVELLE APPROCHE: Attendre que TOUTES les images du mainBlock soient chargées AVANT de cloner
|
|
528
|
-
// Cela garantit que les copies héritent d'images déjà dans le cache du navigateur
|
|
529
511
|
preloadAllImagesFirst(mainBlock).then(function() {
|
|
530
|
-
// Maintenant créer les copies - les images sont déjà en cache
|
|
531
512
|
const repeatBlock1 = mainBlock.cloneNode(true);
|
|
532
513
|
const repeatBlock2 = mainBlock.cloneNode(true);
|
|
533
514
|
|
|
534
|
-
// Forcer l'affichage
|
|
515
|
+
// Forcer l'affichage des images dans les copies
|
|
535
516
|
const forceImagesDisplay = function(block) {
|
|
536
517
|
const images = block.querySelectorAll('img');
|
|
537
518
|
images.forEach(function(img) {
|
|
538
519
|
if (img.dataset.src && !img.src) {
|
|
539
520
|
img.src = img.dataset.src;
|
|
540
521
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
// Forcer un reflow pour s'assurer que l'image est rendue
|
|
547
|
-
void img.offsetHeight;
|
|
522
|
+
if (img.src) {
|
|
523
|
+
img.src = img.src;
|
|
524
|
+
img.style.opacity = '1';
|
|
525
|
+
img.style.visibility = 'visible';
|
|
526
|
+
void img.offsetHeight;
|
|
548
527
|
}
|
|
549
528
|
});
|
|
550
529
|
};
|
|
@@ -552,15 +531,14 @@
|
|
|
552
531
|
forceImagesDisplay(repeatBlock1);
|
|
553
532
|
forceImagesDisplay(repeatBlock2);
|
|
554
533
|
|
|
555
|
-
//
|
|
556
|
-
// pour forcer le navigateur à les rendre complètement avant l'animation
|
|
534
|
+
// Pré-rendre les copies hors écran
|
|
557
535
|
const tempContainer = document.createElement('div');
|
|
558
536
|
tempContainer.style.cssText = 'position: absolute; left: -9999px; top: -9999px; visibility: hidden;';
|
|
559
537
|
tempContainer.appendChild(repeatBlock1);
|
|
560
538
|
tempContainer.appendChild(repeatBlock2);
|
|
561
539
|
document.body.appendChild(tempContainer);
|
|
562
540
|
|
|
563
|
-
//
|
|
541
|
+
// Vérifier que toutes les images sont rendues
|
|
564
542
|
const waitForImagesRender = function(block) {
|
|
565
543
|
return new Promise(function(resolve) {
|
|
566
544
|
const images = block.querySelectorAll('img');
|
|
@@ -579,18 +557,14 @@
|
|
|
579
557
|
};
|
|
580
558
|
|
|
581
559
|
images.forEach(function(img) {
|
|
582
|
-
// Vérifier que l'image est vraiment rendue (naturalWidth > 0 ET dans le DOM)
|
|
583
560
|
const checkImage = function() {
|
|
584
561
|
if (img.complete && img.naturalWidth > 0 && img.naturalHeight > 0 && img.offsetWidth > 0) {
|
|
585
562
|
renderedCount++;
|
|
586
563
|
checkRendered();
|
|
587
564
|
} else {
|
|
588
|
-
// Réessayer après un court délai
|
|
589
565
|
setTimeout(checkImage, 10);
|
|
590
566
|
}
|
|
591
567
|
};
|
|
592
|
-
|
|
593
|
-
// Forcer le chargement si nécessaire
|
|
594
568
|
if (img.dataset.src && !img.src) {
|
|
595
569
|
img.src = img.dataset.src;
|
|
596
570
|
}
|
|
@@ -606,7 +580,7 @@
|
|
|
606
580
|
}
|
|
607
581
|
});
|
|
608
582
|
|
|
609
|
-
// Timeout
|
|
583
|
+
// Timeout
|
|
610
584
|
setTimeout(function() {
|
|
611
585
|
if (renderedCount < totalImages) {
|
|
612
586
|
renderedCount = totalImages;
|
|
@@ -616,11 +590,7 @@
|
|
|
616
590
|
});
|
|
617
591
|
};
|
|
618
592
|
|
|
619
|
-
//
|
|
620
|
-
// pour que toutes les parties soient visibles (même brièvement)
|
|
621
|
-
// Cela force le navigateur à rendre même les parties très larges sur grands écrans
|
|
622
|
-
// Pour "left", on force le rendu de la partie DROITE (où les copies apparaîtront)
|
|
623
|
-
// Pour "right", on force le rendu de la partie GAUCHE
|
|
593
|
+
// Forcer le rendu complet (grands écrans)
|
|
624
594
|
const forceFullRender = function() {
|
|
625
595
|
return new Promise(function(resolve) {
|
|
626
596
|
// Calculer la largeur totale des copies
|
|
@@ -630,29 +600,19 @@
|
|
|
630
600
|
);
|
|
631
601
|
|
|
632
602
|
if (totalWidth > 0 && totalWidth > window.innerWidth) {
|
|
633
|
-
// Déplacer temporairement le conteneur pour forcer le rendu
|
|
634
603
|
tempContainer.style.left = '0px';
|
|
635
604
|
tempContainer.style.width = totalWidth + 'px';
|
|
636
605
|
tempContainer.style.overflow = 'visible';
|
|
637
|
-
|
|
638
|
-
// Forcer un reflow pour que le navigateur calcule les dimensions
|
|
639
606
|
void tempContainer.offsetWidth;
|
|
640
|
-
|
|
641
|
-
// NOUVEAU: Pour "left", déplacer pour que la FIN soit visible (partie droite)
|
|
642
|
-
// Pour "right", déplacer pour que le DÉBUT soit visible (partie gauche)
|
|
643
|
-
// On va faire les deux pour être sûr que tout est rendu
|
|
644
607
|
const translateXEnd = Math.max(0, totalWidth - window.innerWidth);
|
|
645
608
|
const translateXStart = 0;
|
|
646
609
|
|
|
647
|
-
// D'abord rendre la fin (pour "left" - où les copies apparaîtront)
|
|
648
610
|
tempContainer.style.transform = 'translateX(-' + translateXEnd + 'px)';
|
|
649
611
|
void tempContainer.offsetWidth;
|
|
650
612
|
requestAnimationFrame(function() {
|
|
651
|
-
// Ensuite rendre le début (pour "right" - où les copies apparaîtront)
|
|
652
613
|
tempContainer.style.transform = 'translateX(-' + translateXStart + 'px)';
|
|
653
614
|
void tempContainer.offsetWidth;
|
|
654
615
|
requestAnimationFrame(function() {
|
|
655
|
-
// Revenir à la position initiale
|
|
656
616
|
tempContainer.style.transform = '';
|
|
657
617
|
tempContainer.style.left = '-9999px';
|
|
658
618
|
tempContainer.style.width = 'auto';
|
|
@@ -663,7 +623,6 @@
|
|
|
663
623
|
});
|
|
664
624
|
});
|
|
665
625
|
} else {
|
|
666
|
-
// Si pas besoin de déplacement, juste attendre un frame
|
|
667
626
|
requestAnimationFrame(function() {
|
|
668
627
|
requestAnimationFrame(resolve);
|
|
669
628
|
});
|
|
@@ -675,14 +634,13 @@
|
|
|
675
634
|
Promise.all([
|
|
676
635
|
waitForImagesRender(repeatBlock1),
|
|
677
636
|
waitForImagesRender(repeatBlock2),
|
|
678
|
-
forceFullRender()
|
|
637
|
+
forceFullRender()
|
|
679
638
|
]).then(function() {
|
|
680
639
|
// Retirer les copies du conteneur temporaire (si toujours présent)
|
|
681
640
|
if (tempContainer && tempContainer.parentNode === document.body) {
|
|
682
641
|
document.body.removeChild(tempContainer);
|
|
683
642
|
}
|
|
684
643
|
|
|
685
|
-
// Maintenant ajouter les copies au scrollContainer
|
|
686
644
|
// Les images sont maintenant complètement rendues
|
|
687
645
|
if (!isVertical) {
|
|
688
646
|
scrollContainer.appendChild(mainBlock);
|
|
@@ -690,10 +648,9 @@
|
|
|
690
648
|
scrollContainer.appendChild(repeatBlock2);
|
|
691
649
|
mainContainer.appendChild(scrollContainer);
|
|
692
650
|
|
|
693
|
-
// Calculer la hauteur maximale des items après ajout au DOM
|
|
694
651
|
requestAnimationFrame(() => {
|
|
695
652
|
requestAnimationFrame(() => {
|
|
696
|
-
const items = mainBlock.querySelectorAll('.bb-marquee_item, [role="listitem"]
|
|
653
|
+
const items = mainBlock.querySelectorAll('.bb-marquee_item, [role="listitem"]');
|
|
697
654
|
let maxHeight = 0;
|
|
698
655
|
items.forEach(function(item) {
|
|
699
656
|
const itemHeight = item.offsetHeight;
|
|
@@ -701,20 +658,15 @@
|
|
|
701
658
|
maxHeight = itemHeight;
|
|
702
659
|
}
|
|
703
660
|
});
|
|
704
|
-
|
|
705
|
-
// Si aucun item trouvé, essayer de prendre la hauteur du scrollContainer
|
|
706
661
|
if (maxHeight === 0) {
|
|
707
662
|
maxHeight = scrollContainer.offsetHeight;
|
|
708
663
|
}
|
|
709
|
-
|
|
710
|
-
// Appliquer la hauteur calculée au mainContainer si elle est valide
|
|
711
664
|
if (maxHeight > 0) {
|
|
712
665
|
mainContainer.style.height = maxHeight + 'px';
|
|
713
666
|
}
|
|
714
667
|
});
|
|
715
668
|
});
|
|
716
669
|
} else {
|
|
717
|
-
// Pour vertical, garder le comportement actuel
|
|
718
670
|
scrollContainer.appendChild(mainBlock);
|
|
719
671
|
scrollContainer.appendChild(repeatBlock1);
|
|
720
672
|
scrollContainer.appendChild(repeatBlock2);
|
|
@@ -725,11 +677,8 @@
|
|
|
725
677
|
element.appendChild(mainContainer);
|
|
726
678
|
element.setAttribute('data-bb-marquee-processed', 'true');
|
|
727
679
|
|
|
728
|
-
// Attendre un peu pour s'assurer que le rendu est complet
|
|
729
|
-
// Les images sont maintenant complètement rendues
|
|
730
680
|
requestAnimationFrame(() => {
|
|
731
681
|
requestAnimationFrame(() => {
|
|
732
|
-
// Maintenant démarrer l'animation
|
|
733
682
|
const initDelay = isVertical ? 500 : 100;
|
|
734
683
|
setTimeout(() => {
|
|
735
684
|
this.initAnimation(element, scrollContainer, mainBlock, {
|
|
@@ -740,12 +689,9 @@
|
|
|
740
689
|
});
|
|
741
690
|
}.bind(this));
|
|
742
691
|
}.bind(this)).catch(function() {
|
|
743
|
-
// En cas d'erreur, nettoyer le tempContainer s'il existe
|
|
744
692
|
if (tempContainer && tempContainer.parentNode === document.body) {
|
|
745
693
|
document.body.removeChild(tempContainer);
|
|
746
694
|
}
|
|
747
|
-
|
|
748
|
-
// Créer les copies quand même et démarrer
|
|
749
695
|
const repeatBlock1 = mainBlock.cloneNode(true);
|
|
750
696
|
const repeatBlock2 = mainBlock.cloneNode(true);
|
|
751
697
|
|
|
@@ -779,11 +725,9 @@
|
|
|
779
725
|
const { speed, direction, pauseOnHover, gap, isVertical, useAutoHeight } = options;
|
|
780
726
|
|
|
781
727
|
// Calculer les dimensions
|
|
782
|
-
// Maintenant que scrollContainer est en position relative pour horizontal, offsetWidth devrait fonctionner
|
|
783
728
|
const contentSize = isVertical ? mainBlock.offsetHeight : mainBlock.offsetWidth;
|
|
784
729
|
|
|
785
730
|
if (contentSize === 0) {
|
|
786
|
-
// Si toujours 0, réessayer après un délai
|
|
787
731
|
setTimeout(() => this.initAnimation(element, scrollContainer, mainBlock, options), 200);
|
|
788
732
|
return;
|
|
789
733
|
}
|
|
@@ -796,33 +740,26 @@
|
|
|
796
740
|
let isPaused = false;
|
|
797
741
|
|
|
798
742
|
if (isSafari) {
|
|
799
|
-
// Solution Safari : Animation CSS avec keyframes
|
|
800
743
|
this.initSafariAnimation(element, scrollContainer, mainBlock, {
|
|
801
744
|
speed, direction, gap, isVertical, useAutoHeight, contentSize, gapSize
|
|
802
745
|
});
|
|
803
746
|
} else {
|
|
804
|
-
|
|
805
|
-
// (elles ont peut-être été créées pour le calcul de hauteur en horizontal)
|
|
806
|
-
// Utiliser children.length au lieu de querySelectorAll pour compter uniquement les enfants directs
|
|
807
|
-
const hasCopies = scrollContainer.children.length >= 3; // mainBlock + 2 copies
|
|
747
|
+
const hasCopies = scrollContainer.children.length >= 3;
|
|
808
748
|
|
|
809
749
|
if (!hasCopies) {
|
|
810
|
-
// Créer les copies maintenant (les navigateurs non-Safari gèrent mieux)
|
|
811
750
|
const repeatBlock1 = mainBlock.cloneNode(true);
|
|
812
751
|
const repeatBlock2 = mainBlock.cloneNode(true);
|
|
813
752
|
|
|
814
|
-
//
|
|
753
|
+
// Précharger les images dans les copies
|
|
815
754
|
const preloadImagesInBlockSync = function(block) {
|
|
816
755
|
const images = block.querySelectorAll('img');
|
|
817
756
|
images.forEach(function(img) {
|
|
818
757
|
if (img.dataset.src && !img.src) {
|
|
819
758
|
img.src = img.dataset.src;
|
|
820
759
|
}
|
|
821
|
-
// Précharger avec new Image() pour forcer le cache
|
|
822
760
|
if (img.src) {
|
|
823
761
|
const preloadImg = new Image();
|
|
824
762
|
preloadImg.src = img.src;
|
|
825
|
-
// Forcer aussi le chargement dans l'image du DOM
|
|
826
763
|
if (!img.complete) {
|
|
827
764
|
img.src = img.src;
|
|
828
765
|
}
|
|
@@ -836,8 +773,6 @@
|
|
|
836
773
|
scrollContainer.appendChild(repeatBlock1);
|
|
837
774
|
scrollContainer.appendChild(repeatBlock2);
|
|
838
775
|
}
|
|
839
|
-
|
|
840
|
-
// Solution standard pour autres navigateurs
|
|
841
776
|
this.initStandardAnimation(element, scrollContainer, mainBlock, {
|
|
842
777
|
speed, direction, pauseOnHover, gap, isVertical, useAutoHeight, contentSize, gapSize, step
|
|
843
778
|
});
|
|
@@ -847,8 +782,7 @@
|
|
|
847
782
|
initSafariAnimation: function(element, scrollContainer, mainBlock, options) {
|
|
848
783
|
const { speed, direction, gap, isVertical, useAutoHeight, contentSize, gapSize } = options;
|
|
849
784
|
|
|
850
|
-
|
|
851
|
-
// SOLUTION SAFARI : Forcer le chargement des images avant animation
|
|
785
|
+
// Précharger les images
|
|
852
786
|
const images = mainBlock.querySelectorAll('img');
|
|
853
787
|
let imagesLoaded = 0;
|
|
854
788
|
const totalImages = images.length;
|
|
@@ -857,9 +791,7 @@
|
|
|
857
791
|
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
|
858
792
|
// Détecter spécifiquement Safari (pas Chrome mobile)
|
|
859
793
|
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent) || /iPhone|iPad|iPod/.test(navigator.userAgent);
|
|
860
|
-
|
|
861
|
-
// OPTIMISATION: Charger les images et appliquer les styles SVG AVANT le clonage
|
|
862
|
-
// pour éviter les reflows qui causent la saccade de l'animation
|
|
794
|
+
// Appliquer les styles avant clonage
|
|
863
795
|
images.forEach(img => {
|
|
864
796
|
if (img.dataset.src && !img.src) {
|
|
865
797
|
img.src = img.dataset.src;
|
|
@@ -868,39 +800,27 @@
|
|
|
868
800
|
|
|
869
801
|
// Détecter si c'est un SVG (par l'extension du src ou le type)
|
|
870
802
|
const isSVG = img.src && (img.src.toLowerCase().endsWith('.svg') || img.src.includes('data:image/svg+xml'));
|
|
871
|
-
|
|
872
|
-
// OPTIMISATION: Préserver les styles CSS existants (object-fit, etc.)
|
|
803
|
+
// Préserver les styles existants
|
|
873
804
|
const originalObjectFit = img.style.objectFit || getComputedStyle(img).objectFit;
|
|
874
805
|
const originalObjectPosition = img.style.objectPosition || getComputedStyle(img).objectPosition;
|
|
875
806
|
const originalWidth = img.style.width;
|
|
876
807
|
const originalHeight = img.style.height;
|
|
877
808
|
|
|
878
809
|
img.onload = () => {
|
|
879
|
-
// SOLUTION SAFARI : Pour les SVG sur Safari (desktop et mobile), utiliser contain avec optimisations
|
|
880
810
|
if (isSVG && isSafari) {
|
|
881
|
-
// SUR SAFARI : Utiliser contain MAIS avec des optimisations pour éviter le flou
|
|
882
811
|
img.style.objectFit = 'contain';
|
|
883
812
|
img.style.objectPosition = 'center';
|
|
884
|
-
|
|
885
|
-
// Contraindre les dimensions sans forcer (max-width/max-height au lieu de width/height 100%)
|
|
886
813
|
img.style.maxWidth = '100%';
|
|
887
814
|
img.style.maxHeight = '100%';
|
|
888
815
|
img.style.boxSizing = 'border-box';
|
|
889
|
-
|
|
890
|
-
// Optimisations pour améliorer le rendu des SVG avec contain
|
|
891
|
-
// Utiliser auto au lieu de crisp-edges pour contain
|
|
892
816
|
img.style.imageRendering = 'auto';
|
|
893
817
|
img.style.webkitBackfaceVisibility = 'hidden';
|
|
894
818
|
img.style.backfaceVisibility = 'hidden';
|
|
895
|
-
|
|
896
|
-
// Forcer le GPU rendering AVANT d'appliquer contain
|
|
897
819
|
img.style.webkitTransform = 'translateZ(0)';
|
|
898
820
|
img.style.transform = 'translateZ(0)';
|
|
899
821
|
|
|
900
|
-
// Conteneur parent pour contraindre et centrer (sans forcer les dimensions)
|
|
901
822
|
const parent = img.parentElement;
|
|
902
823
|
if (parent) {
|
|
903
|
-
// Vérifier si le parent a déjà des dimensions définies
|
|
904
824
|
const parentStyles = getComputedStyle(parent);
|
|
905
825
|
const hasParentWidth = parentStyles.width && parentStyles.width !== 'auto' && parentStyles.width !== '0px';
|
|
906
826
|
const hasParentHeight = parentStyles.height && parentStyles.height !== 'auto' && parentStyles.height !== '0px';
|
|
@@ -911,12 +831,10 @@
|
|
|
911
831
|
parent.style.overflow = 'hidden';
|
|
912
832
|
parent.style.boxSizing = 'border-box';
|
|
913
833
|
|
|
914
|
-
// Ne forcer les dimensions que si le parent n'en a pas déjà
|
|
915
834
|
if (!hasParentWidth && !parent.style.width) parent.style.width = '100%';
|
|
916
835
|
if (!hasParentHeight && !parent.style.height) parent.style.height = '100%';
|
|
917
836
|
}
|
|
918
837
|
} else if (isSVG && isMobile) {
|
|
919
|
-
// Pour Chrome mobile, utiliser contain normalement
|
|
920
838
|
img.style.objectFit = 'contain';
|
|
921
839
|
img.style.objectPosition = 'center';
|
|
922
840
|
img.style.maxWidth = '100%';
|
|
@@ -934,8 +852,6 @@
|
|
|
934
852
|
parent.style.boxSizing = 'border-box';
|
|
935
853
|
}
|
|
936
854
|
} else if (isSafari) {
|
|
937
|
-
// SUR SAFARI : Optimisations GPU et dimensions pour toutes les images
|
|
938
|
-
// Restaurer les styles CSS après chargement pour les non-SVG
|
|
939
855
|
if (originalObjectFit && originalObjectFit !== 'none') {
|
|
940
856
|
img.style.objectFit = originalObjectFit;
|
|
941
857
|
}
|
|
@@ -950,29 +866,22 @@
|
|
|
950
866
|
if (!originalHeight || originalHeight === '') {
|
|
951
867
|
img.style.height = 'auto';
|
|
952
868
|
}
|
|
953
|
-
|
|
954
|
-
// Optimisations GPU pour Safari (desktop et mobile)
|
|
955
869
|
img.style.webkitBackfaceVisibility = 'hidden';
|
|
956
870
|
img.style.backfaceVisibility = 'hidden';
|
|
957
871
|
img.style.webkitTransform = 'translateZ(0)';
|
|
958
872
|
img.style.transform = 'translateZ(0)';
|
|
959
|
-
|
|
960
|
-
// Conteneur parent pour contraindre
|
|
961
873
|
const parent = img.parentElement;
|
|
962
874
|
if (parent) {
|
|
963
875
|
parent.style.overflow = 'hidden';
|
|
964
876
|
parent.style.boxSizing = 'border-box';
|
|
965
877
|
}
|
|
966
878
|
} else {
|
|
967
|
-
// OPTIMISATION: Restaurer les styles CSS après chargement pour les non-SVG (autres navigateurs)
|
|
968
879
|
if (originalObjectFit && originalObjectFit !== 'none') {
|
|
969
880
|
img.style.objectFit = originalObjectFit;
|
|
970
881
|
}
|
|
971
882
|
if (originalObjectPosition && originalObjectPosition !== 'initial') {
|
|
972
883
|
img.style.objectPosition = originalObjectPosition;
|
|
973
884
|
}
|
|
974
|
-
|
|
975
|
-
// OPTIMISATION: Préserver les dimensions naturelles des images
|
|
976
885
|
if (!originalWidth || originalWidth === '') {
|
|
977
886
|
img.style.width = 'auto';
|
|
978
887
|
}
|
|
@@ -1051,9 +960,7 @@
|
|
|
1051
960
|
});
|
|
1052
961
|
}
|
|
1053
962
|
|
|
1054
|
-
//
|
|
1055
|
-
// Cela évite les reflows qui causaient la saccade de l'animation
|
|
1056
|
-
// Les copies héritent automatiquement des styles des images originales
|
|
963
|
+
// Styles appliqués avant clonage
|
|
1057
964
|
|
|
1058
965
|
// Recalculer la taille après chargement des images
|
|
1059
966
|
const newContentSize = isVertical ? mainBlock.offsetHeight : mainBlock.offsetWidth;
|
|
@@ -1072,12 +979,11 @@
|
|
|
1072
979
|
}
|
|
1073
980
|
}
|
|
1074
981
|
|
|
1075
|
-
// Solution Safari simplifiée
|
|
1076
982
|
const totalSize = finalContentSize * 3 + gapSize * 2;
|
|
1077
983
|
const step = (parseFloat(speed) * (isVertical ? 1.5 : 0.8)) / 60;
|
|
1078
984
|
let isPaused = false;
|
|
1079
985
|
|
|
1080
|
-
//
|
|
986
|
+
// Optimisations Safari mobile
|
|
1081
987
|
if (isSafari && isMobile) {
|
|
1082
988
|
scrollContainer.style.willChange = 'transform';
|
|
1083
989
|
scrollContainer.style.webkitBackfaceVisibility = 'hidden';
|
|
@@ -1092,7 +998,7 @@
|
|
|
1092
998
|
}
|
|
1093
999
|
|
|
1094
1000
|
// Position initiale optimisée pour Safari
|
|
1095
|
-
//
|
|
1001
|
+
// Position initiale pour éviter saccade
|
|
1096
1002
|
let currentPosition;
|
|
1097
1003
|
if (direction === (isVertical ? 'bottom' : 'right')) {
|
|
1098
1004
|
currentPosition = -(finalContentSize + gapSize);
|
|
@@ -1107,7 +1013,7 @@
|
|
|
1107
1013
|
: `translate3d(${currentPosition}px, 0, 0)`;
|
|
1108
1014
|
scrollContainer.style.transform = initialTransform;
|
|
1109
1015
|
|
|
1110
|
-
//
|
|
1016
|
+
// Forcer reflow Safari mobile
|
|
1111
1017
|
if (isSafari && isMobile) {
|
|
1112
1018
|
void scrollContainer.offsetHeight;
|
|
1113
1019
|
}
|
|
@@ -1116,7 +1022,7 @@
|
|
|
1116
1022
|
let lastTime = performance.now();
|
|
1117
1023
|
const animate = (currentTime) => {
|
|
1118
1024
|
if (!isPaused) {
|
|
1119
|
-
//
|
|
1025
|
+
// Animation basée sur le temps
|
|
1120
1026
|
const deltaTime = isSafari && isMobile ? (currentTime - lastTime) / 16.67 : 1;
|
|
1121
1027
|
lastTime = currentTime;
|
|
1122
1028
|
|
|
@@ -1183,8 +1089,7 @@
|
|
|
1183
1089
|
let isPaused = false;
|
|
1184
1090
|
|
|
1185
1091
|
// Position initiale
|
|
1186
|
-
//
|
|
1187
|
-
// Cela évite la saccade au premier cycle
|
|
1092
|
+
// Position initiale pour éviter saccade
|
|
1188
1093
|
let currentPosition;
|
|
1189
1094
|
if (direction === (isVertical ? 'bottom' : 'right')) {
|
|
1190
1095
|
currentPosition = -(contentSize + gapSize);
|
|
@@ -1215,7 +1120,6 @@
|
|
|
1215
1120
|
currentPosition += step * clampedDelta;
|
|
1216
1121
|
// Reset BEAUCOUP PLUS TÔT pour "right" aussi (comme pour "left")
|
|
1217
1122
|
// Reset à 80% du chemin au lieu d'attendre 100% pour avoir une marge de sécurité
|
|
1218
|
-
// Cela garantit que la copie suivante est toujours visible avant le reset
|
|
1219
1123
|
const resetThreshold = -(0.2 * (contentSize + gapSize)); // 80% du chemin (on est à -20%)
|
|
1220
1124
|
if (currentPosition >= resetThreshold) {
|
|
1221
1125
|
// Reset en gardant la position relative pour éviter le saut visible
|
|
@@ -1225,7 +1129,6 @@
|
|
|
1225
1129
|
currentPosition -= step * clampedDelta;
|
|
1226
1130
|
// Reset BEAUCOUP PLUS TÔT pour éviter toute saccade visible
|
|
1227
1131
|
// Reset à 80% du chemin au lieu d'attendre 100% pour avoir une marge de sécurité
|
|
1228
|
-
// Cela garantit que la copie suivante est toujours visible avant le reset
|
|
1229
1132
|
const resetThreshold = -(1.8 * (contentSize + gapSize));
|
|
1230
1133
|
if (currentPosition <= resetThreshold) {
|
|
1231
1134
|
// Reset en gardant la position relative pour éviter le saut visible
|
|
@@ -1258,8 +1161,8 @@
|
|
|
1258
1161
|
|
|
1259
1162
|
// Module Share (Partage Social)
|
|
1260
1163
|
share: {
|
|
1261
|
-
|
|
1262
|
-
|
|
1164
|
+
// Configuration des réseaux
|
|
1165
|
+
networks: {
|
|
1263
1166
|
twitter: function(data) {
|
|
1264
1167
|
return 'https://twitter.com/intent/tweet?url=' +
|
|
1265
1168
|
encodeURIComponent(data.url) +
|
|
@@ -1295,14 +1198,14 @@
|
|
|
1295
1198
|
}
|
|
1296
1199
|
},
|
|
1297
1200
|
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1201
|
+
// Détection
|
|
1202
|
+
detect: function(scope) {
|
|
1203
|
+
const s = scope || document;
|
|
1204
|
+
return s.querySelector(bbContents._attrSelector('share')) !== null;
|
|
1205
|
+
},
|
|
1206
|
+
|
|
1207
|
+
// Initialisation
|
|
1208
|
+
init: function(root) {
|
|
1306
1209
|
const scope = root || document;
|
|
1307
1210
|
if (scope.closest && scope.closest('[data-bb-disable]')) return;
|
|
1308
1211
|
const elements = scope.querySelectorAll(bbContents._attrSelector('share'));
|
|
@@ -1349,8 +1252,8 @@
|
|
|
1349
1252
|
bbContents.utils.log('Module Share initialisé:', elements.length, 'éléments');
|
|
1350
1253
|
},
|
|
1351
1254
|
|
|
1352
|
-
|
|
1353
|
-
|
|
1255
|
+
// Fonction de partage
|
|
1256
|
+
share: function(network, data, element) {
|
|
1354
1257
|
const networkFunc = this.networks[network];
|
|
1355
1258
|
|
|
1356
1259
|
if (!networkFunc) {
|
|
@@ -1389,8 +1292,8 @@
|
|
|
1389
1292
|
bbContents.utils.log('Partage sur', network, data);
|
|
1390
1293
|
},
|
|
1391
1294
|
|
|
1392
|
-
|
|
1393
|
-
|
|
1295
|
+
// Copier dans le presse-papier
|
|
1296
|
+
copyToClipboard: function(text, element, silent) {
|
|
1394
1297
|
const isSilent = !!silent;
|
|
1395
1298
|
// Méthode moderne
|
|
1396
1299
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
@@ -1407,8 +1310,8 @@
|
|
|
1407
1310
|
}
|
|
1408
1311
|
},
|
|
1409
1312
|
|
|
1410
|
-
|
|
1411
|
-
|
|
1313
|
+
// Fallback copie
|
|
1314
|
+
fallbackCopy: function(text, element, silent) {
|
|
1412
1315
|
const isSilent = !!silent;
|
|
1413
1316
|
// Pas de UI si silencieux (exigence produit)
|
|
1414
1317
|
if (isSilent) return;
|
|
@@ -1421,8 +1324,8 @@
|
|
|
1421
1324
|
}
|
|
1422
1325
|
},
|
|
1423
1326
|
|
|
1424
|
-
|
|
1425
|
-
|
|
1327
|
+
// Partage natif (Web Share API)
|
|
1328
|
+
nativeShare: function(data, element) {
|
|
1426
1329
|
// Vérifier si Web Share API est disponible
|
|
1427
1330
|
if (navigator.share) {
|
|
1428
1331
|
navigator.share({
|
|
@@ -1443,8 +1346,8 @@
|
|
|
1443
1346
|
}
|
|
1444
1347
|
},
|
|
1445
1348
|
|
|
1446
|
-
|
|
1447
|
-
|
|
1349
|
+
// Feedback visuel
|
|
1350
|
+
showFeedback: function(element, message) {
|
|
1448
1351
|
const originalText = element.textContent;
|
|
1449
1352
|
element.textContent = message;
|
|
1450
1353
|
element.style.pointerEvents = 'none';
|
|
@@ -1458,11 +1361,12 @@
|
|
|
1458
1361
|
|
|
1459
1362
|
// Module Current Year (Année courante)
|
|
1460
1363
|
currentYear: {
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1364
|
+
detect: function(scope) {
|
|
1365
|
+
const s = scope || document;
|
|
1366
|
+
return s.querySelector(bbContents._attrSelector('current-year')) !== null;
|
|
1367
|
+
},
|
|
1368
|
+
|
|
1369
|
+
init: function(root) {
|
|
1466
1370
|
const scope = root || document;
|
|
1467
1371
|
if (scope.closest && scope.closest('[data-bb-disable]')) return;
|
|
1468
1372
|
const elements = scope.querySelectorAll(bbContents._attrSelector('current-year'));
|
|
@@ -1491,13 +1395,13 @@
|
|
|
1491
1395
|
|
|
1492
1396
|
// Module Reading Time (Temps de lecture)
|
|
1493
1397
|
readingTime: {
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1398
|
+
detect: function(scope) {
|
|
1399
|
+
const s = scope || document;
|
|
1400
|
+
return s.querySelector(bbContents._attrSelector('reading-time')) !== null;
|
|
1401
|
+
},
|
|
1402
|
+
|
|
1403
|
+
// Fonction pour extraire le texte et les images depuis une URL
|
|
1404
|
+
fetchContentFromUrl: function(url, targetSelector) {
|
|
1501
1405
|
return fetch(url)
|
|
1502
1406
|
.then(function(response) {
|
|
1503
1407
|
if (!response.ok) {
|
|
@@ -1554,10 +1458,10 @@
|
|
|
1554
1458
|
|
|
1555
1459
|
return { text: text, images: images };
|
|
1556
1460
|
});
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1461
|
+
},
|
|
1462
|
+
|
|
1463
|
+
// Fonction pour calculer le temps de lecture
|
|
1464
|
+
calculateReadingTime: function(text, images, wordsPerMinute, secondsPerImage) {
|
|
1561
1465
|
// Utiliser split(/\s+/) pour un comptage plus fiable (comme le code de référence)
|
|
1562
1466
|
const wordCount = text ? text.trim().split(/\s+/).filter(function(word) { return word.length > 0; }).length : 0;
|
|
1563
1467
|
const imageCount = images ? images.length : 0;
|
|
@@ -2196,13 +2100,13 @@
|
|
|
2196
2100
|
|
|
2197
2101
|
// Module Favicon (Favicon Dynamique)
|
|
2198
2102
|
favicon: {
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2103
|
+
originalFavicon: null,
|
|
2104
|
+
|
|
2105
|
+
// Détection
|
|
2106
|
+
detect: function(scope) {
|
|
2107
|
+
const s = scope || document;
|
|
2108
|
+
return s.querySelector(bbContents._attrSelector('favicon')) !== null;
|
|
2109
|
+
},
|
|
2206
2110
|
|
|
2207
2111
|
// Initialisation
|
|
2208
2112
|
init: function(root) {
|
|
@@ -2240,8 +2144,8 @@
|
|
|
2240
2144
|
}
|
|
2241
2145
|
},
|
|
2242
2146
|
|
|
2243
|
-
|
|
2244
|
-
|
|
2147
|
+
// Helper: Récupérer ou créer un élément favicon
|
|
2148
|
+
getFaviconElement: function() {
|
|
2245
2149
|
let favicon = document.querySelector('link[rel="icon"]') ||
|
|
2246
2150
|
document.querySelector('link[rel="shortcut icon"]');
|
|
2247
2151
|
if (!favicon) {
|
|
@@ -2252,8 +2156,8 @@
|
|
|
2252
2156
|
return favicon;
|
|
2253
2157
|
},
|
|
2254
2158
|
|
|
2255
|
-
|
|
2256
|
-
|
|
2159
|
+
// Changer le favicon
|
|
2160
|
+
setFavicon: function(url) {
|
|
2257
2161
|
if (!url) return;
|
|
2258
2162
|
|
|
2259
2163
|
// Ajouter un timestamp pour forcer le rafraîchissement du cache
|
|
@@ -2264,8 +2168,8 @@
|
|
|
2264
2168
|
favicon.href = urlWithCacheBuster;
|
|
2265
2169
|
},
|
|
2266
2170
|
|
|
2267
|
-
|
|
2268
|
-
|
|
2171
|
+
// Support dark mode (méthode simplifiée et directe)
|
|
2172
|
+
setupDarkMode: function(lightUrl, darkUrl) {
|
|
2269
2173
|
// Fonction pour mettre à jour le favicon selon le mode sombre
|
|
2270
2174
|
const updateFavicon = function(e) {
|
|
2271
2175
|
const darkModeOn = e ? e.matches : window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
@@ -2288,7 +2192,7 @@
|
|
|
2288
2192
|
|
|
2289
2193
|
// Module YouTube Feed
|
|
2290
2194
|
youtube: {
|
|
2291
|
-
//
|
|
2195
|
+
// Détection des bots
|
|
2292
2196
|
isBot: function() {
|
|
2293
2197
|
const userAgent = navigator.userAgent.toLowerCase();
|
|
2294
2198
|
const botPatterns = [
|
|
@@ -2317,7 +2221,7 @@
|
|
|
2317
2221
|
return isBot;
|
|
2318
2222
|
},
|
|
2319
2223
|
|
|
2320
|
-
//
|
|
2224
|
+
// Cache avec protection appels multiples
|
|
2321
2225
|
cache: {
|
|
2322
2226
|
get: function(key) {
|
|
2323
2227
|
try {
|
|
@@ -2327,7 +2231,7 @@
|
|
|
2327
2231
|
const data = JSON.parse(cached);
|
|
2328
2232
|
const now = Date.now();
|
|
2329
2233
|
|
|
2330
|
-
//
|
|
2234
|
+
// Cache 24h
|
|
2331
2235
|
if (now - data.timestamp > 24 * 60 * 60 * 1000) {
|
|
2332
2236
|
localStorage.removeItem(key);
|
|
2333
2237
|
return null;
|
|
@@ -2352,7 +2256,7 @@
|
|
|
2352
2256
|
}
|
|
2353
2257
|
},
|
|
2354
2258
|
|
|
2355
|
-
//
|
|
2259
|
+
// Protection globale contre les appels multiples
|
|
2356
2260
|
_activeRequests: new Set(),
|
|
2357
2261
|
|
|
2358
2262
|
isRequestActive: function(cacheKey) {
|
|
@@ -2386,6 +2290,9 @@
|
|
|
2386
2290
|
|
|
2387
2291
|
// Module détecté: youtube
|
|
2388
2292
|
|
|
2293
|
+
// OPTIMISATION: Grouper les éléments par configuration unique pour partager les requêtes API
|
|
2294
|
+
const elementsByConfig = {};
|
|
2295
|
+
|
|
2389
2296
|
elements.forEach(element => {
|
|
2390
2297
|
// Vérifier si l'élément a déjà été traité par un autre module
|
|
2391
2298
|
if (element.bbProcessed || element.hasAttribute('data-bb-marquee-processed')) {
|
|
@@ -2394,57 +2301,123 @@
|
|
|
2394
2301
|
}
|
|
2395
2302
|
element.bbProcessed = true;
|
|
2396
2303
|
|
|
2397
|
-
|
|
2398
|
-
|
|
2304
|
+
const channelIdsRaw = bbContents._getAttr(element, 'bb-youtube-channel');
|
|
2305
|
+
if (!channelIdsRaw) return;
|
|
2306
|
+
|
|
2307
|
+
// Parser les channelIds (peuvent être séparés par virgules)
|
|
2308
|
+
const channelIds = channelIdsRaw.split(',').map(id => id.trim()).filter(id => id);
|
|
2309
|
+
if (channelIds.length === 0) return;
|
|
2310
|
+
|
|
2311
|
+
// Normaliser et trier les channelIds pour créer une clé unique
|
|
2312
|
+
const normalizedChannelIds = channelIds.sort().join(',');
|
|
2313
|
+
|
|
2314
|
+
const allowShorts = bbContents._getAttr(element, 'bb-youtube-allow-shorts') === 'true';
|
|
2315
|
+
const language = bbContents._getAttr(element, 'bb-youtube-language') || 'fr';
|
|
2316
|
+
const videoCount = parseInt(bbContents._getAttr(element, 'bb-youtube-video-count') || '10', 10);
|
|
2317
|
+
const skip = parseInt(bbContents._getAttr(element, 'bb-youtube-skip') || '0', 10);
|
|
2318
|
+
|
|
2319
|
+
// Créer une clé unique par configuration (channelIds + allowShorts + language)
|
|
2320
|
+
const configKey = `${normalizedChannelIds}_${allowShorts}_${language}`;
|
|
2321
|
+
|
|
2322
|
+
if (!elementsByConfig[configKey]) {
|
|
2323
|
+
elementsByConfig[configKey] = {
|
|
2324
|
+
elements: [],
|
|
2325
|
+
maxVideoCount: 0,
|
|
2326
|
+
maxSkip: 0,
|
|
2327
|
+
channelIds: normalizedChannelIds,
|
|
2328
|
+
allowShorts: allowShorts,
|
|
2329
|
+
language: language
|
|
2330
|
+
};
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
elementsByConfig[configKey].elements.push(element);
|
|
2334
|
+
// Garder le max videoCount et maxSkip pour ce groupe
|
|
2335
|
+
elementsByConfig[configKey].maxVideoCount = Math.max(
|
|
2336
|
+
elementsByConfig[configKey].maxVideoCount,
|
|
2337
|
+
videoCount
|
|
2338
|
+
);
|
|
2339
|
+
elementsByConfig[configKey].maxSkip = Math.max(
|
|
2340
|
+
elementsByConfig[configKey].maxSkip,
|
|
2341
|
+
skip
|
|
2342
|
+
);
|
|
2343
|
+
});
|
|
2344
|
+
|
|
2345
|
+
// Initialiser chaque groupe d'éléments
|
|
2346
|
+
Object.keys(elementsByConfig).forEach(configKey => {
|
|
2347
|
+
const group = elementsByConfig[configKey];
|
|
2348
|
+
|
|
2349
|
+
// Initialiser tous les éléments de ce groupe
|
|
2350
|
+
group.elements.forEach(element => {
|
|
2351
|
+
const videoCount = parseInt(bbContents._getAttr(element, 'bb-youtube-video-count') || '10', 10);
|
|
2352
|
+
const skip = parseInt(bbContents._getAttr(element, 'bb-youtube-skip') || '0', 10);
|
|
2353
|
+
this.initElement(element, group, videoCount, skip);
|
|
2354
|
+
});
|
|
2399
2355
|
});
|
|
2400
2356
|
},
|
|
2401
2357
|
|
|
2402
2358
|
// Fonction pour initialiser un seul élément YouTube
|
|
2403
|
-
initElement: function(element) {
|
|
2359
|
+
initElement: function(element, groupConfig, videoCount, skip) {
|
|
2404
2360
|
// Vérifier si c'est un bot - pas d'appel API
|
|
2405
2361
|
if (this.isBot()) {
|
|
2406
2362
|
return;
|
|
2407
2363
|
}
|
|
2408
2364
|
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2365
|
+
// Si les paramètres ne sont pas fournis, les récupérer depuis l'élément
|
|
2366
|
+
if (!groupConfig) {
|
|
2367
|
+
const channelIdsRaw = bbContents._getAttr(element, 'bb-youtube-channel');
|
|
2368
|
+
if (!channelIdsRaw) return;
|
|
2369
|
+
|
|
2370
|
+
const channelIds = channelIdsRaw.split(',').map(id => id.trim()).filter(id => id);
|
|
2371
|
+
if (channelIds.length === 0) return;
|
|
2372
|
+
|
|
2373
|
+
const normalizedChannelIds = channelIds.sort().join(',');
|
|
2374
|
+
const allowShorts = bbContents._getAttr(element, 'bb-youtube-allow-shorts') === 'true';
|
|
2375
|
+
const language = bbContents._getAttr(element, 'bb-youtube-language') || 'fr';
|
|
2376
|
+
|
|
2377
|
+
groupConfig = {
|
|
2378
|
+
channelIds: normalizedChannelIds,
|
|
2379
|
+
allowShorts: allowShorts,
|
|
2380
|
+
language: language,
|
|
2381
|
+
maxVideoCount: parseInt(bbContents._getAttr(element, 'bb-youtube-video-count') || '10', 10),
|
|
2382
|
+
maxSkip: parseInt(bbContents._getAttr(element, 'bb-youtube-skip') || '0', 10)
|
|
2383
|
+
};
|
|
2384
|
+
videoCount = groupConfig.maxVideoCount;
|
|
2385
|
+
skip = groupConfig.maxSkip;
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
if (!videoCount) {
|
|
2389
|
+
videoCount = parseInt(bbContents._getAttr(element, 'bb-youtube-video-count') || '10', 10);
|
|
2390
|
+
}
|
|
2391
|
+
if (skip === undefined || skip === null) {
|
|
2392
|
+
skip = parseInt(bbContents._getAttr(element, 'bb-youtube-skip') || '0', 10);
|
|
2393
|
+
}
|
|
2413
2394
|
|
|
2414
2395
|
// Vérifier la configuration au moment de l'initialisation
|
|
2415
2396
|
const endpoint = bbContents.checkYouTubeConfig() ? bbContents.config.youtubeEndpoint : null;
|
|
2416
2397
|
|
|
2417
|
-
|
|
2418
|
-
if (!channelId) {
|
|
2419
|
-
return;
|
|
2420
|
-
}
|
|
2421
|
-
|
|
2422
2398
|
if (!endpoint) {
|
|
2423
|
-
//
|
|
2399
|
+
// Limiter les retries
|
|
2424
2400
|
const retryCount = element.getAttribute('data-youtube-retry-count') || '0';
|
|
2425
2401
|
const retries = parseInt(retryCount);
|
|
2426
2402
|
|
|
2427
|
-
if (retries < 10) {
|
|
2403
|
+
if (retries < 10) {
|
|
2428
2404
|
element.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Configuration YouTube en cours...</div>';
|
|
2429
2405
|
element.setAttribute('data-youtube-retry-count', (retries + 1).toString());
|
|
2430
2406
|
|
|
2431
|
-
// OPTIMISATION: Espacer les retries (500ms au lieu de 100ms)
|
|
2432
2407
|
setTimeout(() => {
|
|
2433
2408
|
this.initElement(element);
|
|
2434
2409
|
}, 500);
|
|
2435
2410
|
return;
|
|
2436
2411
|
} else {
|
|
2437
|
-
// Timeout après 5 secondes
|
|
2438
2412
|
element.innerHTML = '<div style="padding: 20px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; color: #dc2626;"><strong>Configuration YouTube manquante</strong><br>Ajoutez dans le <head> :<br><code style="display: block; background: #f3f4f6; padding: 10px; margin: 10px 0; border-radius: 4px; font-family: monospace;"><script><br>bbContents.config.youtubeEndpoint = \'votre-worker-url\';<br></script></code></div>';
|
|
2439
|
-
|
|
2413
|
+
return;
|
|
2440
2414
|
}
|
|
2441
2415
|
}
|
|
2442
2416
|
|
|
2443
|
-
// Chercher le template pour une vidéo
|
|
2417
|
+
// Chercher le template pour une vidéo
|
|
2444
2418
|
let template = element.querySelector('[bb-youtube-item]');
|
|
2445
2419
|
let container = element;
|
|
2446
2420
|
|
|
2447
|
-
// Si pas de template direct, chercher dans un conteneur
|
|
2448
2421
|
if (!template) {
|
|
2449
2422
|
const containerElement = element.querySelector('[bb-youtube-container]');
|
|
2450
2423
|
if (containerElement) {
|
|
@@ -2455,110 +2428,213 @@
|
|
|
2455
2428
|
|
|
2456
2429
|
if (!template) {
|
|
2457
2430
|
element.innerHTML = '<div style="padding: 20px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; color: #dc2626;"><strong>Template manquant</strong><br>Ajoutez un élément avec l\'attribut bb-youtube-item</div>';
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2461
2434
|
// Cacher le template original
|
|
2462
2435
|
template.style.display = 'none';
|
|
2463
2436
|
|
|
2464
2437
|
// Marquer l'élément comme traité par le module YouTube
|
|
2465
2438
|
element.setAttribute('data-bb-youtube-processed', 'true');
|
|
2466
2439
|
|
|
2467
|
-
//
|
|
2468
|
-
const
|
|
2469
|
-
const cachedData = this.cache.get(
|
|
2440
|
+
// OPTIMISATION: Cache partagé par configuration (sans videoCount ni skip)
|
|
2441
|
+
const baseCacheKey = `youtube_${groupConfig.channelIds}_${groupConfig.allowShorts}_${groupConfig.language}`;
|
|
2442
|
+
const cachedData = this.cache.get(baseCacheKey);
|
|
2470
2443
|
|
|
2471
2444
|
if (cachedData && cachedData.value) {
|
|
2472
2445
|
// Données YouTube récupérées du cache (économie API)
|
|
2473
|
-
|
|
2446
|
+
// Appliquer skip puis limiter par videoCount
|
|
2447
|
+
const limitedData = this.applySkipAndLimit(cachedData.value, skip, videoCount);
|
|
2448
|
+
this.generateYouTubeFeed(container, template, limitedData, groupConfig.allowShorts, groupConfig.language);
|
|
2474
2449
|
return;
|
|
2475
2450
|
}
|
|
2476
2451
|
|
|
2477
|
-
|
|
2478
|
-
if (this.isRequestActive(cacheKey)) {
|
|
2479
|
-
// Un appel est déjà en cours pour cette clé, attendre
|
|
2452
|
+
if (this.isRequestActive(baseCacheKey)) {
|
|
2480
2453
|
const checkActive = () => {
|
|
2481
|
-
if (!this.isRequestActive(
|
|
2454
|
+
if (!this.isRequestActive(baseCacheKey)) {
|
|
2482
2455
|
// L'autre appel est terminé, vérifier le cache
|
|
2483
|
-
const newCachedData = this.cache.get(
|
|
2456
|
+
const newCachedData = this.cache.get(baseCacheKey);
|
|
2484
2457
|
if (newCachedData && newCachedData.value) {
|
|
2485
|
-
this.
|
|
2486
|
-
|
|
2458
|
+
const limitedData = this.applySkipAndLimit(newCachedData.value, skip, videoCount);
|
|
2459
|
+
this.generateYouTubeFeed(container, template, limitedData, groupConfig.allowShorts, groupConfig.language);
|
|
2460
|
+
} else {
|
|
2487
2461
|
container.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Erreur de chargement</div>';
|
|
2488
2462
|
}
|
|
2489
2463
|
} else {
|
|
2490
|
-
setTimeout(checkActive, 200);
|
|
2464
|
+
setTimeout(checkActive, 200);
|
|
2491
2465
|
}
|
|
2492
2466
|
};
|
|
2493
2467
|
checkActive();
|
|
2494
2468
|
return;
|
|
2495
2469
|
}
|
|
2496
2470
|
|
|
2497
|
-
// Marquer qu'un appel API est en cours
|
|
2498
|
-
this.markRequestActive(
|
|
2471
|
+
// Marquer qu'un appel API est en cours (utiliser baseCacheKey)
|
|
2472
|
+
this.markRequestActive(baseCacheKey);
|
|
2499
2473
|
|
|
2500
2474
|
// Afficher un loader
|
|
2501
2475
|
container.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Chargement des vidéos YouTube...</div>';
|
|
2502
2476
|
|
|
2503
|
-
//
|
|
2504
|
-
|
|
2477
|
+
// OPTIMISATION: Utiliser le maxVideoCount et maxSkip du groupe pour la requête API
|
|
2478
|
+
const apiVideoCount = groupConfig.maxVideoCount + groupConfig.maxSkip;
|
|
2479
|
+
|
|
2480
|
+
// Valider l'endpoint
|
|
2505
2481
|
if (!endpoint || typeof endpoint !== 'string') {
|
|
2506
2482
|
throw new Error('Endpoint YouTube invalide');
|
|
2507
2483
|
}
|
|
2508
|
-
// Vérifier que l'endpoint correspond à la configuration
|
|
2509
2484
|
if (bbContents.config.youtubeEndpoint && !endpoint.startsWith(bbContents.config.youtubeEndpoint)) {
|
|
2510
2485
|
throw new Error('Endpoint YouTube non autorisé');
|
|
2511
2486
|
}
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2487
|
+
|
|
2488
|
+
// Parser les channelIds
|
|
2489
|
+
const channelIds = groupConfig.channelIds.split(',');
|
|
2490
|
+
|
|
2491
|
+
// Valider les channelIds
|
|
2492
|
+
channelIds.forEach(channelId => {
|
|
2493
|
+
if (!channelId || !/^[a-zA-Z0-9_-]+$/.test(channelId)) {
|
|
2494
|
+
throw new Error('Channel ID invalide: ' + channelId);
|
|
2495
|
+
}
|
|
2496
|
+
});
|
|
2497
|
+
|
|
2498
|
+
const safeAllowShorts = groupConfig.allowShorts === true || groupConfig.allowShorts === 'true';
|
|
2499
|
+
|
|
2500
|
+
// OPTIMISATION: Si plusieurs channelIds, récupérer depuis plusieurs chaînes
|
|
2501
|
+
if (channelIds.length > 1) {
|
|
2502
|
+
this.fetchMultipleChannels(endpoint, channelIds, apiVideoCount, safeAllowShorts)
|
|
2503
|
+
.then(data => {
|
|
2504
|
+
if (data.error) {
|
|
2505
|
+
throw new Error(data.error.message || 'Erreur API YouTube');
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
// Stocker dans le cache avec la clé de base (sans videoCount ni skip)
|
|
2509
|
+
this.cache.set(baseCacheKey, data);
|
|
2510
|
+
|
|
2511
|
+
// Appliquer skip puis limiter par videoCount pour cet élément
|
|
2512
|
+
const limitedData = this.applySkipAndLimit(data, skip, videoCount);
|
|
2513
|
+
|
|
2514
|
+
this.generateYouTubeFeed(container, template, limitedData, groupConfig.allowShorts, groupConfig.language);
|
|
2515
|
+
|
|
2516
|
+
this.markRequestComplete(baseCacheKey);
|
|
2517
|
+
})
|
|
2518
|
+
.catch(error => {
|
|
2519
|
+
this.markRequestComplete(baseCacheKey);
|
|
2520
|
+
this.handleFetchError(error, container, baseCacheKey, skip, videoCount, template, groupConfig);
|
|
2521
|
+
});
|
|
2522
|
+
} else {
|
|
2523
|
+
// Un seul channelId, requête simple
|
|
2524
|
+
fetch(`${endpoint}?channelId=${encodeURIComponent(channelIds[0])}&maxResults=${apiVideoCount}&allowShorts=${safeAllowShorts}`)
|
|
2525
|
+
.then(response => {
|
|
2526
|
+
if (!response.ok) {
|
|
2527
|
+
throw new Error(`HTTP ${response.status}`);
|
|
2528
|
+
}
|
|
2529
|
+
return response.json();
|
|
2530
|
+
})
|
|
2531
|
+
.then(data => {
|
|
2532
|
+
if (data.error) {
|
|
2533
|
+
throw new Error(data.error.message || 'Erreur API YouTube');
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2536
|
+
// Stocker dans le cache avec la clé de base (sans videoCount ni skip)
|
|
2537
|
+
this.cache.set(baseCacheKey, data);
|
|
2538
|
+
|
|
2539
|
+
// Appliquer skip puis limiter par videoCount pour cet élément
|
|
2540
|
+
const limitedData = this.applySkipAndLimit(data, skip, videoCount);
|
|
2541
|
+
|
|
2542
|
+
this.generateYouTubeFeed(container, template, limitedData, groupConfig.allowShorts, groupConfig.language);
|
|
2543
|
+
|
|
2544
|
+
this.markRequestComplete(baseCacheKey);
|
|
2545
|
+
})
|
|
2546
|
+
.catch(error => {
|
|
2547
|
+
this.markRequestComplete(baseCacheKey);
|
|
2548
|
+
this.handleFetchError(error, container, baseCacheKey, skip, videoCount, template, groupConfig);
|
|
2549
|
+
});
|
|
2515
2550
|
}
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
if (expiredCache) {
|
|
2550
|
-
try {
|
|
2551
|
-
const cachedData = JSON.parse(expiredCache);
|
|
2552
|
-
// Utilisation du cache expiré en cas d'erreur API
|
|
2553
|
-
this.generateYouTubeFeed(container, template, cachedData.value, allowShorts, language);
|
|
2554
|
-
return;
|
|
2555
|
-
} catch (e) {
|
|
2556
|
-
// Ignorer les erreurs de parsing
|
|
2551
|
+
},
|
|
2552
|
+
|
|
2553
|
+
// Fonction pour appliquer skip puis limiter par videoCount
|
|
2554
|
+
applySkipAndLimit: function(data, skip, videoCount) {
|
|
2555
|
+
if (!data || !data.items) {
|
|
2556
|
+
return data;
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
// Appliquer skip
|
|
2560
|
+
const afterSkip = skip > 0 ? data.items.slice(skip) : data.items;
|
|
2561
|
+
|
|
2562
|
+
// Limiter par videoCount
|
|
2563
|
+
const limited = afterSkip.slice(0, videoCount);
|
|
2564
|
+
|
|
2565
|
+
return {
|
|
2566
|
+
...data,
|
|
2567
|
+
items: limited
|
|
2568
|
+
};
|
|
2569
|
+
},
|
|
2570
|
+
|
|
2571
|
+
// Fonction pour récupérer les vidéos de plusieurs chaînes
|
|
2572
|
+
fetchMultipleChannels: function(endpoint, channelIds, maxResults, allowShorts) {
|
|
2573
|
+
// Limiter le nombre de channelIds pour éviter les abus
|
|
2574
|
+
if (channelIds.length > 10) {
|
|
2575
|
+
throw new Error('Maximum 10 channelIds allowed');
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
// Faire une requête par channelId unique
|
|
2579
|
+
const promises = channelIds.map(channelId => {
|
|
2580
|
+
return fetch(`${endpoint}?channelId=${encodeURIComponent(channelId)}&maxResults=${maxResults}&allowShorts=${allowShorts}`)
|
|
2581
|
+
.then(response => {
|
|
2582
|
+
if (!response.ok) {
|
|
2583
|
+
throw new Error(`HTTP ${response.status} for channel ${channelId}`);
|
|
2557
2584
|
}
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2585
|
+
return response.json();
|
|
2586
|
+
})
|
|
2587
|
+
.then(data => {
|
|
2588
|
+
if (data.error) {
|
|
2589
|
+
throw new Error(data.error.message || 'Erreur API YouTube');
|
|
2590
|
+
}
|
|
2591
|
+
return data.items || [];
|
|
2592
|
+
})
|
|
2593
|
+
.catch(error => {
|
|
2594
|
+
// En cas d'erreur pour une chaîne, retourner un tableau vide
|
|
2595
|
+
// Les autres chaînes continueront à fonctionner
|
|
2596
|
+
return [];
|
|
2597
|
+
});
|
|
2598
|
+
});
|
|
2599
|
+
|
|
2600
|
+
return Promise.all(promises).then(allItems => {
|
|
2601
|
+
// Fusionner tous les items de toutes les chaînes
|
|
2602
|
+
const mergedItems = [].concat(...allItems);
|
|
2603
|
+
|
|
2604
|
+
// Trier par date (publishedAt) - du plus récent au plus ancien
|
|
2605
|
+
mergedItems.sort((a, b) => {
|
|
2606
|
+
const dateA = new Date(a.snippet.publishedAt);
|
|
2607
|
+
const dateB = new Date(b.snippet.publishedAt);
|
|
2608
|
+
return dateB - dateA;
|
|
2561
2609
|
});
|
|
2610
|
+
|
|
2611
|
+
// Retourner dans le format attendu
|
|
2612
|
+
return {
|
|
2613
|
+
items: mergedItems,
|
|
2614
|
+
pageInfo: {
|
|
2615
|
+
totalResults: mergedItems.length,
|
|
2616
|
+
resultsPerPage: mergedItems.length
|
|
2617
|
+
}
|
|
2618
|
+
};
|
|
2619
|
+
});
|
|
2620
|
+
},
|
|
2621
|
+
|
|
2622
|
+
// Fonction pour gérer les erreurs de fetch
|
|
2623
|
+
handleFetchError: function(error, container, cacheKey, skip, videoCount, template, groupConfig) {
|
|
2624
|
+
// En cas d'erreur, essayer de récupérer du cache même expiré
|
|
2625
|
+
const expiredCache = localStorage.getItem(cacheKey);
|
|
2626
|
+
if (expiredCache) {
|
|
2627
|
+
try {
|
|
2628
|
+
const cachedData = JSON.parse(expiredCache);
|
|
2629
|
+
const limitedData = this.applySkipAndLimit(cachedData.value, skip, videoCount);
|
|
2630
|
+
this.generateYouTubeFeed(container, template, limitedData, groupConfig.allowShorts, groupConfig.language);
|
|
2631
|
+
return;
|
|
2632
|
+
} catch (e) {
|
|
2633
|
+
// Ignorer les erreurs de parsing
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
container.innerHTML = `<div style="padding: 20px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; color: #dc2626;"><strong>Erreur de chargement</strong><br>${bbContents.utils.sanitize(error.message || 'Erreur inconnue')}</div>`;
|
|
2562
2638
|
},
|
|
2563
2639
|
|
|
2564
2640
|
generateYouTubeFeed: function(container, template, data, allowShorts, language = 'fr') {
|
|
@@ -2732,7 +2808,7 @@
|
|
|
2732
2808
|
return textarea.value;
|
|
2733
2809
|
},
|
|
2734
2810
|
|
|
2735
|
-
//
|
|
2811
|
+
// Nettoyer le cache expiré
|
|
2736
2812
|
cleanCache: function() {
|
|
2737
2813
|
try {
|
|
2738
2814
|
const keys = Object.keys(localStorage);
|
|
@@ -2743,7 +2819,7 @@
|
|
|
2743
2819
|
if (key.startsWith('youtube_')) {
|
|
2744
2820
|
try {
|
|
2745
2821
|
const cached = JSON.parse(localStorage.getItem(key));
|
|
2746
|
-
//
|
|
2822
|
+
// Cache 24h
|
|
2747
2823
|
if (now - cached.timestamp > 24 * 60 * 60 * 1000) {
|
|
2748
2824
|
localStorage.removeItem(key);
|
|
2749
2825
|
cleaned++;
|
|
@@ -2778,51 +2854,25 @@
|
|
|
2778
2854
|
}
|
|
2779
2855
|
};
|
|
2780
2856
|
|
|
2781
|
-
// Initialisation automatique avec délai
|
|
2857
|
+
// Initialisation automatique avec délai
|
|
2782
2858
|
function initBBContents() {
|
|
2783
|
-
// Attendre que la page soit prête
|
|
2784
2859
|
if (document.readyState === 'loading') {
|
|
2785
2860
|
document.addEventListener('DOMContentLoaded', function() {
|
|
2786
|
-
// Délai pour éviter le blocage du rendu
|
|
2787
2861
|
const delay = document.body.hasAttribute('bb-performance-boost') ? 300 : 100;
|
|
2788
2862
|
setTimeout(function() {
|
|
2789
2863
|
bbContents.init();
|
|
2790
2864
|
}, delay);
|
|
2791
2865
|
});
|
|
2792
2866
|
} else {
|
|
2793
|
-
// Délai pour éviter le blocage du rendu
|
|
2794
2867
|
const delay = document.body.hasAttribute('bb-performance-boost') ? 300 : 100;
|
|
2795
2868
|
setTimeout(function() {
|
|
2796
2869
|
bbContents.init();
|
|
2797
2870
|
}, delay);
|
|
2798
2871
|
}
|
|
2799
|
-
|
|
2800
|
-
// Initialisation différée supplémentaire pour les cas difficiles - Solution cache optimisée
|
|
2801
|
-
window.addEventListener('load', function() {
|
|
2802
|
-
const loadDelay = document.body.hasAttribute('bb-performance-boost') ? 4000 : 3000; // Délais plus longs pour le cache
|
|
2803
|
-
setTimeout(function() {
|
|
2804
|
-
// Vérifier s'il y a des éléments non initialisés
|
|
2805
|
-
const unprocessedMarquees = document.querySelectorAll('[bb-marquee]:not([data-bb-marquee-processed])');
|
|
2806
|
-
if (unprocessedMarquees.length > 0) {
|
|
2807
|
-
// Éléments marquee non initialisés détectés après load, réinitialisation
|
|
2808
|
-
bbContents.reinit();
|
|
2809
|
-
}
|
|
2810
|
-
|
|
2811
|
-
// Vérification supplémentaire des images chargées - Solution cache optimisée
|
|
2812
|
-
const allImages = document.querySelectorAll('img');
|
|
2813
|
-
const unloadedImages = Array.from(allImages).filter(img => !img.complete || img.naturalHeight === 0);
|
|
2814
|
-
if (unloadedImages.length > 0) {
|
|
2815
|
-
// Images non chargées détectées, attente supplémentaire plus longue
|
|
2816
|
-
setTimeout(() => {
|
|
2817
|
-
bbContents.reinit();
|
|
2818
|
-
}, 2000); // 2 secondes au lieu de 1 seconde
|
|
2819
|
-
}
|
|
2820
|
-
}, loadDelay);
|
|
2821
|
-
});
|
|
2822
2872
|
}
|
|
2823
2873
|
|
|
2824
2874
|
// Initialisation
|
|
2825
2875
|
initBBContents();
|
|
2826
2876
|
|
|
2827
|
-
// Message de confirmation supprimé
|
|
2877
|
+
// Message de confirmation supprimé
|
|
2828
2878
|
})();
|