@bebranded/bb-contents 1.0.135 → 1.0.137
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 +192 -32
- 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.137
|
|
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.137');
|
|
36
36
|
|
|
37
37
|
// Configuration
|
|
38
38
|
const config = {
|
|
39
|
-
version: '1.0.
|
|
39
|
+
version: '1.0.137',
|
|
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)
|
|
@@ -84,6 +84,38 @@
|
|
|
84
84
|
return div.innerHTML;
|
|
85
85
|
},
|
|
86
86
|
|
|
87
|
+
// Valider un code pays ISO 3166-1 alpha-2 (2 lettres)
|
|
88
|
+
isValidCountryCode: function(code) {
|
|
89
|
+
if (!code || typeof code !== 'string') return false;
|
|
90
|
+
return /^[a-z]{2}$/i.test(code.trim());
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
// Échapper les valeurs CSS pour éviter l'injection CSS
|
|
94
|
+
escapeCss: function(value) {
|
|
95
|
+
if (!value || typeof value !== 'string') return '';
|
|
96
|
+
// Échapper les guillemets et caractères spéciaux
|
|
97
|
+
return value.replace(/[<>"']/g, function(match) {
|
|
98
|
+
const escapeMap = {
|
|
99
|
+
'<': '\\3C ',
|
|
100
|
+
'>': '\\3E ',
|
|
101
|
+
'"': '\\22 ',
|
|
102
|
+
"'": '\\27 '
|
|
103
|
+
};
|
|
104
|
+
return escapeMap[match] || match;
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
// Nettoyer le HTML en supprimant les scripts et événements
|
|
109
|
+
cleanHtml: function(html) {
|
|
110
|
+
if (!html || typeof html !== 'string') return '';
|
|
111
|
+
// Supprimer les scripts
|
|
112
|
+
let cleaned = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
|
|
113
|
+
// Supprimer les attributs d'événements (onclick, onerror, etc.)
|
|
114
|
+
cleaned = cleaned.replace(/\s*on\w+\s*=\s*["'][^"']*["']/gi, '');
|
|
115
|
+
cleaned = cleaned.replace(/\s*on\w+\s*=\s*[^\s>]*/gi, '');
|
|
116
|
+
return cleaned;
|
|
117
|
+
},
|
|
118
|
+
|
|
87
119
|
// Validation des URLs
|
|
88
120
|
isValidUrl: function(string) {
|
|
89
121
|
try {
|
|
@@ -362,10 +394,50 @@
|
|
|
362
394
|
const repeatBlock1 = mainBlock.cloneNode(true);
|
|
363
395
|
const repeatBlock2 = mainBlock.cloneNode(true);
|
|
364
396
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
397
|
+
// Pour les marquees horizontaux, calculer la hauteur avant de mettre en absolute
|
|
398
|
+
if (!isVertical) {
|
|
399
|
+
// Temporairement mettre scrollContainer en relative pour calculer la hauteur
|
|
400
|
+
scrollContainer.style.position = 'relative';
|
|
401
|
+
scrollContainer.appendChild(mainBlock);
|
|
402
|
+
scrollContainer.appendChild(repeatBlock1);
|
|
403
|
+
scrollContainer.appendChild(repeatBlock2);
|
|
404
|
+
mainContainer.appendChild(scrollContainer);
|
|
405
|
+
|
|
406
|
+
// Forcer un reflow pour calculer les dimensions
|
|
407
|
+
void scrollContainer.offsetHeight;
|
|
408
|
+
|
|
409
|
+
// Calculer la hauteur maximale des items
|
|
410
|
+
const items = mainBlock.querySelectorAll('.bb-marquee_item, [role="listitem"], > *');
|
|
411
|
+
let maxHeight = 0;
|
|
412
|
+
items.forEach(function(item) {
|
|
413
|
+
const itemHeight = item.offsetHeight;
|
|
414
|
+
if (itemHeight > maxHeight) {
|
|
415
|
+
maxHeight = itemHeight;
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// Si aucun item trouvé, essayer de prendre la hauteur du scrollContainer
|
|
420
|
+
if (maxHeight === 0) {
|
|
421
|
+
maxHeight = scrollContainer.offsetHeight;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Appliquer la hauteur calculée au mainContainer si elle est valide
|
|
425
|
+
if (maxHeight > 0) {
|
|
426
|
+
mainContainer.style.height = maxHeight + 'px';
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Maintenant mettre scrollContainer en absolute
|
|
430
|
+
scrollContainer.style.position = 'absolute';
|
|
431
|
+
scrollContainer.style.height = '100%';
|
|
432
|
+
scrollContainer.style.top = '0px';
|
|
433
|
+
scrollContainer.style.left = '0px';
|
|
434
|
+
} else {
|
|
435
|
+
// Pour vertical, garder le comportement actuel
|
|
436
|
+
scrollContainer.appendChild(mainBlock);
|
|
437
|
+
scrollContainer.appendChild(repeatBlock1);
|
|
438
|
+
scrollContainer.appendChild(repeatBlock2);
|
|
439
|
+
mainContainer.appendChild(scrollContainer);
|
|
440
|
+
}
|
|
369
441
|
|
|
370
442
|
element.innerHTML = '';
|
|
371
443
|
element.appendChild(mainContainer);
|
|
@@ -1046,9 +1118,11 @@
|
|
|
1046
1118
|
return response.text();
|
|
1047
1119
|
})
|
|
1048
1120
|
.then(function(html) {
|
|
1121
|
+
// Nettoyer le HTML avant parsing (supprimer scripts et événements)
|
|
1122
|
+
const cleanedHtml = bbContents.utils.cleanHtml(html);
|
|
1049
1123
|
// Parser le HTML pour extraire le contenu principal
|
|
1050
1124
|
const parser = new DOMParser();
|
|
1051
|
-
const doc = parser.parseFromString(
|
|
1125
|
+
const doc = parser.parseFromString(cleanedHtml, 'text/html');
|
|
1052
1126
|
|
|
1053
1127
|
let contentNode = null;
|
|
1054
1128
|
|
|
@@ -1151,7 +1225,30 @@
|
|
|
1151
1225
|
articleUrl = urlAttr;
|
|
1152
1226
|
// Si l'URL est relative, la transformer en absolue
|
|
1153
1227
|
if (articleUrl && !bbContents.utils.isValidUrl(articleUrl)) {
|
|
1154
|
-
|
|
1228
|
+
try {
|
|
1229
|
+
const url = new URL(articleUrl, window.location.origin);
|
|
1230
|
+
// Vérifier que c'est bien le même domaine (sécurité)
|
|
1231
|
+
if (url.origin !== window.location.origin) {
|
|
1232
|
+
// URL externe non autorisée, ignorer
|
|
1233
|
+
articleUrl = null;
|
|
1234
|
+
} else {
|
|
1235
|
+
articleUrl = url.href;
|
|
1236
|
+
}
|
|
1237
|
+
} catch (e) {
|
|
1238
|
+
// URL invalide, ignorer
|
|
1239
|
+
articleUrl = null;
|
|
1240
|
+
}
|
|
1241
|
+
} else if (articleUrl && bbContents.utils.isValidUrl(articleUrl)) {
|
|
1242
|
+
// Vérifier que l'URL absolue est du même domaine
|
|
1243
|
+
try {
|
|
1244
|
+
const url = new URL(articleUrl);
|
|
1245
|
+
if (url.origin !== window.location.origin) {
|
|
1246
|
+
// URL externe non autorisée, ignorer
|
|
1247
|
+
articleUrl = null;
|
|
1248
|
+
}
|
|
1249
|
+
} catch (e) {
|
|
1250
|
+
articleUrl = null;
|
|
1251
|
+
}
|
|
1155
1252
|
}
|
|
1156
1253
|
}
|
|
1157
1254
|
|
|
@@ -1225,11 +1322,20 @@
|
|
|
1225
1322
|
|
|
1226
1323
|
// Détecter la langue
|
|
1227
1324
|
getLanguage: function(element) {
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1325
|
+
let lang = element.getAttribute('lang');
|
|
1326
|
+
if (!lang && element.closest) {
|
|
1327
|
+
const langElement = element.closest('[lang]');
|
|
1328
|
+
if (langElement) {
|
|
1329
|
+
lang = langElement.getAttribute('lang');
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
if (!lang) {
|
|
1333
|
+
lang = document.documentElement.getAttribute('lang');
|
|
1334
|
+
}
|
|
1335
|
+
if (!lang) {
|
|
1336
|
+
lang = 'fr';
|
|
1337
|
+
}
|
|
1338
|
+
return lang && lang.startsWith('en') ? 'en' : 'fr';
|
|
1233
1339
|
},
|
|
1234
1340
|
|
|
1235
1341
|
// Trouver un pays par code ou nom
|
|
@@ -1351,13 +1457,29 @@
|
|
|
1351
1457
|
|
|
1352
1458
|
// Récupérer les styles visuels du select pour les appliquer au dropdown custom
|
|
1353
1459
|
const selectBgColor = selectComputedStyle.backgroundColor;
|
|
1354
|
-
|
|
1460
|
+
// Construire selectBorder de manière sécurisée
|
|
1461
|
+
let selectBorder = selectComputedStyle.border;
|
|
1462
|
+
if (!selectBorder || selectBorder === 'none' || selectBorder === '0px none rgb(0, 0, 0)') {
|
|
1463
|
+
if (selectComputedStyle.borderWidth && selectComputedStyle.borderStyle && selectComputedStyle.borderColor) {
|
|
1464
|
+
selectBorder = selectComputedStyle.borderWidth + ' ' + selectComputedStyle.borderStyle + ' ' + selectComputedStyle.borderColor;
|
|
1465
|
+
} else {
|
|
1466
|
+
selectBorder = null;
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1355
1469
|
const selectBorderColor = selectComputedStyle.borderColor;
|
|
1356
1470
|
const selectBorderRadius = selectComputedStyle.borderRadius;
|
|
1357
1471
|
const selectColor = selectComputedStyle.color;
|
|
1358
1472
|
const selectFontSize = selectComputedStyle.fontSize;
|
|
1359
1473
|
const selectFontFamily = selectComputedStyle.fontFamily;
|
|
1360
|
-
|
|
1474
|
+
// Construire selectPadding de manière sécurisée
|
|
1475
|
+
let selectPadding = selectComputedStyle.padding;
|
|
1476
|
+
if (!selectPadding || selectPadding === '0px') {
|
|
1477
|
+
if (selectComputedStyle.paddingTop && selectComputedStyle.paddingRight && selectComputedStyle.paddingBottom && selectComputedStyle.paddingLeft) {
|
|
1478
|
+
selectPadding = selectComputedStyle.paddingTop + ' ' + selectComputedStyle.paddingRight + ' ' + selectComputedStyle.paddingBottom + ' ' + selectComputedStyle.paddingLeft;
|
|
1479
|
+
} else {
|
|
1480
|
+
selectPadding = null;
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1361
1483
|
|
|
1362
1484
|
// Créer le wrapper avec les dimensions du select
|
|
1363
1485
|
const wrapper = document.createElement('div');
|
|
@@ -1389,7 +1511,8 @@
|
|
|
1389
1511
|
|
|
1390
1512
|
const selectedCountry = defaultCountry;
|
|
1391
1513
|
const selectedName = selectedCountry ? selectedCountry.name[language] : placeholder;
|
|
1392
|
-
|
|
1514
|
+
// Valider le code pays avant utilisation
|
|
1515
|
+
const selectedFlag = selectedCountry && bbContents.utils.isValidCountryCode(selectedCountry.alpha2) ?
|
|
1393
1516
|
'<img src="https://hatscripts.github.io/circle-flags/flags/' + selectedCountry.alpha2.toLowerCase() + '.svg" alt="' + bbContents.utils.sanitize(selectedCountry.name[language]) + '" style="width: 20px; height: 20px; border-radius: 50%; object-fit: cover; flex-shrink: 0;">' :
|
|
1394
1517
|
'';
|
|
1395
1518
|
|
|
@@ -1502,19 +1625,24 @@
|
|
|
1502
1625
|
}
|
|
1503
1626
|
|
|
1504
1627
|
list.innerHTML = countries.map(function(country) {
|
|
1628
|
+
// Valider le code pays avant utilisation
|
|
1629
|
+
if (!bbContents.utils.isValidCountryCode(country.alpha2)) {
|
|
1630
|
+
return ''; // Ignorer les pays avec codes invalides
|
|
1631
|
+
}
|
|
1505
1632
|
const isSelected = currentSelectedCountry && currentSelectedCountry.alpha2 === country.alpha2;
|
|
1506
1633
|
let itemStyle = 'display: flex; align-items: center; gap: 8px; padding: 8px 12px; cursor: pointer; transition: background-color 0.15s; min-height: 36px; box-sizing: border-box;';
|
|
1507
1634
|
// Appliquer uniquement font-size et font-family du select natif (pas la couleur)
|
|
1635
|
+
// Échapper les valeurs CSS pour éviter l'injection
|
|
1508
1636
|
if (selectFontSize) {
|
|
1509
|
-
itemStyle += ' font-size: ' + selectFontSize + ';';
|
|
1637
|
+
itemStyle += ' font-size: ' + bbContents.utils.escapeCss(selectFontSize) + ';';
|
|
1510
1638
|
}
|
|
1511
1639
|
if (selectFontFamily) {
|
|
1512
|
-
itemStyle += ' font-family: ' + selectFontFamily + ';';
|
|
1640
|
+
itemStyle += ' font-family: ' + bbContents.utils.escapeCss(selectFontFamily) + ';';
|
|
1513
1641
|
}
|
|
1514
1642
|
if (isSelected) {
|
|
1515
1643
|
itemStyle += ' background-color: #f3f4f6;';
|
|
1516
1644
|
}
|
|
1517
|
-
return '<div class="bb-country-item" data-country="' + country.alpha2 + '" role="option" aria-selected="' + (isSelected ? 'true' : 'false') + '" style="' + itemStyle + '"><img src="https://hatscripts.github.io/circle-flags/flags/' + country.alpha2.toLowerCase() + '.svg" alt="' + bbContents.utils.sanitize(country.name[language]) + '" style="width: 20px; height: 20px; border-radius: 50%; object-fit: cover; flex-shrink: 0;"><span style="line-height: 1.2;">' + bbContents.utils.sanitize(country.name[language]) + '</span></div>';
|
|
1645
|
+
return '<div class="bb-country-item" data-country="' + country.alpha2.toLowerCase() + '" role="option" aria-selected="' + (isSelected ? 'true' : 'false') + '" style="' + itemStyle + '"><img src="https://hatscripts.github.io/circle-flags/flags/' + country.alpha2.toLowerCase() + '.svg" alt="' + bbContents.utils.sanitize(country.name[language]) + '" style="width: 20px; height: 20px; border-radius: 50%; object-fit: cover; flex-shrink: 0;"><span style="line-height: 1.2;">' + bbContents.utils.sanitize(country.name[language]) + '</span></div>';
|
|
1518
1646
|
}).join('');
|
|
1519
1647
|
|
|
1520
1648
|
// Ajouter hover effect
|
|
@@ -1557,11 +1685,13 @@
|
|
|
1557
1685
|
document.querySelectorAll('.bb-country-select-popover').forEach(function(otherPopover) {
|
|
1558
1686
|
if (otherPopover !== popover && otherPopover.style.display === 'block') {
|
|
1559
1687
|
otherPopover.style.display = 'none';
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
otherTrigger
|
|
1563
|
-
|
|
1564
|
-
|
|
1688
|
+
if (otherPopover.parentElement) {
|
|
1689
|
+
const otherTrigger = otherPopover.parentElement.querySelector('.bb-country-select-trigger');
|
|
1690
|
+
if (otherTrigger) {
|
|
1691
|
+
otherTrigger.setAttribute('aria-expanded', 'false');
|
|
1692
|
+
const otherChevron = otherTrigger.querySelector('svg');
|
|
1693
|
+
if (otherChevron) otherChevron.style.transform = 'rotate(0deg)';
|
|
1694
|
+
}
|
|
1565
1695
|
}
|
|
1566
1696
|
}
|
|
1567
1697
|
});
|
|
@@ -1613,17 +1743,23 @@
|
|
|
1613
1743
|
if (!item) return;
|
|
1614
1744
|
|
|
1615
1745
|
const countryCode = item.dataset.country;
|
|
1746
|
+
// Valider le code pays avant utilisation
|
|
1747
|
+
if (!bbContents.utils.isValidCountryCode(countryCode)) {
|
|
1748
|
+
return; // Code invalide, ignorer
|
|
1749
|
+
}
|
|
1616
1750
|
const country = self.countries.find(function(c) {
|
|
1617
|
-
return c.alpha2 === countryCode;
|
|
1751
|
+
return c.alpha2.toLowerCase() === countryCode.toLowerCase();
|
|
1618
1752
|
});
|
|
1619
1753
|
if (!country) return;
|
|
1620
1754
|
|
|
1621
1755
|
// Mettre à jour le pays sélectionné
|
|
1622
1756
|
currentSelectedCountry = country;
|
|
1623
1757
|
|
|
1624
|
-
// Mettre à jour l'affichage
|
|
1625
|
-
|
|
1626
|
-
|
|
1758
|
+
// Mettre à jour l'affichage (country.alpha2 déjà validé par la recherche)
|
|
1759
|
+
if (bbContents.utils.isValidCountryCode(country.alpha2)) {
|
|
1760
|
+
flagSpan.innerHTML = '<img src="https://hatscripts.github.io/circle-flags/flags/' + country.alpha2.toLowerCase() + '.svg" alt="' + bbContents.utils.sanitize(country.name[language]) + '" style="width: 20px; height: 20px; border-radius: 50%; object-fit: cover; flex-shrink: 0;">';
|
|
1761
|
+
nameSpan.textContent = country.name[language];
|
|
1762
|
+
}
|
|
1627
1763
|
|
|
1628
1764
|
// Mettre à jour le select natif avec le nom du pays (pas le code ISO)
|
|
1629
1765
|
const countryName = country.name[language];
|
|
@@ -1637,7 +1773,15 @@
|
|
|
1637
1773
|
const newOption = document.createElement('option');
|
|
1638
1774
|
newOption.value = countryName;
|
|
1639
1775
|
newOption.textContent = countryName;
|
|
1640
|
-
|
|
1776
|
+
// Vérifier s'il y a d'autres options avant de tout supprimer
|
|
1777
|
+
if (element.options.length > 0) {
|
|
1778
|
+
// Supprimer seulement les options vides ou placeholder
|
|
1779
|
+
Array.from(element.options).forEach(function(opt) {
|
|
1780
|
+
if (!opt.value || opt.value === '') {
|
|
1781
|
+
opt.remove();
|
|
1782
|
+
}
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1641
1785
|
element.appendChild(newOption);
|
|
1642
1786
|
}
|
|
1643
1787
|
const changeEvent = new Event('change', { bubbles: true });
|
|
@@ -1969,7 +2113,23 @@
|
|
|
1969
2113
|
container.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Chargement des vidéos YouTube...</div>';
|
|
1970
2114
|
|
|
1971
2115
|
// Appeler l'API via le Worker
|
|
1972
|
-
|
|
2116
|
+
// Valider l'endpoint et le channelId avant fetch
|
|
2117
|
+
if (!endpoint || typeof endpoint !== 'string') {
|
|
2118
|
+
throw new Error('Endpoint YouTube invalide');
|
|
2119
|
+
}
|
|
2120
|
+
// Vérifier que l'endpoint correspond à la configuration
|
|
2121
|
+
if (bbContents.config.youtubeEndpoint && !endpoint.startsWith(bbContents.config.youtubeEndpoint)) {
|
|
2122
|
+
throw new Error('Endpoint YouTube non autorisé');
|
|
2123
|
+
}
|
|
2124
|
+
// Valider le format de channelId (alphanumérique, tirets, underscores)
|
|
2125
|
+
if (!channelId || !/^[a-zA-Z0-9_-]+$/.test(channelId)) {
|
|
2126
|
+
throw new Error('Channel ID invalide');
|
|
2127
|
+
}
|
|
2128
|
+
// Valider videoCount et allowShorts
|
|
2129
|
+
const safeVideoCount = parseInt(videoCount, 10);
|
|
2130
|
+
const safeAllowShorts = allowShorts === true || allowShorts === 'true';
|
|
2131
|
+
|
|
2132
|
+
fetch(`${endpoint}?channelId=${encodeURIComponent(channelId)}&maxResults=${safeVideoCount}&allowShorts=${safeAllowShorts}`)
|
|
1973
2133
|
.then(response => {
|
|
1974
2134
|
if (!response.ok) {
|
|
1975
2135
|
throw new Error(`HTTP ${response.status}`);
|
|
@@ -2009,7 +2169,7 @@
|
|
|
2009
2169
|
}
|
|
2010
2170
|
}
|
|
2011
2171
|
|
|
2012
|
-
container.innerHTML = `<div style="padding: 20px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; color: #dc2626;"><strong>Erreur de chargement</strong><br>${error.message}</div>`;
|
|
2172
|
+
container.innerHTML = `<div style="padding: 20px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; color: #dc2626;"><strong>Erreur de chargement</strong><br>${bbContents.utils.sanitize(error.message || 'Erreur inconnue')}</div>`;
|
|
2013
2173
|
});
|
|
2014
2174
|
},
|
|
2015
2175
|
|