@bebranded/bb-contents 1.0.114 → 1.0.116

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 +134 -39
  2. 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.114
4
+ * @version 1.0.116
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.114');
35
+ console.log('bb-contents | v1.0.116');
36
36
 
37
37
  // Configuration
38
38
  const config = {
39
- version: '1.0.114',
39
+ version: '1.0.116',
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)
@@ -390,6 +390,10 @@
390
390
  let imagesLoaded = 0;
391
391
  const totalImages = images.length;
392
392
 
393
+ // DÉCLARER isMobile et isSafari AVANT leur utilisation dans img.onload
394
+ const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
395
+ // Détecter spécifiquement Safari (pas Chrome mobile)
396
+ const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent) || /iPhone|iPad|iPod/.test(navigator.userAgent);
393
397
 
394
398
  // OPTIMISATION: Charger les images et appliquer les styles SVG AVANT le clonage
395
399
  // pour éviter les reflows qui causent la saccade de l'animation
@@ -488,11 +492,6 @@
488
492
  };
489
493
  });
490
494
 
491
- // SOLUTION SAFARI MOBILE SIMPLE : Attendre plus longtemps
492
- const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
493
- // Détecter spécifiquement Safari (pas Chrome mobile)
494
- const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent) || /iPhone|iPad|iPod/.test(navigator.userAgent);
495
-
496
495
  // Timeout plus long sur mobile pour laisser le temps aux images de se charger
497
496
  const maxWaitTime = isMobile ? 5000 : 3000; // 5 secondes sur mobile
498
497
  let waitTimeout = 0;
@@ -531,7 +530,9 @@
531
530
  }
532
531
  } else if (waitTimeout >= maxWaitTime) {
533
532
  // Timeout atteint : forcer le démarrage mais c'est un fallback
534
- console.warn('[MARQUEE] Timeout atteint, certaines images peuvent ne pas être chargées');
533
+ if (bbContents.config.debug) {
534
+ console.warn('[MARQUEE] Timeout atteint, certaines images peuvent ne pas être chargées');
535
+ }
535
536
  const renderDelay = isSafari && isMobile ? 1500 : (isMobile ? 1000 : 200);
536
537
  setTimeout(() => {
537
538
  startSafariAnimation();
@@ -559,14 +560,6 @@
559
560
  // Cela évite les reflows qui causaient la saccade de l'animation
560
561
  // Les copies héritent automatiquement des styles des images originales
561
562
 
562
- // Vérifier que les images ont une taille visible
563
- let imagesWithSize = 0;
564
- images.forEach(img => {
565
- if (img.offsetWidth > 0 && img.offsetHeight > 0) {
566
- imagesWithSize++;
567
- }
568
- });
569
-
570
563
  // Recalculer la taille après chargement des images
571
564
  const newContentSize = isVertical ? mainBlock.offsetHeight : mainBlock.offsetWidth;
572
565
 
@@ -623,12 +616,9 @@
623
616
  }
624
617
 
625
618
  // Fonction d'animation Safari optimisée
626
- let frameCount = 0;
627
619
  let lastTime = performance.now();
628
620
  const animate = (currentTime) => {
629
621
  if (!isPaused) {
630
- frameCount++;
631
-
632
622
  // OPTIMISATION SAFARI MOBILE : Utiliser le temps réel pour une animation plus fluide
633
623
  const deltaTime = isSafari && isMobile ? (currentTime - lastTime) / 16.67 : 1;
634
624
  lastTime = currentTime;
@@ -975,31 +965,146 @@
975
965
  const s = scope || document;
976
966
  return s.querySelector(bbContents._attrSelector('reading-time')) !== null;
977
967
  },
968
+
969
+ // Fonction pour extraire le texte et les images depuis une URL
970
+ fetchContentFromUrl: function(url, targetSelector) {
971
+ return fetch(url)
972
+ .then(function(response) {
973
+ if (!response.ok) {
974
+ throw new Error('HTTP ' + response.status);
975
+ }
976
+ return response.text();
977
+ })
978
+ .then(function(html) {
979
+ // Parser le HTML pour extraire le contenu principal
980
+ const parser = new DOMParser();
981
+ const doc = parser.parseFromString(html, 'text/html');
982
+
983
+ let contentNode = null;
984
+
985
+ // Priorité 1 : Utiliser le targetSelector si fourni
986
+ if (targetSelector) {
987
+ contentNode = doc.querySelector(targetSelector);
988
+ }
989
+
990
+ // Priorité 2 : Si aucun targetSelector ou rien trouvé, utiliser les sélecteurs génériques
991
+ if (!contentNode) {
992
+ const contentSelectors = [
993
+ 'article',
994
+ '[role="article"]',
995
+ '.blog-post-content',
996
+ '.post-content',
997
+ '.article-content',
998
+ '.content',
999
+ 'main article',
1000
+ 'main .w-dyn-bind-empty', // Webflow CMS content
1001
+ 'main .w-richtext' // Webflow rich text
1002
+ ];
1003
+
1004
+ for (let i = 0; i < contentSelectors.length; i++) {
1005
+ contentNode = doc.querySelector(contentSelectors[i]);
1006
+ if (contentNode) break;
1007
+ }
1008
+ }
1009
+
1010
+ // Fallback final : utiliser le body
1011
+ if (!contentNode) {
1012
+ contentNode = doc.body;
1013
+ }
1014
+
1015
+ if (!contentNode) {
1016
+ return { text: '', images: [] };
1017
+ }
1018
+
1019
+ // Extraire le texte et les images
1020
+ const text = contentNode.textContent.trim();
1021
+ const images = contentNode.querySelectorAll('img');
1022
+
1023
+ return { text: text, images: images };
1024
+ });
1025
+ },
1026
+
1027
+ // Fonction pour calculer le temps de lecture
1028
+ calculateReadingTime: function(text, images, wordsPerMinute, secondsPerImage) {
1029
+ const wordCount = text ? (text.match(/\b\w+\b/g) || []).length : 0;
1030
+ const imageCount = images ? images.length : 0;
1031
+ const imageTimeInMinutes = (imageCount * secondsPerImage) / 60;
1032
+
1033
+ let minutesFloat = (wordCount / wordsPerMinute) + imageTimeInMinutes;
1034
+ let minutes = Math.ceil(minutesFloat);
1035
+
1036
+ if ((wordCount > 0 || imageCount > 0) && minutes < 1) minutes = 1;
1037
+ if (wordCount === 0 && imageCount === 0) minutes = 0;
1038
+
1039
+ return minutes;
1040
+ },
1041
+
978
1042
  init: function(root) {
979
1043
  const scope = root || document;
980
1044
  if (scope.closest && scope.closest('[data-bb-disable]')) return;
981
1045
  const elements = scope.querySelectorAll(bbContents._attrSelector('reading-time'));
1046
+ const self = this;
982
1047
 
983
1048
  elements.forEach(function(element) {
984
1049
  if (element.bbProcessed) return;
985
1050
  element.bbProcessed = true;
986
1051
 
987
- const targetSelector = bbContents._getAttr(element, 'bb-reading-time-target');
1052
+ const targetSelector = bbContents._getAttr(element, 'bb-reading-time-target');
988
1053
  const speedAttr = bbContents._getAttr(element, 'bb-reading-time-speed');
989
1054
  const imageSpeedAttr = bbContents._getAttr(element, 'bb-reading-time-image-speed');
990
1055
  const format = bbContents._getAttr(element, 'bb-reading-time-format') || '{minutes} min';
1056
+ const urlAttr = bbContents._getAttr(element, 'bb-reading-time-url');
991
1057
 
992
1058
  const wordsPerMinute = Number(speedAttr) > 0 ? Number(speedAttr) : 230;
993
1059
  const secondsPerImage = Number(imageSpeedAttr) > 0 ? Number(imageSpeedAttr) : 12;
994
1060
 
995
- // Validation des valeurs
996
- if (isNaN(wordsPerMinute) || wordsPerMinute <= 0) {
997
- bbContents.utils.log('Vitesse de lecture invalide, utilisation de la valeur par défaut (230)');
998
- }
999
- if (isNaN(secondsPerImage) || secondsPerImage < 0) {
1000
- bbContents.utils.log('Temps par image invalide, utilisation de la valeur par défaut (12)');
1001
- }
1061
+ // Validation des valeurs
1062
+ if (isNaN(wordsPerMinute) || wordsPerMinute <= 0) {
1063
+ bbContents.utils.log('Vitesse de lecture invalide, utilisation de la valeur par défaut (230)');
1064
+ }
1065
+ if (isNaN(secondsPerImage) || secondsPerImage < 0) {
1066
+ bbContents.utils.log('Temps par image invalide, utilisation de la valeur par défaut (12)');
1067
+ }
1002
1068
 
1069
+ // Détecter l'URL : priorité 1 = lien parent, priorité 2 = attribut
1070
+ let articleUrl = null;
1071
+
1072
+ // Priorité 1 : Chercher un lien parent
1073
+ let linkElement = element.closest('a');
1074
+ if (linkElement && linkElement.href) {
1075
+ articleUrl = linkElement.href;
1076
+ }
1077
+
1078
+ // Priorité 2 : Utiliser l'attribut si pas de lien trouvé
1079
+ if (!articleUrl && urlAttr) {
1080
+ articleUrl = urlAttr;
1081
+ // Si l'URL est relative, la transformer en absolue
1082
+ if (articleUrl && !bbContents.utils.isValidUrl(articleUrl)) {
1083
+ articleUrl = new URL(articleUrl, window.location.origin).href;
1084
+ }
1085
+ }
1086
+
1087
+ // Si une URL est trouvée, faire un fetch
1088
+ if (articleUrl && bbContents.utils.isValidUrl(articleUrl)) {
1089
+ // Afficher un état de chargement (optionnel, on peut laisser vide ou mettre "...")
1090
+ const originalText = element.textContent;
1091
+
1092
+ self.fetchContentFromUrl(articleUrl, targetSelector)
1093
+ .then(function(data) {
1094
+ const minutes = self.calculateReadingTime(data.text, data.images, wordsPerMinute, secondsPerImage);
1095
+ const output = format.replace('{minutes}', String(minutes));
1096
+ element.textContent = output;
1097
+ })
1098
+ .catch(function(error) {
1099
+ bbContents.utils.log('Erreur lors de la récupération du contenu pour reading-time:', error);
1100
+ // En cas d'erreur, on affiche un message ou on laisse le contenu original
1101
+ element.textContent = originalText || '';
1102
+ });
1103
+
1104
+ return; // Sortir de la fonction pour cet élément (traitement async)
1105
+ }
1106
+
1107
+ // Comportement par défaut : analyser le contenu de la page actuelle
1003
1108
  let sourceNode = element;
1004
1109
  if (targetSelector) {
1005
1110
  const found = document.querySelector(targetSelector);
@@ -1007,18 +1112,8 @@
1007
1112
  }
1008
1113
 
1009
1114
  const text = (sourceNode.textContent || '').trim();
1010
- const wordCount = text ? (text.match(/\b\w+\b/g) || []).length : 0;
1011
-
1012
- // Compter les images dans le contenu ciblé
1013
1115
  const images = sourceNode.querySelectorAll('img');
1014
- const imageCount = images.length;
1015
- const imageTimeInMinutes = (imageCount * secondsPerImage) / 60;
1016
-
1017
- let minutesFloat = (wordCount / wordsPerMinute) + imageTimeInMinutes;
1018
- let minutes = Math.ceil(minutesFloat);
1019
-
1020
- if ((wordCount > 0 || imageCount > 0) && minutes < 1) minutes = 1; // affichage minimal 1 min si contenu non vide
1021
- if (wordCount === 0 && imageCount === 0) minutes = 0;
1116
+ const minutes = self.calculateReadingTime(text, images, wordsPerMinute, secondsPerImage);
1022
1117
 
1023
1118
  const output = format.replace('{minutes}', String(minutes));
1024
1119
  element.textContent = output;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bebranded/bb-contents",
3
- "version": "1.0.114",
3
+ "version": "1.0.116",
4
4
  "description": "Contenus additionnels français pour Webflow",
5
5
  "main": "bb-contents.js",
6
6
  "scripts": {