@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.
- package/bb-contents.js +161 -26
- 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.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.
|
|
35
|
+
console.log('bb-contents | v1.0.117');
|
|
36
36
|
|
|
37
37
|
// Configuration
|
|
38
38
|
const config = {
|
|
39
|
-
version: '1.0.
|
|
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
|
|
417
|
-
if (isSVG &&
|
|
418
|
-
// SUR SAFARI
|
|
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
|
-
|
|
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
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
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
|
|
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;
|