@bebranded/bb-contents 1.0.80-beta → 1.0.82-beta

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.
Files changed (2) hide show
  1. package/bb-contents.js +404 -28
  2. package/package.json +1 -1
package/bb-contents.js CHANGED
@@ -34,15 +34,13 @@
34
34
  }
35
35
  window._bbContentsInitialized = true;
36
36
 
37
- // Log de démarrage très visible
38
- console.log('🚀 [BB Contents] DÉMARRAGE v1.0.72-beta - Safari Debug');
39
- console.log('🔍 [BB Contents] User Agent:', navigator.userAgent);
40
- console.log('🔍 [BB Contents] Safari détecté:', /^((?!chrome|android).)*safari/i.test(navigator.userAgent));
37
+ // Log de démarrage simple
38
+ console.log('bb-contents | v1.0.82-beta');
41
39
 
42
40
  // Configuration
43
41
  const config = {
44
- version: '1.0.80-beta',
45
- debug: true, // Debug activé pour diagnostic
42
+ version: '1.0.82-beta',
43
+ debug: false, // Debug désactivé pour rendu propre
46
44
  prefix: 'bb-', // utilisé pour générer les sélecteurs (data-bb-*)
47
45
  youtubeEndpoint: null, // URL du worker YouTube (à définir par l'utilisateur)
48
46
  i18n: {
@@ -247,7 +245,7 @@
247
245
  subtree: true
248
246
  });
249
247
 
250
- this.utils.log('MutationObserver actif');
248
+ this.utils.log('MutationObserver actif');
251
249
  }
252
250
  };
253
251
 
@@ -255,14 +253,14 @@
255
253
  bbContents.modules = {
256
254
  // Module Marquee - Version simplifiée et robuste
257
255
  marquee: {
258
- detect: function(scope) {
259
- const s = scope || document;
256
+ detect: function(scope) {
257
+ const s = scope || document;
260
258
  return s.querySelector(bbContents._attrSelector('marquee')) !== null;
261
- },
262
-
263
- init: function(root) {
264
- const scope = root || document;
265
- if (scope.closest && scope.closest('[data-bb-disable]')) return;
259
+ },
260
+
261
+ init: function(root) {
262
+ const scope = root || document;
263
+ if (scope.closest && scope.closest('[data-bb-disable]')) return;
266
264
  const elements = scope.querySelectorAll(bbContents._attrSelector('marquee'));
267
265
 
268
266
  console.log('🔍 [MARQUEE] Éléments trouvés:', elements.length);
@@ -273,9 +271,8 @@
273
271
  if (element.bbProcessed || element.hasAttribute('data-bb-marquee-processed')) {
274
272
  return;
275
273
  }
276
- element.bbProcessed = true;
277
-
278
- console.log(`🔍 [MARQUEE] Initialisation ${index + 1}/${elements.length}`);
274
+ element.bbProcessed = true;
275
+
279
276
 
280
277
  // Récupérer les options
281
278
  const speed = bbContents._getAttr(element, 'bb-marquee-speed') || '100';
@@ -357,7 +354,6 @@
357
354
  // Calculer les dimensions
358
355
  const contentSize = isVertical ? mainBlock.offsetHeight : mainBlock.offsetWidth;
359
356
 
360
- console.log(`🔍 [MARQUEE] Animation démarrée - contentSize: ${contentSize}px, isVertical: ${isVertical}`);
361
357
 
362
358
  if (contentSize === 0) {
363
359
  console.log('⚠️ [MARQUEE] Contenu vide, retry dans 200ms');
@@ -367,7 +363,6 @@
367
363
 
368
364
  // Détection Safari
369
365
  const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
370
- console.log(`🔍 [MARQUEE] Safari détecté: ${isSafari}`);
371
366
 
372
367
  const gapSize = parseInt(gap);
373
368
  const step = (parseFloat(speed) * (isVertical ? 1.5 : 0.8)) / 60;
@@ -389,14 +384,12 @@
389
384
  initSafariAnimation: function(element, scrollContainer, mainBlock, options) {
390
385
  const { speed, direction, gap, isVertical, useAutoHeight, contentSize, gapSize } = options;
391
386
 
392
- console.log(`🔍 [MARQUEE] Safari Animation - direction: ${direction}, isVertical: ${isVertical}, contentSize: ${contentSize}`);
393
387
 
394
388
  // SOLUTION SAFARI : Forcer le chargement des images avant animation
395
389
  const images = mainBlock.querySelectorAll('img');
396
390
  let imagesLoaded = 0;
397
391
  const totalImages = images.length;
398
392
 
399
- console.log(`🔍 [MARQUEE] Safari - ${totalImages} images détectées`);
400
393
 
401
394
  // Forcer le chargement de toutes les images
402
395
  images.forEach(img => {
@@ -406,17 +399,14 @@
406
399
  }
407
400
  img.onload = () => {
408
401
  imagesLoaded++;
409
- console.log(`🖼️ [MARQUEE] Safari - Image ${imagesLoaded}/${totalImages} chargée`);
410
402
  };
411
403
  img.onerror = () => {
412
404
  imagesLoaded++;
413
- console.log(`❌ [MARQUEE] Safari - Image ${imagesLoaded}/${totalImages} erreur de chargement`);
414
405
  };
415
406
  });
416
407
 
417
408
  // SOLUTION SAFARI MOBILE SIMPLE : Attendre plus longtemps
418
409
  const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
419
- console.log(`🔍 [MARQUEE] Safari - Mobile détecté: ${isMobile}`);
420
410
 
421
411
  // Timeout plus long sur mobile pour laisser le temps aux images de se charger
422
412
  const maxWaitTime = isMobile ? 5000 : 3000; // 5 secondes sur mobile
@@ -497,7 +487,7 @@
497
487
  let currentPosition;
498
488
  if (direction === (isVertical ? 'bottom' : 'right')) {
499
489
  currentPosition = -(finalContentSize + gapSize);
500
- } else {
490
+ } else {
501
491
  currentPosition = 0;
502
492
  }
503
493
 
@@ -521,7 +511,7 @@
521
511
  console.log(`🔄 [MARQUEE] Safari RESET bottom/right: ${currentPosition} → ${-(finalContentSize + gapSize)}`);
522
512
  currentPosition = -(finalContentSize + gapSize);
523
513
  }
524
- } else {
514
+ } else {
525
515
  currentPosition -= step;
526
516
  if (currentPosition <= -(2 * (finalContentSize + gapSize))) {
527
517
  console.log(`🔄 [MARQUEE] Safari RESET top/left: ${currentPosition} → ${-(finalContentSize + gapSize)}`);
@@ -619,6 +609,392 @@
619
609
  }
620
610
  },
621
611
 
612
+ // Module Share (Partage Social)
613
+ share: {
614
+ // Configuration des réseaux
615
+ networks: {
616
+ twitter: function(data) {
617
+ return 'https://twitter.com/intent/tweet?url=' +
618
+ encodeURIComponent(data.url) +
619
+ '&text=' + encodeURIComponent(data.text);
620
+ },
621
+ facebook: function(data) {
622
+ return 'https://facebook.com/sharer/sharer.php?u=' +
623
+ encodeURIComponent(data.url);
624
+ },
625
+ linkedin: function(data) {
626
+ // LinkedIn - URL de partage officielle (2024+)
627
+ return 'https://www.linkedin.com/sharing/share-offsite/?url=' + encodeURIComponent(data.url);
628
+ },
629
+ whatsapp: function(data) {
630
+ return 'https://wa.me/?text=' +
631
+ encodeURIComponent(data.text + ' ' + data.url);
632
+ },
633
+ telegram: function(data) {
634
+ return 'https://t.me/share/url?url=' +
635
+ encodeURIComponent(data.url) +
636
+ '&text=' + encodeURIComponent(data.text);
637
+ },
638
+ email: function(data) {
639
+ return 'mailto:?subject=' +
640
+ encodeURIComponent(data.text) +
641
+ '&body=' + encodeURIComponent(data.text + ' ' + data.url);
642
+ },
643
+ copy: function(data) {
644
+ return 'copy:' + data.url;
645
+ },
646
+ native: function(data) {
647
+ return 'native:' + JSON.stringify(data);
648
+ }
649
+ },
650
+
651
+ // Détection
652
+ detect: function(scope) {
653
+ const s = scope || document;
654
+ return s.querySelector(bbContents._attrSelector('share')) !== null;
655
+ },
656
+
657
+ // Initialisation
658
+ init: function(root) {
659
+ const scope = root || document;
660
+ if (scope.closest && scope.closest('[data-bb-disable]')) return;
661
+ const elements = scope.querySelectorAll(bbContents._attrSelector('share'));
662
+
663
+ elements.forEach(function(element) {
664
+ // Vérifier si déjà traité
665
+ if (element.bbProcessed) return;
666
+ element.bbProcessed = true;
667
+
668
+ // Récupérer les données
669
+ const network = bbContents._getAttr(element, 'bb-share');
670
+ const customUrl = bbContents._getAttr(element, 'bb-url');
671
+ const customText = bbContents._getAttr(element, 'bb-text');
672
+
673
+ // Valeurs par défaut sécurisées
674
+ const data = {
675
+ url: bbContents.utils.isValidUrl(customUrl) ? customUrl : window.location.href,
676
+ text: bbContents.utils.sanitize(customText || document.title || 'Découvrez ce site')
677
+ };
678
+
679
+ // Gestionnaire de clic
680
+ element.addEventListener('click', function(e) {
681
+ e.preventDefault();
682
+ bbContents.modules.share.share(network, data, element);
683
+ });
684
+
685
+ // Accessibilité
686
+ if (element.tagName !== 'BUTTON' && element.tagName !== 'A') {
687
+ element.setAttribute('role', 'button');
688
+ element.setAttribute('tabindex', '0');
689
+
690
+ // Support clavier
691
+ element.addEventListener('keydown', function(e) {
692
+ if (e.key === 'Enter' || e.key === ' ') {
693
+ e.preventDefault();
694
+ bbContents.modules.share.share(network, data, element);
695
+ }
696
+ });
697
+ }
698
+
699
+ element.style.cursor = 'pointer';
700
+ });
701
+
702
+ bbContents.utils.log('Module Share initialisé:', elements.length, 'éléments');
703
+ },
704
+
705
+ // Fonction de partage
706
+ share: function(network, data, element) {
707
+ const networkFunc = this.networks[network];
708
+
709
+ if (!networkFunc) {
710
+ console.error('[BB Contents] Réseau non supporté:', network);
711
+ return;
712
+ }
713
+
714
+ const shareUrl = networkFunc(data);
715
+
716
+ // Cas spécial : copier le lien
717
+ if (shareUrl.startsWith('copy:')) {
718
+ const url = shareUrl.substring(5);
719
+ // Copie silencieuse (pas de feedback visuel)
720
+ this.copyToClipboard(url, element, true);
721
+ return;
722
+ }
723
+
724
+ // Cas spécial : partage natif (Web Share API)
725
+ if (shareUrl.startsWith('native:')) {
726
+ const shareData = JSON.parse(shareUrl.substring(7));
727
+ this.nativeShare(shareData, element);
728
+ return;
729
+ }
730
+
731
+ // Ouvrir popup de partage
732
+ const width = 600;
733
+ const height = 400;
734
+ const left = (window.innerWidth - width) / 2;
735
+ const top = (window.innerHeight - height) / 2;
736
+
737
+ window.open(
738
+ shareUrl,
739
+ 'bbshare',
740
+ 'width=' + width + ',height=' + height + ',left=' + left + ',top=' + top + ',noopener,noreferrer'
741
+ );
742
+
743
+ bbContents.utils.log('Partage sur', network, data);
744
+ },
745
+
746
+ // Copier dans le presse-papier
747
+ copyToClipboard: function(text, element, silent) {
748
+ const isSilent = !!silent;
749
+ // Méthode moderne
750
+ if (navigator.clipboard && navigator.clipboard.writeText) {
751
+ navigator.clipboard.writeText(text).then(function() {
752
+ if (!isSilent) {
753
+ bbContents.modules.share.showFeedback(element, '✓ ' + (bbContents.config.i18n.copied || 'Lien copié !'));
754
+ }
755
+ }).catch(function() {
756
+ bbContents.modules.share.fallbackCopy(text, element, isSilent);
757
+ });
758
+ } else {
759
+ // Fallback pour environnements sans Clipboard API
760
+ this.fallbackCopy(text, element, isSilent);
761
+ }
762
+ },
763
+
764
+ // Fallback copie
765
+ fallbackCopy: function(text, element, silent) {
766
+ const isSilent = !!silent;
767
+ // Pas de UI si silencieux (exigence produit)
768
+ if (isSilent) return;
769
+ try {
770
+ // Afficher un prompt natif pour permettre à l'utilisateur de copier manuellement
771
+ // (solution universelle sans execCommand)
772
+ window.prompt('Copiez le lien ci-dessous (Ctrl/Cmd+C) :', text);
773
+ } catch (err) {
774
+ // Dernier recours: ne rien faire
775
+ }
776
+ },
777
+
778
+ // Partage natif (Web Share API)
779
+ nativeShare: function(data, element) {
780
+ // Vérifier si Web Share API est disponible
781
+ if (navigator.share) {
782
+ navigator.share({
783
+ title: data.text,
784
+ url: data.url
785
+ }).then(function() {
786
+ bbContents.utils.log('Partage natif réussi');
787
+ }).catch(function(error) {
788
+ if (error.name !== 'AbortError') {
789
+ console.error('[BB Contents] Erreur partage natif:', error);
790
+ // Fallback vers copie si échec
791
+ bbContents.modules.share.copyToClipboard(data.url, element, false);
792
+ }
793
+ });
794
+ } else {
795
+ // Fallback si Web Share API non disponible
796
+ bbContents.utils.log('Web Share API non disponible, fallback vers copie');
797
+ this.copyToClipboard(data.url, element, false);
798
+ }
799
+ },
800
+
801
+ // Feedback visuel
802
+ showFeedback: function(element, message) {
803
+ const originalText = element.textContent;
804
+ element.textContent = message;
805
+ element.style.pointerEvents = 'none';
806
+
807
+ setTimeout(function() {
808
+ element.textContent = originalText;
809
+ element.style.pointerEvents = '';
810
+ }, 2000);
811
+ }
812
+ },
813
+
814
+ // Module Current Year (Année courante)
815
+ currentYear: {
816
+ detect: function(scope) {
817
+ const s = scope || document;
818
+ return s.querySelector(bbContents._attrSelector('current-year')) !== null;
819
+ },
820
+ init: function(root) {
821
+ const scope = root || document;
822
+ if (scope.closest && scope.closest('[data-bb-disable]')) return;
823
+ const elements = scope.querySelectorAll(bbContents._attrSelector('current-year'));
824
+
825
+ const year = String(new Date().getFullYear());
826
+ elements.forEach(function(element) {
827
+ if (element.bbProcessed) return;
828
+ element.bbProcessed = true;
829
+
830
+ const customFormat = bbContents._getAttr(element, 'bb-current-year-format');
831
+ const prefix = bbContents._getAttr(element, 'bb-current-year-prefix');
832
+ const suffix = bbContents._getAttr(element, 'bb-current-year-suffix');
833
+
834
+ if (customFormat && customFormat.includes('{year}')) {
835
+ element.textContent = customFormat.replace('{year}', year);
836
+ } else if (prefix || suffix) {
837
+ element.textContent = prefix + year + suffix;
838
+ } else {
839
+ element.textContent = year;
840
+ }
841
+ });
842
+
843
+ bbContents.utils.log('Module CurrentYear initialisé:', elements.length, 'éléments');
844
+ }
845
+ },
846
+
847
+ // Module Reading Time (Temps de lecture)
848
+ readingTime: {
849
+ detect: function(scope) {
850
+ const s = scope || document;
851
+ return s.querySelector(bbContents._attrSelector('reading-time')) !== null;
852
+ },
853
+ init: function(root) {
854
+ const scope = root || document;
855
+ if (scope.closest && scope.closest('[data-bb-disable]')) return;
856
+ const elements = scope.querySelectorAll(bbContents._attrSelector('reading-time'));
857
+
858
+ elements.forEach(function(element) {
859
+ if (element.bbProcessed) return;
860
+ element.bbProcessed = true;
861
+
862
+ const targetSelector = bbContents._getAttr(element, 'bb-reading-time-target');
863
+ const speedAttr = bbContents._getAttr(element, 'bb-reading-time-speed');
864
+ const imageSpeedAttr = bbContents._getAttr(element, 'bb-reading-time-image-speed');
865
+ const format = bbContents._getAttr(element, 'bb-reading-time-format') || '{minutes} min';
866
+
867
+ const wordsPerMinute = Number(speedAttr) > 0 ? Number(speedAttr) : 230;
868
+ const secondsPerImage = Number(imageSpeedAttr) > 0 ? Number(imageSpeedAttr) : 12;
869
+
870
+ // Validation des valeurs
871
+ if (isNaN(wordsPerMinute) || wordsPerMinute <= 0) {
872
+ bbContents.utils.log('Vitesse de lecture invalide, utilisation de la valeur par défaut (230)');
873
+ }
874
+ if (isNaN(secondsPerImage) || secondsPerImage < 0) {
875
+ bbContents.utils.log('Temps par image invalide, utilisation de la valeur par défaut (12)');
876
+ }
877
+
878
+ let sourceNode = element;
879
+ if (targetSelector) {
880
+ const found = document.querySelector(targetSelector);
881
+ if (found) sourceNode = found;
882
+ }
883
+
884
+ const text = (sourceNode.textContent || '').trim();
885
+ const wordCount = text ? (text.match(/\b\w+\b/g) || []).length : 0;
886
+
887
+ // Compter les images dans le contenu ciblé
888
+ const images = sourceNode.querySelectorAll('img');
889
+ const imageCount = images.length;
890
+ const imageTimeInMinutes = (imageCount * secondsPerImage) / 60;
891
+
892
+ let minutesFloat = (wordCount / wordsPerMinute) + imageTimeInMinutes;
893
+ let minutes = Math.ceil(minutesFloat);
894
+
895
+ if ((wordCount > 0 || imageCount > 0) && minutes < 1) minutes = 1; // affichage minimal 1 min si contenu non vide
896
+ if (wordCount === 0 && imageCount === 0) minutes = 0;
897
+
898
+ const output = format.replace('{minutes}', String(minutes));
899
+ element.textContent = output;
900
+ });
901
+
902
+ bbContents.utils.log('Module ReadingTime initialisé:', elements.length, 'éléments');
903
+ }
904
+ },
905
+
906
+ // Module Favicon (Favicon Dynamique)
907
+ favicon: {
908
+ originalFavicon: null,
909
+
910
+ // Détection
911
+ detect: function(scope) {
912
+ const s = scope || document;
913
+ return s.querySelector(bbContents._attrSelector('favicon')) !== null;
914
+ },
915
+
916
+ // Initialisation
917
+ init: function(root) {
918
+ const scope = root || document;
919
+ if (scope.closest && scope.closest('[data-bb-disable]')) return;
920
+
921
+ // Chercher les éléments avec bb-favicon ou bb-favicon-dark
922
+ const elements = scope.querySelectorAll(bbContents._attrSelector('favicon') + ', ' + bbContents._attrSelector('favicon-dark'));
923
+ if (elements.length === 0) return;
924
+
925
+ // Sauvegarder le favicon original
926
+ const existingLink = document.querySelector("link[rel*='icon']");
927
+ if (existingLink) {
928
+ this.originalFavicon = existingLink.href;
929
+ }
930
+
931
+ // Collecter les URLs depuis tous les éléments
932
+ let faviconUrl = null;
933
+ let darkUrl = null;
934
+
935
+ elements.forEach(function(element) {
936
+ const light = bbContents._getAttr(element, 'bb-favicon') || bbContents._getAttr(element, 'favicon');
937
+ const dark = bbContents._getAttr(element, 'bb-favicon-dark') || bbContents._getAttr(element, 'favicon-dark');
938
+
939
+ if (light) faviconUrl = light;
940
+ if (dark) darkUrl = dark;
941
+ });
942
+
943
+ // Appliquer la logique
944
+ if (faviconUrl && darkUrl) {
945
+ this.setupDarkMode(faviconUrl, darkUrl);
946
+ } else if (faviconUrl) {
947
+ this.setFavicon(faviconUrl);
948
+ bbContents.utils.log('Favicon changé:', faviconUrl);
949
+ }
950
+ },
951
+
952
+ // Helper: Récupérer ou créer un élément favicon
953
+ getFaviconElement: function() {
954
+ let favicon = document.querySelector('link[rel="icon"]') ||
955
+ document.querySelector('link[rel="shortcut icon"]');
956
+ if (!favicon) {
957
+ favicon = document.createElement('link');
958
+ favicon.rel = 'icon';
959
+ document.head.appendChild(favicon);
960
+ }
961
+ return favicon;
962
+ },
963
+
964
+ // Changer le favicon
965
+ setFavicon: function(url) {
966
+ if (!url) return;
967
+
968
+ // Ajouter un timestamp pour forcer le rafraîchissement du cache
969
+ const cacheBuster = '?v=' + Date.now();
970
+ const urlWithCacheBuster = url + cacheBuster;
971
+
972
+ const favicon = this.getFaviconElement();
973
+ favicon.href = urlWithCacheBuster;
974
+ },
975
+
976
+ // Support dark mode (méthode simplifiée et directe)
977
+ setupDarkMode: function(lightUrl, darkUrl) {
978
+ // Fonction pour mettre à jour le favicon selon le mode sombre
979
+ const updateFavicon = function(e) {
980
+ const darkModeOn = e ? e.matches : window.matchMedia('(prefers-color-scheme: dark)').matches;
981
+ const selectedUrl = darkModeOn ? darkUrl : lightUrl;
982
+ bbContents.modules.favicon.setFavicon(selectedUrl);
983
+ };
984
+
985
+ // Initialiser le favicon au chargement de la page
986
+ updateFavicon();
987
+
988
+ // Écouter les changements du mode sombre
989
+ const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
990
+ if (typeof darkModeMediaQuery.addEventListener === 'function') {
991
+ darkModeMediaQuery.addEventListener('change', updateFavicon);
992
+ } else if (typeof darkModeMediaQuery.addListener === 'function') {
993
+ darkModeMediaQuery.addListener(updateFavicon);
994
+ }
995
+ }
996
+ },
997
+
622
998
  // Module YouTube Feed
623
999
  youtube: {
624
1000
  // Détection des bots pour éviter les appels API inutiles
@@ -932,8 +1308,8 @@
932
1308
  // Debug: logger la qualité utilisée (en mode debug seulement)
933
1309
  if (bbContents.config.debug) {
934
1310
  // Thumbnail optimisée
935
- }
936
- } else {
1311
+ }
1312
+ } else {
937
1313
  // Aucune thumbnail disponible
938
1314
  }
939
1315
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bebranded/bb-contents",
3
- "version": "1.0.80-beta",
3
+ "version": "1.0.82-beta",
4
4
  "description": "Contenus additionnels français pour Webflow",
5
5
  "main": "bb-contents.js",
6
6
  "scripts": {