@bebranded/bb-contents 1.0.115 → 1.0.117

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 +161 -26
  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.115
4
+ * @version 1.0.117
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.115');
35
+ console.log('bb-contents | v1.0.117');
36
36
 
37
37
  // Configuration
38
38
  const config = {
39
- version: '1.0.115',
39
+ version: '1.0.117',
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)
@@ -413,9 +413,9 @@
413
413
  const originalHeight = img.style.height;
414
414
 
415
415
  img.onload = () => {
416
- // SOLUTION MOBILE SAFARI : Pour les SVG sur mobile Safari, utiliser contain avec optimisations
417
- if (isSVG && isMobile && isSafari) {
418
- // SUR SAFARI MOBILE : Utiliser contain MAIS avec des optimisations pour éviter le flou
416
+ // SOLUTION SAFARI : Pour les SVG sur Safari (desktop et mobile), utiliser contain avec optimisations
417
+ if (isSVG && isSafari) {
418
+ // SUR SAFARI : Utiliser contain MAIS avec des optimisations pour éviter le flou
419
419
  img.style.objectFit = 'contain';
420
420
  img.style.objectPosition = 'center';
421
421
 
@@ -467,8 +467,38 @@
467
467
  parent.style.overflow = 'hidden';
468
468
  parent.style.boxSizing = 'border-box';
469
469
  }
470
+ } else if (isSafari) {
471
+ // SUR SAFARI : Optimisations GPU et dimensions pour toutes les images
472
+ // Restaurer les styles CSS après chargement pour les non-SVG
473
+ if (originalObjectFit && originalObjectFit !== 'none') {
474
+ img.style.objectFit = originalObjectFit;
475
+ }
476
+ if (originalObjectPosition && originalObjectPosition !== 'initial') {
477
+ img.style.objectPosition = originalObjectPosition;
478
+ }
479
+
480
+ // Préserver les dimensions naturelles des images
481
+ if (!originalWidth || originalWidth === '') {
482
+ img.style.width = 'auto';
483
+ }
484
+ if (!originalHeight || originalHeight === '') {
485
+ img.style.height = 'auto';
486
+ }
487
+
488
+ // Optimisations GPU pour Safari (desktop et mobile)
489
+ img.style.webkitBackfaceVisibility = 'hidden';
490
+ img.style.backfaceVisibility = 'hidden';
491
+ img.style.webkitTransform = 'translateZ(0)';
492
+ img.style.transform = 'translateZ(0)';
493
+
494
+ // Conteneur parent pour contraindre
495
+ const parent = img.parentElement;
496
+ if (parent) {
497
+ parent.style.overflow = 'hidden';
498
+ parent.style.boxSizing = 'border-box';
499
+ }
470
500
  } else {
471
- // OPTIMISATION: Restaurer les styles CSS après chargement pour les non-SVG
501
+ // OPTIMISATION: Restaurer les styles CSS après chargement pour les non-SVG (autres navigateurs)
472
502
  if (originalObjectFit && originalObjectFit !== 'none') {
473
503
  img.style.objectFit = originalObjectFit;
474
504
  }
@@ -965,31 +995,146 @@
965
995
  const s = scope || document;
966
996
  return s.querySelector(bbContents._attrSelector('reading-time')) !== null;
967
997
  },
998
+
999
+ // Fonction pour extraire le texte et les images depuis une URL
1000
+ fetchContentFromUrl: function(url, targetSelector) {
1001
+ return fetch(url)
1002
+ .then(function(response) {
1003
+ if (!response.ok) {
1004
+ throw new Error('HTTP ' + response.status);
1005
+ }
1006
+ return response.text();
1007
+ })
1008
+ .then(function(html) {
1009
+ // Parser le HTML pour extraire le contenu principal
1010
+ const parser = new DOMParser();
1011
+ const doc = parser.parseFromString(html, 'text/html');
1012
+
1013
+ let contentNode = null;
1014
+
1015
+ // Priorité 1 : Utiliser le targetSelector si fourni
1016
+ if (targetSelector) {
1017
+ contentNode = doc.querySelector(targetSelector);
1018
+ }
1019
+
1020
+ // Priorité 2 : Si aucun targetSelector ou rien trouvé, utiliser les sélecteurs génériques
1021
+ if (!contentNode) {
1022
+ const contentSelectors = [
1023
+ 'article',
1024
+ '[role="article"]',
1025
+ '.blog-post-content',
1026
+ '.post-content',
1027
+ '.article-content',
1028
+ '.content',
1029
+ 'main article',
1030
+ 'main .w-dyn-bind-empty', // Webflow CMS content
1031
+ 'main .w-richtext' // Webflow rich text
1032
+ ];
1033
+
1034
+ for (let i = 0; i < contentSelectors.length; i++) {
1035
+ contentNode = doc.querySelector(contentSelectors[i]);
1036
+ if (contentNode) break;
1037
+ }
1038
+ }
1039
+
1040
+ // Fallback final : utiliser le body
1041
+ if (!contentNode) {
1042
+ contentNode = doc.body;
1043
+ }
1044
+
1045
+ if (!contentNode) {
1046
+ return { text: '', images: [] };
1047
+ }
1048
+
1049
+ // Extraire le texte et les images
1050
+ const text = contentNode.textContent.trim();
1051
+ const images = contentNode.querySelectorAll('img');
1052
+
1053
+ return { text: text, images: images };
1054
+ });
1055
+ },
1056
+
1057
+ // Fonction pour calculer le temps de lecture
1058
+ calculateReadingTime: function(text, images, wordsPerMinute, secondsPerImage) {
1059
+ const wordCount = text ? (text.match(/\b\w+\b/g) || []).length : 0;
1060
+ const imageCount = images ? images.length : 0;
1061
+ const imageTimeInMinutes = (imageCount * secondsPerImage) / 60;
1062
+
1063
+ let minutesFloat = (wordCount / wordsPerMinute) + imageTimeInMinutes;
1064
+ let minutes = Math.ceil(minutesFloat);
1065
+
1066
+ if ((wordCount > 0 || imageCount > 0) && minutes < 1) minutes = 1;
1067
+ if (wordCount === 0 && imageCount === 0) minutes = 0;
1068
+
1069
+ return minutes;
1070
+ },
1071
+
968
1072
  init: function(root) {
969
1073
  const scope = root || document;
970
1074
  if (scope.closest && scope.closest('[data-bb-disable]')) return;
971
1075
  const elements = scope.querySelectorAll(bbContents._attrSelector('reading-time'));
1076
+ const self = this;
972
1077
 
973
1078
  elements.forEach(function(element) {
974
1079
  if (element.bbProcessed) return;
975
1080
  element.bbProcessed = true;
976
1081
 
977
- const targetSelector = bbContents._getAttr(element, 'bb-reading-time-target');
1082
+ const targetSelector = bbContents._getAttr(element, 'bb-reading-time-target');
978
1083
  const speedAttr = bbContents._getAttr(element, 'bb-reading-time-speed');
979
1084
  const imageSpeedAttr = bbContents._getAttr(element, 'bb-reading-time-image-speed');
980
1085
  const format = bbContents._getAttr(element, 'bb-reading-time-format') || '{minutes} min';
1086
+ const urlAttr = bbContents._getAttr(element, 'bb-reading-time-url');
981
1087
 
982
1088
  const wordsPerMinute = Number(speedAttr) > 0 ? Number(speedAttr) : 230;
983
1089
  const secondsPerImage = Number(imageSpeedAttr) > 0 ? Number(imageSpeedAttr) : 12;
984
1090
 
985
- // Validation des valeurs
986
- if (isNaN(wordsPerMinute) || wordsPerMinute <= 0) {
987
- bbContents.utils.log('Vitesse de lecture invalide, utilisation de la valeur par défaut (230)');
988
- }
989
- if (isNaN(secondsPerImage) || secondsPerImage < 0) {
990
- bbContents.utils.log('Temps par image invalide, utilisation de la valeur par défaut (12)');
991
- }
1091
+ // Validation des valeurs
1092
+ if (isNaN(wordsPerMinute) || wordsPerMinute <= 0) {
1093
+ bbContents.utils.log('Vitesse de lecture invalide, utilisation de la valeur par défaut (230)');
1094
+ }
1095
+ if (isNaN(secondsPerImage) || secondsPerImage < 0) {
1096
+ bbContents.utils.log('Temps par image invalide, utilisation de la valeur par défaut (12)');
1097
+ }
992
1098
 
1099
+ // Détecter l'URL : priorité 1 = lien parent, priorité 2 = attribut
1100
+ let articleUrl = null;
1101
+
1102
+ // Priorité 1 : Chercher un lien parent
1103
+ let linkElement = element.closest('a');
1104
+ if (linkElement && linkElement.href) {
1105
+ articleUrl = linkElement.href;
1106
+ }
1107
+
1108
+ // Priorité 2 : Utiliser l'attribut si pas de lien trouvé
1109
+ if (!articleUrl && urlAttr) {
1110
+ articleUrl = urlAttr;
1111
+ // Si l'URL est relative, la transformer en absolue
1112
+ if (articleUrl && !bbContents.utils.isValidUrl(articleUrl)) {
1113
+ articleUrl = new URL(articleUrl, window.location.origin).href;
1114
+ }
1115
+ }
1116
+
1117
+ // Si une URL est trouvée, faire un fetch
1118
+ if (articleUrl && bbContents.utils.isValidUrl(articleUrl)) {
1119
+ // Afficher un état de chargement (optionnel, on peut laisser vide ou mettre "...")
1120
+ const originalText = element.textContent;
1121
+
1122
+ self.fetchContentFromUrl(articleUrl, targetSelector)
1123
+ .then(function(data) {
1124
+ const minutes = self.calculateReadingTime(data.text, data.images, wordsPerMinute, secondsPerImage);
1125
+ const output = format.replace('{minutes}', String(minutes));
1126
+ element.textContent = output;
1127
+ })
1128
+ .catch(function(error) {
1129
+ bbContents.utils.log('Erreur lors de la récupération du contenu pour reading-time:', error);
1130
+ // En cas d'erreur, on affiche un message ou on laisse le contenu original
1131
+ element.textContent = originalText || '';
1132
+ });
1133
+
1134
+ return; // Sortir de la fonction pour cet élément (traitement async)
1135
+ }
1136
+
1137
+ // Comportement par défaut : analyser le contenu de la page actuelle
993
1138
  let sourceNode = element;
994
1139
  if (targetSelector) {
995
1140
  const found = document.querySelector(targetSelector);
@@ -997,18 +1142,8 @@
997
1142
  }
998
1143
 
999
1144
  const text = (sourceNode.textContent || '').trim();
1000
- const wordCount = text ? (text.match(/\b\w+\b/g) || []).length : 0;
1001
-
1002
- // Compter les images dans le contenu ciblé
1003
1145
  const images = sourceNode.querySelectorAll('img');
1004
- const imageCount = images.length;
1005
- const imageTimeInMinutes = (imageCount * secondsPerImage) / 60;
1006
-
1007
- let minutesFloat = (wordCount / wordsPerMinute) + imageTimeInMinutes;
1008
- let minutes = Math.ceil(minutesFloat);
1009
-
1010
- if ((wordCount > 0 || imageCount > 0) && minutes < 1) minutes = 1; // affichage minimal 1 min si contenu non vide
1011
- if (wordCount === 0 && imageCount === 0) minutes = 0;
1146
+ const minutes = self.calculateReadingTime(text, images, wordsPerMinute, secondsPerImage);
1012
1147
 
1013
1148
  const output = format.replace('{minutes}', String(minutes));
1014
1149
  element.textContent = output;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bebranded/bb-contents",
3
- "version": "1.0.115",
3
+ "version": "1.0.117",
4
4
  "description": "Contenus additionnels français pour Webflow",
5
5
  "main": "bb-contents.js",
6
6
  "scripts": {