@bebranded/bb-contents 1.0.81-beta → 1.0.82

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 +388 -2
  2. package/package.json +1 -1
package/bb-contents.js CHANGED
@@ -35,11 +35,11 @@
35
35
  window._bbContentsInitialized = true;
36
36
 
37
37
  // Log de démarrage simple
38
- console.log('bb-contents | v1.0.81-beta');
38
+ console.log('bb-contents | v1.0.82');
39
39
 
40
40
  // Configuration
41
41
  const config = {
42
- version: '1.0.81-beta',
42
+ version: '1.0.82',
43
43
  debug: false, // Debug désactivé pour rendu propre
44
44
  prefix: 'bb-', // utilisé pour générer les sélecteurs (data-bb-*)
45
45
  youtubeEndpoint: null, // URL du worker YouTube (à définir par l'utilisateur)
@@ -609,6 +609,392 @@
609
609
  }
610
610
  },
611
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
+
612
998
  // Module YouTube Feed
613
999
  youtube: {
614
1000
  // Détection des bots pour éviter les appels API inutiles
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bebranded/bb-contents",
3
- "version": "1.0.81-beta",
3
+ "version": "1.0.82",
4
4
  "description": "Contenus additionnels français pour Webflow",
5
5
  "main": "bb-contents.js",
6
6
  "scripts": {