@bebranded/bb-contents 1.0.84-beta → 1.0.85
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 +386 -356
- 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.0.
|
|
4
|
+
* @version 1.0.85
|
|
5
5
|
* @author BeBranded
|
|
6
6
|
* @license MIT
|
|
7
7
|
* @website https://www.bebranded.xyz
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
window._bbContentsInitialized = true;
|
|
33
33
|
|
|
34
34
|
// Log de démarrage simple (une seule fois)
|
|
35
|
-
console.log('bb-contents | v1.0.
|
|
35
|
+
console.log('bb-contents | v1.0.85');
|
|
36
36
|
|
|
37
37
|
// Configuration
|
|
38
38
|
const config = {
|
|
39
|
-
version: '1.0.
|
|
39
|
+
version: '1.0.85',
|
|
40
40
|
debug: false, // Debug désactivé pour rendu propre
|
|
41
41
|
prefix: 'bb-', // utilisé pour générer les sélecteurs (data-bb-*)
|
|
42
42
|
youtubeEndpoint: null, // URL du worker YouTube (à définir par l'utilisateur)
|
|
@@ -589,259 +589,259 @@
|
|
|
589
589
|
|
|
590
590
|
// Module Share (Partage Social)
|
|
591
591
|
share: {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
},
|
|
599
|
-
facebook: function(data) {
|
|
600
|
-
return 'https://facebook.com/sharer/sharer.php?u=' +
|
|
601
|
-
encodeURIComponent(data.url);
|
|
602
|
-
},
|
|
603
|
-
linkedin: function(data) {
|
|
604
|
-
// LinkedIn - URL de partage officielle (2024+)
|
|
605
|
-
return 'https://www.linkedin.com/sharing/share-offsite/?url=' + encodeURIComponent(data.url);
|
|
606
|
-
},
|
|
607
|
-
whatsapp: function(data) {
|
|
608
|
-
return 'https://wa.me/?text=' +
|
|
609
|
-
encodeURIComponent(data.text + ' ' + data.url);
|
|
610
|
-
},
|
|
611
|
-
telegram: function(data) {
|
|
612
|
-
return 'https://t.me/share/url?url=' +
|
|
613
|
-
encodeURIComponent(data.url) +
|
|
614
|
-
'&text=' + encodeURIComponent(data.text);
|
|
615
|
-
},
|
|
616
|
-
email: function(data) {
|
|
617
|
-
return 'mailto:?subject=' +
|
|
618
|
-
encodeURIComponent(data.text) +
|
|
619
|
-
'&body=' + encodeURIComponent(data.text + ' ' + data.url);
|
|
620
|
-
},
|
|
621
|
-
copy: function(data) {
|
|
622
|
-
return 'copy:' + data.url;
|
|
623
|
-
},
|
|
624
|
-
native: function(data) {
|
|
625
|
-
return 'native:' + JSON.stringify(data);
|
|
626
|
-
}
|
|
592
|
+
// Configuration des réseaux
|
|
593
|
+
networks: {
|
|
594
|
+
twitter: function(data) {
|
|
595
|
+
return 'https://twitter.com/intent/tweet?url=' +
|
|
596
|
+
encodeURIComponent(data.url) +
|
|
597
|
+
'&text=' + encodeURIComponent(data.text);
|
|
627
598
|
},
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
const s = scope || document;
|
|
632
|
-
return s.querySelector(bbContents._attrSelector('share')) !== null;
|
|
599
|
+
facebook: function(data) {
|
|
600
|
+
return 'https://facebook.com/sharer/sharer.php?u=' +
|
|
601
|
+
encodeURIComponent(data.url);
|
|
633
602
|
},
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
const scope = root || document;
|
|
638
|
-
if (scope.closest && scope.closest('[data-bb-disable]')) return;
|
|
639
|
-
const elements = scope.querySelectorAll(bbContents._attrSelector('share'));
|
|
640
|
-
|
|
641
|
-
elements.forEach(function(element) {
|
|
642
|
-
// Vérifier si déjà traité
|
|
643
|
-
if (element.bbProcessed) return;
|
|
644
|
-
element.bbProcessed = true;
|
|
645
|
-
|
|
646
|
-
// Récupérer les données
|
|
647
|
-
const network = bbContents._getAttr(element, 'bb-share');
|
|
648
|
-
const customUrl = bbContents._getAttr(element, 'bb-url');
|
|
649
|
-
const customText = bbContents._getAttr(element, 'bb-text');
|
|
650
|
-
|
|
651
|
-
// Valeurs par défaut sécurisées
|
|
652
|
-
const data = {
|
|
653
|
-
url: bbContents.utils.isValidUrl(customUrl) ? customUrl : window.location.href,
|
|
654
|
-
text: bbContents.utils.sanitize(customText || document.title || 'Découvrez ce site')
|
|
655
|
-
};
|
|
656
|
-
|
|
657
|
-
// Gestionnaire de clic
|
|
658
|
-
element.addEventListener('click', function(e) {
|
|
659
|
-
e.preventDefault();
|
|
660
|
-
bbContents.modules.share.share(network, data, element);
|
|
661
|
-
});
|
|
662
|
-
|
|
663
|
-
// Accessibilité
|
|
664
|
-
if (element.tagName !== 'BUTTON' && element.tagName !== 'A') {
|
|
665
|
-
element.setAttribute('role', 'button');
|
|
666
|
-
element.setAttribute('tabindex', '0');
|
|
667
|
-
|
|
668
|
-
// Support clavier
|
|
669
|
-
element.addEventListener('keydown', function(e) {
|
|
670
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
671
|
-
e.preventDefault();
|
|
672
|
-
bbContents.modules.share.share(network, data, element);
|
|
673
|
-
}
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
element.style.cursor = 'pointer';
|
|
678
|
-
});
|
|
679
|
-
|
|
680
|
-
bbContents.utils.log('Module Share initialisé:', elements.length, 'éléments');
|
|
603
|
+
linkedin: function(data) {
|
|
604
|
+
// LinkedIn - URL de partage officielle (2024+)
|
|
605
|
+
return 'https://www.linkedin.com/sharing/share-offsite/?url=' + encodeURIComponent(data.url);
|
|
681
606
|
},
|
|
607
|
+
whatsapp: function(data) {
|
|
608
|
+
return 'https://wa.me/?text=' +
|
|
609
|
+
encodeURIComponent(data.text + ' ' + data.url);
|
|
610
|
+
},
|
|
611
|
+
telegram: function(data) {
|
|
612
|
+
return 'https://t.me/share/url?url=' +
|
|
613
|
+
encodeURIComponent(data.url) +
|
|
614
|
+
'&text=' + encodeURIComponent(data.text);
|
|
615
|
+
},
|
|
616
|
+
email: function(data) {
|
|
617
|
+
return 'mailto:?subject=' +
|
|
618
|
+
encodeURIComponent(data.text) +
|
|
619
|
+
'&body=' + encodeURIComponent(data.text + ' ' + data.url);
|
|
620
|
+
},
|
|
621
|
+
copy: function(data) {
|
|
622
|
+
return 'copy:' + data.url;
|
|
623
|
+
},
|
|
624
|
+
native: function(data) {
|
|
625
|
+
return 'native:' + JSON.stringify(data);
|
|
626
|
+
}
|
|
627
|
+
},
|
|
628
|
+
|
|
629
|
+
// Détection
|
|
630
|
+
detect: function(scope) {
|
|
631
|
+
const s = scope || document;
|
|
632
|
+
return s.querySelector(bbContents._attrSelector('share')) !== null;
|
|
633
|
+
},
|
|
634
|
+
|
|
635
|
+
// Initialisation
|
|
636
|
+
init: function(root) {
|
|
637
|
+
const scope = root || document;
|
|
638
|
+
if (scope.closest && scope.closest('[data-bb-disable]')) return;
|
|
639
|
+
const elements = scope.querySelectorAll(bbContents._attrSelector('share'));
|
|
682
640
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
if (!networkFunc) {
|
|
688
|
-
return;
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
const shareUrl = networkFunc(data);
|
|
692
|
-
|
|
693
|
-
// Cas spécial : copier le lien
|
|
694
|
-
if (shareUrl.startsWith('copy:')) {
|
|
695
|
-
const url = shareUrl.substring(5);
|
|
696
|
-
// Copie silencieuse (pas de feedback visuel)
|
|
697
|
-
this.copyToClipboard(url, element, true);
|
|
698
|
-
return;
|
|
699
|
-
}
|
|
641
|
+
elements.forEach(function(element) {
|
|
642
|
+
// Vérifier si déjà traité
|
|
643
|
+
if (element.bbProcessed) return;
|
|
644
|
+
element.bbProcessed = true;
|
|
700
645
|
|
|
701
|
-
//
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
return;
|
|
706
|
-
}
|
|
646
|
+
// Récupérer les données
|
|
647
|
+
const network = bbContents._getAttr(element, 'bb-share');
|
|
648
|
+
const customUrl = bbContents._getAttr(element, 'bb-url');
|
|
649
|
+
const customText = bbContents._getAttr(element, 'bb-text');
|
|
707
650
|
|
|
708
|
-
//
|
|
709
|
-
const
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
651
|
+
// Valeurs par défaut sécurisées
|
|
652
|
+
const data = {
|
|
653
|
+
url: bbContents.utils.isValidUrl(customUrl) ? customUrl : window.location.href,
|
|
654
|
+
text: bbContents.utils.sanitize(customText || document.title || 'Découvrez ce site')
|
|
655
|
+
};
|
|
713
656
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
);
|
|
657
|
+
// Gestionnaire de clic
|
|
658
|
+
element.addEventListener('click', function(e) {
|
|
659
|
+
e.preventDefault();
|
|
660
|
+
bbContents.modules.share.share(network, data, element);
|
|
661
|
+
});
|
|
719
662
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
bbContents.modules.share.showFeedback(element, '✓ ' + (bbContents.config.i18n.copied || 'Lien copié !'));
|
|
663
|
+
// Accessibilité
|
|
664
|
+
if (element.tagName !== 'BUTTON' && element.tagName !== 'A') {
|
|
665
|
+
element.setAttribute('role', 'button');
|
|
666
|
+
element.setAttribute('tabindex', '0');
|
|
667
|
+
|
|
668
|
+
// Support clavier
|
|
669
|
+
element.addEventListener('keydown', function(e) {
|
|
670
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
671
|
+
e.preventDefault();
|
|
672
|
+
bbContents.modules.share.share(network, data, element);
|
|
731
673
|
}
|
|
732
|
-
}).catch(function() {
|
|
733
|
-
bbContents.modules.share.fallbackCopy(text, element, isSilent);
|
|
734
674
|
});
|
|
735
|
-
} else {
|
|
736
|
-
// Fallback pour environnements sans Clipboard API
|
|
737
|
-
this.fallbackCopy(text, element, isSilent);
|
|
738
675
|
}
|
|
739
|
-
|
|
676
|
+
|
|
677
|
+
element.style.cursor = 'pointer';
|
|
678
|
+
});
|
|
740
679
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
// Afficher un prompt natif pour permettre à l'utilisateur de copier manuellement
|
|
748
|
-
// (solution universelle sans execCommand)
|
|
749
|
-
window.prompt('Copiez le lien ci-dessous (Ctrl/Cmd+C) :', text);
|
|
750
|
-
} catch (err) {
|
|
751
|
-
// Dernier recours: ne rien faire
|
|
752
|
-
}
|
|
753
|
-
},
|
|
680
|
+
bbContents.utils.log('Module Share initialisé:', elements.length, 'éléments');
|
|
681
|
+
},
|
|
682
|
+
|
|
683
|
+
// Fonction de partage
|
|
684
|
+
share: function(network, data, element) {
|
|
685
|
+
const networkFunc = this.networks[network];
|
|
754
686
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
if (navigator.share) {
|
|
759
|
-
navigator.share({
|
|
760
|
-
title: data.text,
|
|
761
|
-
url: data.url
|
|
762
|
-
}).then(function() {
|
|
763
|
-
bbContents.utils.log('Partage natif réussi');
|
|
764
|
-
}).catch(function(error) {
|
|
765
|
-
if (error.name !== 'AbortError') {
|
|
766
|
-
// Fallback vers copie si échec
|
|
767
|
-
bbContents.modules.share.copyToClipboard(data.url, element, false);
|
|
768
|
-
}
|
|
769
|
-
});
|
|
770
|
-
} else {
|
|
771
|
-
// Fallback si Web Share API non disponible
|
|
772
|
-
bbContents.utils.log('Web Share API non disponible, fallback vers copie');
|
|
773
|
-
this.copyToClipboard(data.url, element, false);
|
|
774
|
-
}
|
|
775
|
-
},
|
|
687
|
+
if (!networkFunc) {
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
776
690
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
element.style.pointerEvents = '';
|
|
786
|
-
}, 2000);
|
|
691
|
+
const shareUrl = networkFunc(data);
|
|
692
|
+
|
|
693
|
+
// Cas spécial : copier le lien
|
|
694
|
+
if (shareUrl.startsWith('copy:')) {
|
|
695
|
+
const url = shareUrl.substring(5);
|
|
696
|
+
// Copie silencieuse (pas de feedback visuel)
|
|
697
|
+
this.copyToClipboard(url, element, true);
|
|
698
|
+
return;
|
|
787
699
|
}
|
|
700
|
+
|
|
701
|
+
// Cas spécial : partage natif (Web Share API)
|
|
702
|
+
if (shareUrl.startsWith('native:')) {
|
|
703
|
+
const shareData = JSON.parse(shareUrl.substring(7));
|
|
704
|
+
this.nativeShare(shareData, element);
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Ouvrir popup de partage
|
|
709
|
+
const width = 600;
|
|
710
|
+
const height = 400;
|
|
711
|
+
const left = (window.innerWidth - width) / 2;
|
|
712
|
+
const top = (window.innerHeight - height) / 2;
|
|
713
|
+
|
|
714
|
+
window.open(
|
|
715
|
+
shareUrl,
|
|
716
|
+
'bbshare',
|
|
717
|
+
'width=' + width + ',height=' + height + ',left=' + left + ',top=' + top + ',noopener,noreferrer'
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
bbContents.utils.log('Partage sur', network, data);
|
|
721
|
+
},
|
|
722
|
+
|
|
723
|
+
// Copier dans le presse-papier
|
|
724
|
+
copyToClipboard: function(text, element, silent) {
|
|
725
|
+
const isSilent = !!silent;
|
|
726
|
+
// Méthode moderne
|
|
727
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
728
|
+
navigator.clipboard.writeText(text).then(function() {
|
|
729
|
+
if (!isSilent) {
|
|
730
|
+
bbContents.modules.share.showFeedback(element, '✓ ' + (bbContents.config.i18n.copied || 'Lien copié !'));
|
|
731
|
+
}
|
|
732
|
+
}).catch(function() {
|
|
733
|
+
bbContents.modules.share.fallbackCopy(text, element, isSilent);
|
|
734
|
+
});
|
|
735
|
+
} else {
|
|
736
|
+
// Fallback pour environnements sans Clipboard API
|
|
737
|
+
this.fallbackCopy(text, element, isSilent);
|
|
738
|
+
}
|
|
739
|
+
},
|
|
740
|
+
|
|
741
|
+
// Fallback copie
|
|
742
|
+
fallbackCopy: function(text, element, silent) {
|
|
743
|
+
const isSilent = !!silent;
|
|
744
|
+
// Pas de UI si silencieux (exigence produit)
|
|
745
|
+
if (isSilent) return;
|
|
746
|
+
try {
|
|
747
|
+
// Afficher un prompt natif pour permettre à l'utilisateur de copier manuellement
|
|
748
|
+
// (solution universelle sans execCommand)
|
|
749
|
+
window.prompt('Copiez le lien ci-dessous (Ctrl/Cmd+C) :', text);
|
|
750
|
+
} catch (err) {
|
|
751
|
+
// Dernier recours: ne rien faire
|
|
752
|
+
}
|
|
753
|
+
},
|
|
754
|
+
|
|
755
|
+
// Partage natif (Web Share API)
|
|
756
|
+
nativeShare: function(data, element) {
|
|
757
|
+
// Vérifier si Web Share API est disponible
|
|
758
|
+
if (navigator.share) {
|
|
759
|
+
navigator.share({
|
|
760
|
+
title: data.text,
|
|
761
|
+
url: data.url
|
|
762
|
+
}).then(function() {
|
|
763
|
+
bbContents.utils.log('Partage natif réussi');
|
|
764
|
+
}).catch(function(error) {
|
|
765
|
+
if (error.name !== 'AbortError') {
|
|
766
|
+
// Fallback vers copie si échec
|
|
767
|
+
bbContents.modules.share.copyToClipboard(data.url, element, false);
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
} else {
|
|
771
|
+
// Fallback si Web Share API non disponible
|
|
772
|
+
bbContents.utils.log('Web Share API non disponible, fallback vers copie');
|
|
773
|
+
this.copyToClipboard(data.url, element, false);
|
|
774
|
+
}
|
|
775
|
+
},
|
|
776
|
+
|
|
777
|
+
// Feedback visuel
|
|
778
|
+
showFeedback: function(element, message) {
|
|
779
|
+
const originalText = element.textContent;
|
|
780
|
+
element.textContent = message;
|
|
781
|
+
element.style.pointerEvents = 'none';
|
|
782
|
+
|
|
783
|
+
setTimeout(function() {
|
|
784
|
+
element.textContent = originalText;
|
|
785
|
+
element.style.pointerEvents = '';
|
|
786
|
+
}, 2000);
|
|
787
|
+
}
|
|
788
788
|
},
|
|
789
789
|
|
|
790
790
|
// Module Current Year (Année courante)
|
|
791
791
|
currentYear: {
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
792
|
+
detect: function(scope) {
|
|
793
|
+
const s = scope || document;
|
|
794
|
+
return s.querySelector(bbContents._attrSelector('current-year')) !== null;
|
|
795
|
+
},
|
|
796
|
+
init: function(root) {
|
|
797
|
+
const scope = root || document;
|
|
798
|
+
if (scope.closest && scope.closest('[data-bb-disable]')) return;
|
|
799
|
+
const elements = scope.querySelectorAll(bbContents._attrSelector('current-year'));
|
|
800
800
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
801
|
+
const year = String(new Date().getFullYear());
|
|
802
|
+
elements.forEach(function(element) {
|
|
803
|
+
if (element.bbProcessed) return;
|
|
804
|
+
element.bbProcessed = true;
|
|
805
805
|
|
|
806
806
|
const customFormat = bbContents._getAttr(element, 'bb-current-year-format');
|
|
807
807
|
const prefix = bbContents._getAttr(element, 'bb-current-year-prefix');
|
|
808
808
|
const suffix = bbContents._getAttr(element, 'bb-current-year-suffix');
|
|
809
809
|
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
810
|
+
if (customFormat && customFormat.includes('{year}')) {
|
|
811
|
+
element.textContent = customFormat.replace('{year}', year);
|
|
812
|
+
} else if (prefix || suffix) {
|
|
813
|
+
element.textContent = prefix + year + suffix;
|
|
814
|
+
} else {
|
|
815
|
+
element.textContent = year;
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
818
|
|
|
819
|
-
|
|
820
|
-
|
|
819
|
+
bbContents.utils.log('Module CurrentYear initialisé:', elements.length, 'éléments');
|
|
820
|
+
}
|
|
821
821
|
},
|
|
822
822
|
|
|
823
823
|
// Module Reading Time (Temps de lecture)
|
|
824
824
|
readingTime: {
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
825
|
+
detect: function(scope) {
|
|
826
|
+
const s = scope || document;
|
|
827
|
+
return s.querySelector(bbContents._attrSelector('reading-time')) !== null;
|
|
828
|
+
},
|
|
829
|
+
init: function(root) {
|
|
830
|
+
const scope = root || document;
|
|
831
|
+
if (scope.closest && scope.closest('[data-bb-disable]')) return;
|
|
832
|
+
const elements = scope.querySelectorAll(bbContents._attrSelector('reading-time'));
|
|
833
833
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
834
|
+
elements.forEach(function(element) {
|
|
835
|
+
if (element.bbProcessed) return;
|
|
836
|
+
element.bbProcessed = true;
|
|
837
837
|
|
|
838
838
|
const targetSelector = bbContents._getAttr(element, 'bb-reading-time-target');
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
839
|
+
const speedAttr = bbContents._getAttr(element, 'bb-reading-time-speed');
|
|
840
|
+
const imageSpeedAttr = bbContents._getAttr(element, 'bb-reading-time-image-speed');
|
|
841
|
+
const format = bbContents._getAttr(element, 'bb-reading-time-format') || '{minutes} min';
|
|
842
842
|
|
|
843
|
-
|
|
844
|
-
|
|
843
|
+
const wordsPerMinute = Number(speedAttr) > 0 ? Number(speedAttr) : 230;
|
|
844
|
+
const secondsPerImage = Number(imageSpeedAttr) > 0 ? Number(imageSpeedAttr) : 12;
|
|
845
845
|
|
|
846
846
|
// Validation des valeurs
|
|
847
847
|
if (isNaN(wordsPerMinute) || wordsPerMinute <= 0) {
|
|
@@ -851,143 +851,158 @@
|
|
|
851
851
|
bbContents.utils.log('Temps par image invalide, utilisation de la valeur par défaut (12)');
|
|
852
852
|
}
|
|
853
853
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
854
|
+
let sourceNode = element;
|
|
855
|
+
if (targetSelector) {
|
|
856
|
+
const found = document.querySelector(targetSelector);
|
|
857
|
+
if (found) sourceNode = found;
|
|
858
|
+
}
|
|
859
859
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
860
|
+
const text = (sourceNode.textContent || '').trim();
|
|
861
|
+
const wordCount = text ? (text.match(/\b\w+\b/g) || []).length : 0;
|
|
862
|
+
|
|
863
|
+
// Compter les images dans le contenu ciblé
|
|
864
|
+
const images = sourceNode.querySelectorAll('img');
|
|
865
|
+
const imageCount = images.length;
|
|
866
|
+
const imageTimeInMinutes = (imageCount * secondsPerImage) / 60;
|
|
867
|
+
|
|
868
|
+
let minutesFloat = (wordCount / wordsPerMinute) + imageTimeInMinutes;
|
|
869
|
+
let minutes = Math.ceil(minutesFloat);
|
|
870
870
|
|
|
871
|
-
|
|
872
|
-
|
|
871
|
+
if ((wordCount > 0 || imageCount > 0) && minutes < 1) minutes = 1; // affichage minimal 1 min si contenu non vide
|
|
872
|
+
if (wordCount === 0 && imageCount === 0) minutes = 0;
|
|
873
873
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
874
|
+
const output = format.replace('{minutes}', String(minutes));
|
|
875
|
+
element.textContent = output;
|
|
876
|
+
});
|
|
877
877
|
|
|
878
|
-
|
|
879
|
-
|
|
878
|
+
bbContents.utils.log('Module ReadingTime initialisé:', elements.length, 'éléments');
|
|
879
|
+
}
|
|
880
880
|
},
|
|
881
881
|
|
|
882
882
|
// Module Favicon (Favicon Dynamique)
|
|
883
883
|
favicon: {
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
884
|
+
originalFavicon: null,
|
|
885
|
+
|
|
886
|
+
// Détection
|
|
887
|
+
detect: function(scope) {
|
|
888
|
+
const s = scope || document;
|
|
889
|
+
return s.querySelector(bbContents._attrSelector('favicon')) !== null;
|
|
890
|
+
},
|
|
891
|
+
|
|
892
|
+
// Initialisation
|
|
893
|
+
init: function(root) {
|
|
894
|
+
const scope = root || document;
|
|
895
|
+
if (scope.closest && scope.closest('[data-bb-disable]')) return;
|
|
891
896
|
|
|
892
|
-
//
|
|
893
|
-
init: function(root) {
|
|
894
|
-
const scope = root || document;
|
|
895
|
-
if (scope.closest && scope.closest('[data-bb-disable]')) return;
|
|
896
|
-
|
|
897
|
-
// Chercher les éléments avec bb-favicon ou bb-favicon-dark
|
|
897
|
+
// Chercher les éléments avec bb-favicon ou bb-favicon-dark
|
|
898
898
|
const elements = scope.querySelectorAll(bbContents._attrSelector('favicon') + ', ' + bbContents._attrSelector('favicon-dark'));
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
// Sauvegarder le favicon original
|
|
902
|
-
const existingLink = document.querySelector("link[rel*='icon']");
|
|
903
|
-
if (existingLink) {
|
|
904
|
-
this.originalFavicon = existingLink.href;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
// Collecter les URLs depuis tous les éléments
|
|
908
|
-
let faviconUrl = null;
|
|
909
|
-
let darkUrl = null;
|
|
910
|
-
|
|
911
|
-
elements.forEach(function(element) {
|
|
912
|
-
const light = bbContents._getAttr(element, 'bb-favicon') || bbContents._getAttr(element, 'favicon');
|
|
913
|
-
const dark = bbContents._getAttr(element, 'bb-favicon-dark') || bbContents._getAttr(element, 'favicon-dark');
|
|
914
|
-
|
|
915
|
-
if (light) faviconUrl = light;
|
|
916
|
-
if (dark) darkUrl = dark;
|
|
917
|
-
});
|
|
918
|
-
|
|
919
|
-
// Appliquer la logique
|
|
920
|
-
if (faviconUrl && darkUrl) {
|
|
921
|
-
this.setupDarkMode(faviconUrl, darkUrl);
|
|
922
|
-
} else if (faviconUrl) {
|
|
923
|
-
this.setFavicon(faviconUrl);
|
|
924
|
-
bbContents.utils.log('Favicon changé:', faviconUrl);
|
|
925
|
-
}
|
|
926
|
-
},
|
|
899
|
+
if (elements.length === 0) return;
|
|
927
900
|
|
|
928
|
-
//
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
favicon = document.createElement('link');
|
|
934
|
-
favicon.rel = 'icon';
|
|
935
|
-
document.head.appendChild(favicon);
|
|
936
|
-
}
|
|
937
|
-
return favicon;
|
|
938
|
-
},
|
|
901
|
+
// Sauvegarder le favicon original
|
|
902
|
+
const existingLink = document.querySelector("link[rel*='icon']");
|
|
903
|
+
if (existingLink) {
|
|
904
|
+
this.originalFavicon = existingLink.href;
|
|
905
|
+
}
|
|
939
906
|
|
|
940
|
-
//
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
// Ajouter un timestamp pour forcer le rafraîchissement du cache
|
|
945
|
-
const cacheBuster = '?v=' + Date.now();
|
|
946
|
-
const urlWithCacheBuster = url + cacheBuster;
|
|
947
|
-
|
|
948
|
-
const favicon = this.getFaviconElement();
|
|
949
|
-
favicon.href = urlWithCacheBuster;
|
|
950
|
-
},
|
|
907
|
+
// Collecter les URLs depuis tous les éléments
|
|
908
|
+
let faviconUrl = null;
|
|
909
|
+
let darkUrl = null;
|
|
951
910
|
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
const updateFavicon = function(e) {
|
|
956
|
-
const darkModeOn = e ? e.matches : window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
957
|
-
const selectedUrl = darkModeOn ? darkUrl : lightUrl;
|
|
958
|
-
bbContents.modules.favicon.setFavicon(selectedUrl);
|
|
959
|
-
};
|
|
960
|
-
|
|
961
|
-
// Initialiser le favicon au chargement de la page
|
|
962
|
-
updateFavicon();
|
|
911
|
+
elements.forEach(function(element) {
|
|
912
|
+
const light = bbContents._getAttr(element, 'bb-favicon') || bbContents._getAttr(element, 'favicon');
|
|
913
|
+
const dark = bbContents._getAttr(element, 'bb-favicon-dark') || bbContents._getAttr(element, 'favicon-dark');
|
|
963
914
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
915
|
+
if (light) faviconUrl = light;
|
|
916
|
+
if (dark) darkUrl = dark;
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
// Appliquer la logique
|
|
920
|
+
if (faviconUrl && darkUrl) {
|
|
921
|
+
this.setupDarkMode(faviconUrl, darkUrl);
|
|
922
|
+
} else if (faviconUrl) {
|
|
923
|
+
this.setFavicon(faviconUrl);
|
|
924
|
+
bbContents.utils.log('Favicon changé:', faviconUrl);
|
|
925
|
+
}
|
|
926
|
+
},
|
|
927
|
+
|
|
928
|
+
// Helper: Récupérer ou créer un élément favicon
|
|
929
|
+
getFaviconElement: function() {
|
|
930
|
+
let favicon = document.querySelector('link[rel="icon"]') ||
|
|
931
|
+
document.querySelector('link[rel="shortcut icon"]');
|
|
932
|
+
if (!favicon) {
|
|
933
|
+
favicon = document.createElement('link');
|
|
934
|
+
favicon.rel = 'icon';
|
|
935
|
+
document.head.appendChild(favicon);
|
|
936
|
+
}
|
|
937
|
+
return favicon;
|
|
938
|
+
},
|
|
939
|
+
|
|
940
|
+
// Changer le favicon
|
|
941
|
+
setFavicon: function(url) {
|
|
942
|
+
if (!url) return;
|
|
943
|
+
|
|
944
|
+
// Ajouter un timestamp pour forcer le rafraîchissement du cache
|
|
945
|
+
const cacheBuster = '?v=' + Date.now();
|
|
946
|
+
const urlWithCacheBuster = url + cacheBuster;
|
|
947
|
+
|
|
948
|
+
const favicon = this.getFaviconElement();
|
|
949
|
+
favicon.href = urlWithCacheBuster;
|
|
950
|
+
},
|
|
951
|
+
|
|
952
|
+
// Support dark mode (méthode simplifiée et directe)
|
|
953
|
+
setupDarkMode: function(lightUrl, darkUrl) {
|
|
954
|
+
// Fonction pour mettre à jour le favicon selon le mode sombre
|
|
955
|
+
const updateFavicon = function(e) {
|
|
956
|
+
const darkModeOn = e ? e.matches : window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
957
|
+
const selectedUrl = darkModeOn ? darkUrl : lightUrl;
|
|
958
|
+
bbContents.modules.favicon.setFavicon(selectedUrl);
|
|
959
|
+
};
|
|
960
|
+
|
|
961
|
+
// Initialiser le favicon au chargement de la page
|
|
962
|
+
updateFavicon();
|
|
963
|
+
|
|
964
|
+
// Écouter les changements du mode sombre
|
|
965
|
+
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
966
|
+
if (typeof darkModeMediaQuery.addEventListener === 'function') {
|
|
967
|
+
darkModeMediaQuery.addEventListener('change', updateFavicon);
|
|
968
|
+
} else if (typeof darkModeMediaQuery.addListener === 'function') {
|
|
969
|
+
darkModeMediaQuery.addListener(updateFavicon);
|
|
971
970
|
}
|
|
971
|
+
}
|
|
972
972
|
},
|
|
973
973
|
|
|
974
974
|
// Module YouTube Feed
|
|
975
975
|
youtube: {
|
|
976
|
-
// Détection des bots pour éviter les appels API inutiles
|
|
976
|
+
// OPTIMISATION: Détection améliorée des bots pour éviter les appels API inutiles
|
|
977
977
|
isBot: function() {
|
|
978
978
|
const userAgent = navigator.userAgent.toLowerCase();
|
|
979
979
|
const botPatterns = [
|
|
980
980
|
'bot', 'crawler', 'spider', 'scraper', 'googlebot', 'bingbot', 'slurp',
|
|
981
981
|
'duckduckbot', 'baiduspider', 'yandexbot', 'facebookexternalhit', 'twitterbot',
|
|
982
|
-
'linkedinbot', 'whatsapp', 'telegrambot', 'discordbot', 'slackbot'
|
|
982
|
+
'linkedinbot', 'whatsapp', 'telegrambot', 'discordbot', 'slackbot', 'headless',
|
|
983
|
+
'phantom', 'selenium', 'puppeteer', 'playwright', 'lighthouse', 'gtmetrix',
|
|
984
|
+
'pagespeed', 'pingdom', 'uptime', 'monitor', 'check', 'test'
|
|
983
985
|
];
|
|
984
986
|
|
|
985
|
-
|
|
987
|
+
// Vérifications supplémentaires pour détecter plus de bots
|
|
988
|
+
const isBot = botPatterns.some(pattern => userAgent.includes(pattern)) ||
|
|
986
989
|
navigator.webdriver ||
|
|
987
|
-
!navigator.userAgent
|
|
990
|
+
!navigator.userAgent ||
|
|
991
|
+
!window.chrome || // Détecte les navigateurs headless
|
|
992
|
+
navigator.userAgent.includes('HeadlessChrome') ||
|
|
993
|
+
window.navigator.plugins.length === 0; // Bots n'ont souvent pas de plugins
|
|
994
|
+
|
|
995
|
+
if (isBot) {
|
|
996
|
+
// Log pour debug (en mode debug seulement)
|
|
997
|
+
if (bbContents.config.debug) {
|
|
998
|
+
bbContents.utils.log('Bot détecté, pas d\'appel API YouTube');
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
return isBot;
|
|
988
1003
|
},
|
|
989
1004
|
|
|
990
|
-
//
|
|
1005
|
+
// OPTIMISATION: Cache amélioré avec protection contre les appels multiples
|
|
991
1006
|
cache: {
|
|
992
1007
|
get: function(key) {
|
|
993
1008
|
try {
|
|
@@ -997,7 +1012,7 @@
|
|
|
997
1012
|
const data = JSON.parse(cached);
|
|
998
1013
|
const now = Date.now();
|
|
999
1014
|
|
|
1000
|
-
// Cache
|
|
1015
|
+
// OPTIMISATION: Cache plus long (24h maintenu)
|
|
1001
1016
|
if (now - data.timestamp > 24 * 60 * 60 * 1000) {
|
|
1002
1017
|
localStorage.removeItem(key);
|
|
1003
1018
|
return null;
|
|
@@ -1022,7 +1037,22 @@
|
|
|
1022
1037
|
}
|
|
1023
1038
|
},
|
|
1024
1039
|
|
|
1025
|
-
|
|
1040
|
+
// OPTIMISATION: Protection globale contre les appels multiples
|
|
1041
|
+
_activeRequests: new Set(),
|
|
1042
|
+
|
|
1043
|
+
isRequestActive: function(cacheKey) {
|
|
1044
|
+
return this._activeRequests.has(cacheKey);
|
|
1045
|
+
},
|
|
1046
|
+
|
|
1047
|
+
markRequestActive: function(cacheKey) {
|
|
1048
|
+
this._activeRequests.add(cacheKey);
|
|
1049
|
+
},
|
|
1050
|
+
|
|
1051
|
+
markRequestComplete: function(cacheKey) {
|
|
1052
|
+
this._activeRequests.delete(cacheKey);
|
|
1053
|
+
},
|
|
1054
|
+
|
|
1055
|
+
detect: function(scope) {
|
|
1026
1056
|
return scope.querySelector('[bb-youtube-channel]') !== null;
|
|
1027
1057
|
},
|
|
1028
1058
|
|
|
@@ -1075,23 +1105,23 @@
|
|
|
1075
1105
|
}
|
|
1076
1106
|
|
|
1077
1107
|
if (!endpoint) {
|
|
1078
|
-
//
|
|
1108
|
+
// OPTIMISATION: Réduire drastiquement les retries (de 50 à 10)
|
|
1079
1109
|
const retryCount = element.getAttribute('data-youtube-retry-count') || '0';
|
|
1080
1110
|
const retries = parseInt(retryCount);
|
|
1081
1111
|
|
|
1082
|
-
if (retries <
|
|
1112
|
+
if (retries < 10) { // 10 * 500ms = 5 secondes max (plus espacé)
|
|
1083
1113
|
element.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Configuration YouTube en cours...</div>';
|
|
1084
1114
|
element.setAttribute('data-youtube-retry-count', (retries + 1).toString());
|
|
1085
1115
|
|
|
1086
|
-
//
|
|
1116
|
+
// OPTIMISATION: Espacer les retries (500ms au lieu de 100ms)
|
|
1087
1117
|
setTimeout(() => {
|
|
1088
1118
|
this.initElement(element);
|
|
1089
|
-
},
|
|
1119
|
+
}, 500);
|
|
1090
1120
|
return;
|
|
1091
1121
|
} else {
|
|
1092
1122
|
// Timeout après 5 secondes
|
|
1093
1123
|
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>';
|
|
1094
|
-
|
|
1124
|
+
return;
|
|
1095
1125
|
}
|
|
1096
1126
|
}
|
|
1097
1127
|
|
|
@@ -1110,9 +1140,9 @@
|
|
|
1110
1140
|
|
|
1111
1141
|
if (!template) {
|
|
1112
1142
|
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>';
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1116
1146
|
// Cacher le template original
|
|
1117
1147
|
template.style.display = 'none';
|
|
1118
1148
|
|
|
@@ -1129,12 +1159,11 @@
|
|
|
1129
1159
|
return;
|
|
1130
1160
|
}
|
|
1131
1161
|
|
|
1132
|
-
//
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
if (!window[loadingKey]) {
|
|
1162
|
+
// OPTIMISATION: Protection globale contre les appels multiples
|
|
1163
|
+
if (this.isRequestActive(cacheKey)) {
|
|
1164
|
+
// Un appel est déjà en cours pour cette clé, attendre
|
|
1165
|
+
const checkActive = () => {
|
|
1166
|
+
if (!this.isRequestActive(cacheKey)) {
|
|
1138
1167
|
// L'autre appel est terminé, vérifier le cache
|
|
1139
1168
|
const newCachedData = this.cache.get(cacheKey);
|
|
1140
1169
|
if (newCachedData && newCachedData.value) {
|
|
@@ -1143,15 +1172,15 @@
|
|
|
1143
1172
|
container.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Erreur de chargement</div>';
|
|
1144
1173
|
}
|
|
1145
1174
|
} else {
|
|
1146
|
-
setTimeout(
|
|
1175
|
+
setTimeout(checkActive, 200); // Vérifier moins souvent
|
|
1147
1176
|
}
|
|
1148
1177
|
};
|
|
1149
|
-
|
|
1178
|
+
checkActive();
|
|
1150
1179
|
return;
|
|
1151
1180
|
}
|
|
1152
1181
|
|
|
1153
1182
|
// Marquer qu'un appel API est en cours
|
|
1154
|
-
|
|
1183
|
+
this.markRequestActive(cacheKey);
|
|
1155
1184
|
|
|
1156
1185
|
// Afficher un loader
|
|
1157
1186
|
container.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Chargement des vidéos YouTube...</div>';
|
|
@@ -1169,20 +1198,20 @@
|
|
|
1169
1198
|
throw new Error(data.error.message || 'Erreur API YouTube');
|
|
1170
1199
|
}
|
|
1171
1200
|
|
|
1172
|
-
// Sauvegarder en cache pour 24h
|
|
1201
|
+
// OPTIMISATION: Sauvegarder en cache pour 24h
|
|
1173
1202
|
this.cache.set(cacheKey, data);
|
|
1174
1203
|
// Données YouTube mises en cache pour 24h (économie API)
|
|
1175
1204
|
|
|
1176
1205
|
this.generateYouTubeFeed(container, template, data, allowShorts, language);
|
|
1177
1206
|
|
|
1178
|
-
// Libérer le verrou
|
|
1179
|
-
|
|
1207
|
+
// OPTIMISATION: Libérer le verrou avec la nouvelle méthode
|
|
1208
|
+
this.markRequestComplete(cacheKey);
|
|
1180
1209
|
})
|
|
1181
1210
|
.catch(error => {
|
|
1182
1211
|
// Erreur dans le module youtube
|
|
1183
1212
|
|
|
1184
|
-
// Libérer le verrou en cas d'erreur
|
|
1185
|
-
|
|
1213
|
+
// OPTIMISATION: Libérer le verrou en cas d'erreur
|
|
1214
|
+
this.markRequestComplete(cacheKey);
|
|
1186
1215
|
|
|
1187
1216
|
// En cas d'erreur, essayer de récupérer du cache même expiré
|
|
1188
1217
|
const expiredCache = localStorage.getItem(cacheKey);
|
|
@@ -1284,7 +1313,7 @@
|
|
|
1284
1313
|
if (bbContents.config.debug) {
|
|
1285
1314
|
// Thumbnail optimisée
|
|
1286
1315
|
}
|
|
1287
|
-
|
|
1316
|
+
} else {
|
|
1288
1317
|
// Aucune thumbnail disponible
|
|
1289
1318
|
}
|
|
1290
1319
|
}
|
|
@@ -1372,7 +1401,7 @@
|
|
|
1372
1401
|
return textarea.value;
|
|
1373
1402
|
},
|
|
1374
1403
|
|
|
1375
|
-
// Nettoyer le cache expiré
|
|
1404
|
+
// OPTIMISATION: Nettoyer le cache expiré (48h)
|
|
1376
1405
|
cleanCache: function() {
|
|
1377
1406
|
try {
|
|
1378
1407
|
const keys = Object.keys(localStorage);
|
|
@@ -1383,6 +1412,7 @@
|
|
|
1383
1412
|
if (key.startsWith('youtube_')) {
|
|
1384
1413
|
try {
|
|
1385
1414
|
const cached = JSON.parse(localStorage.getItem(key));
|
|
1415
|
+
// OPTIMISATION: Cache 24h maintenu
|
|
1386
1416
|
if (now - cached.timestamp > 24 * 60 * 60 * 1000) {
|
|
1387
1417
|
localStorage.removeItem(key);
|
|
1388
1418
|
cleaned++;
|