@bebranded/bb-contents 1.0.135 → 1.0.136
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 +148 -28
- 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.136
|
|
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.136');
|
|
36
36
|
|
|
37
37
|
// Configuration
|
|
38
38
|
const config = {
|
|
39
|
-
version: '1.0.
|
|
39
|
+
version: '1.0.136',
|
|
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 {
|
|
@@ -1046,9 +1078,11 @@
|
|
|
1046
1078
|
return response.text();
|
|
1047
1079
|
})
|
|
1048
1080
|
.then(function(html) {
|
|
1081
|
+
// Nettoyer le HTML avant parsing (supprimer scripts et événements)
|
|
1082
|
+
const cleanedHtml = bbContents.utils.cleanHtml(html);
|
|
1049
1083
|
// Parser le HTML pour extraire le contenu principal
|
|
1050
1084
|
const parser = new DOMParser();
|
|
1051
|
-
const doc = parser.parseFromString(
|
|
1085
|
+
const doc = parser.parseFromString(cleanedHtml, 'text/html');
|
|
1052
1086
|
|
|
1053
1087
|
let contentNode = null;
|
|
1054
1088
|
|
|
@@ -1151,7 +1185,30 @@
|
|
|
1151
1185
|
articleUrl = urlAttr;
|
|
1152
1186
|
// Si l'URL est relative, la transformer en absolue
|
|
1153
1187
|
if (articleUrl && !bbContents.utils.isValidUrl(articleUrl)) {
|
|
1154
|
-
|
|
1188
|
+
try {
|
|
1189
|
+
const url = new URL(articleUrl, window.location.origin);
|
|
1190
|
+
// Vérifier que c'est bien le même domaine (sécurité)
|
|
1191
|
+
if (url.origin !== window.location.origin) {
|
|
1192
|
+
// URL externe non autorisée, ignorer
|
|
1193
|
+
articleUrl = null;
|
|
1194
|
+
} else {
|
|
1195
|
+
articleUrl = url.href;
|
|
1196
|
+
}
|
|
1197
|
+
} catch (e) {
|
|
1198
|
+
// URL invalide, ignorer
|
|
1199
|
+
articleUrl = null;
|
|
1200
|
+
}
|
|
1201
|
+
} else if (articleUrl && bbContents.utils.isValidUrl(articleUrl)) {
|
|
1202
|
+
// Vérifier que l'URL absolue est du même domaine
|
|
1203
|
+
try {
|
|
1204
|
+
const url = new URL(articleUrl);
|
|
1205
|
+
if (url.origin !== window.location.origin) {
|
|
1206
|
+
// URL externe non autorisée, ignorer
|
|
1207
|
+
articleUrl = null;
|
|
1208
|
+
}
|
|
1209
|
+
} catch (e) {
|
|
1210
|
+
articleUrl = null;
|
|
1211
|
+
}
|
|
1155
1212
|
}
|
|
1156
1213
|
}
|
|
1157
1214
|
|
|
@@ -1225,11 +1282,20 @@
|
|
|
1225
1282
|
|
|
1226
1283
|
// Détecter la langue
|
|
1227
1284
|
getLanguage: function(element) {
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1285
|
+
let lang = element.getAttribute('lang');
|
|
1286
|
+
if (!lang && element.closest) {
|
|
1287
|
+
const langElement = element.closest('[lang]');
|
|
1288
|
+
if (langElement) {
|
|
1289
|
+
lang = langElement.getAttribute('lang');
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
if (!lang) {
|
|
1293
|
+
lang = document.documentElement.getAttribute('lang');
|
|
1294
|
+
}
|
|
1295
|
+
if (!lang) {
|
|
1296
|
+
lang = 'fr';
|
|
1297
|
+
}
|
|
1298
|
+
return lang && lang.startsWith('en') ? 'en' : 'fr';
|
|
1233
1299
|
},
|
|
1234
1300
|
|
|
1235
1301
|
// Trouver un pays par code ou nom
|
|
@@ -1351,13 +1417,29 @@
|
|
|
1351
1417
|
|
|
1352
1418
|
// Récupérer les styles visuels du select pour les appliquer au dropdown custom
|
|
1353
1419
|
const selectBgColor = selectComputedStyle.backgroundColor;
|
|
1354
|
-
|
|
1420
|
+
// Construire selectBorder de manière sécurisée
|
|
1421
|
+
let selectBorder = selectComputedStyle.border;
|
|
1422
|
+
if (!selectBorder || selectBorder === 'none' || selectBorder === '0px none rgb(0, 0, 0)') {
|
|
1423
|
+
if (selectComputedStyle.borderWidth && selectComputedStyle.borderStyle && selectComputedStyle.borderColor) {
|
|
1424
|
+
selectBorder = selectComputedStyle.borderWidth + ' ' + selectComputedStyle.borderStyle + ' ' + selectComputedStyle.borderColor;
|
|
1425
|
+
} else {
|
|
1426
|
+
selectBorder = null;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1355
1429
|
const selectBorderColor = selectComputedStyle.borderColor;
|
|
1356
1430
|
const selectBorderRadius = selectComputedStyle.borderRadius;
|
|
1357
1431
|
const selectColor = selectComputedStyle.color;
|
|
1358
1432
|
const selectFontSize = selectComputedStyle.fontSize;
|
|
1359
1433
|
const selectFontFamily = selectComputedStyle.fontFamily;
|
|
1360
|
-
|
|
1434
|
+
// Construire selectPadding de manière sécurisée
|
|
1435
|
+
let selectPadding = selectComputedStyle.padding;
|
|
1436
|
+
if (!selectPadding || selectPadding === '0px') {
|
|
1437
|
+
if (selectComputedStyle.paddingTop && selectComputedStyle.paddingRight && selectComputedStyle.paddingBottom && selectComputedStyle.paddingLeft) {
|
|
1438
|
+
selectPadding = selectComputedStyle.paddingTop + ' ' + selectComputedStyle.paddingRight + ' ' + selectComputedStyle.paddingBottom + ' ' + selectComputedStyle.paddingLeft;
|
|
1439
|
+
} else {
|
|
1440
|
+
selectPadding = null;
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1361
1443
|
|
|
1362
1444
|
// Créer le wrapper avec les dimensions du select
|
|
1363
1445
|
const wrapper = document.createElement('div');
|
|
@@ -1389,7 +1471,8 @@
|
|
|
1389
1471
|
|
|
1390
1472
|
const selectedCountry = defaultCountry;
|
|
1391
1473
|
const selectedName = selectedCountry ? selectedCountry.name[language] : placeholder;
|
|
1392
|
-
|
|
1474
|
+
// Valider le code pays avant utilisation
|
|
1475
|
+
const selectedFlag = selectedCountry && bbContents.utils.isValidCountryCode(selectedCountry.alpha2) ?
|
|
1393
1476
|
'<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
1477
|
'';
|
|
1395
1478
|
|
|
@@ -1502,19 +1585,24 @@
|
|
|
1502
1585
|
}
|
|
1503
1586
|
|
|
1504
1587
|
list.innerHTML = countries.map(function(country) {
|
|
1588
|
+
// Valider le code pays avant utilisation
|
|
1589
|
+
if (!bbContents.utils.isValidCountryCode(country.alpha2)) {
|
|
1590
|
+
return ''; // Ignorer les pays avec codes invalides
|
|
1591
|
+
}
|
|
1505
1592
|
const isSelected = currentSelectedCountry && currentSelectedCountry.alpha2 === country.alpha2;
|
|
1506
1593
|
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
1594
|
// Appliquer uniquement font-size et font-family du select natif (pas la couleur)
|
|
1595
|
+
// Échapper les valeurs CSS pour éviter l'injection
|
|
1508
1596
|
if (selectFontSize) {
|
|
1509
|
-
itemStyle += ' font-size: ' + selectFontSize + ';';
|
|
1597
|
+
itemStyle += ' font-size: ' + bbContents.utils.escapeCss(selectFontSize) + ';';
|
|
1510
1598
|
}
|
|
1511
1599
|
if (selectFontFamily) {
|
|
1512
|
-
itemStyle += ' font-family: ' + selectFontFamily + ';';
|
|
1600
|
+
itemStyle += ' font-family: ' + bbContents.utils.escapeCss(selectFontFamily) + ';';
|
|
1513
1601
|
}
|
|
1514
1602
|
if (isSelected) {
|
|
1515
1603
|
itemStyle += ' background-color: #f3f4f6;';
|
|
1516
1604
|
}
|
|
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>';
|
|
1605
|
+
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
1606
|
}).join('');
|
|
1519
1607
|
|
|
1520
1608
|
// Ajouter hover effect
|
|
@@ -1557,11 +1645,13 @@
|
|
|
1557
1645
|
document.querySelectorAll('.bb-country-select-popover').forEach(function(otherPopover) {
|
|
1558
1646
|
if (otherPopover !== popover && otherPopover.style.display === 'block') {
|
|
1559
1647
|
otherPopover.style.display = 'none';
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
otherTrigger
|
|
1563
|
-
|
|
1564
|
-
|
|
1648
|
+
if (otherPopover.parentElement) {
|
|
1649
|
+
const otherTrigger = otherPopover.parentElement.querySelector('.bb-country-select-trigger');
|
|
1650
|
+
if (otherTrigger) {
|
|
1651
|
+
otherTrigger.setAttribute('aria-expanded', 'false');
|
|
1652
|
+
const otherChevron = otherTrigger.querySelector('svg');
|
|
1653
|
+
if (otherChevron) otherChevron.style.transform = 'rotate(0deg)';
|
|
1654
|
+
}
|
|
1565
1655
|
}
|
|
1566
1656
|
}
|
|
1567
1657
|
});
|
|
@@ -1613,17 +1703,23 @@
|
|
|
1613
1703
|
if (!item) return;
|
|
1614
1704
|
|
|
1615
1705
|
const countryCode = item.dataset.country;
|
|
1706
|
+
// Valider le code pays avant utilisation
|
|
1707
|
+
if (!bbContents.utils.isValidCountryCode(countryCode)) {
|
|
1708
|
+
return; // Code invalide, ignorer
|
|
1709
|
+
}
|
|
1616
1710
|
const country = self.countries.find(function(c) {
|
|
1617
|
-
return c.alpha2 === countryCode;
|
|
1711
|
+
return c.alpha2.toLowerCase() === countryCode.toLowerCase();
|
|
1618
1712
|
});
|
|
1619
1713
|
if (!country) return;
|
|
1620
1714
|
|
|
1621
1715
|
// Mettre à jour le pays sélectionné
|
|
1622
1716
|
currentSelectedCountry = country;
|
|
1623
1717
|
|
|
1624
|
-
// Mettre à jour l'affichage
|
|
1625
|
-
|
|
1626
|
-
|
|
1718
|
+
// Mettre à jour l'affichage (country.alpha2 déjà validé par la recherche)
|
|
1719
|
+
if (bbContents.utils.isValidCountryCode(country.alpha2)) {
|
|
1720
|
+
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;">';
|
|
1721
|
+
nameSpan.textContent = country.name[language];
|
|
1722
|
+
}
|
|
1627
1723
|
|
|
1628
1724
|
// Mettre à jour le select natif avec le nom du pays (pas le code ISO)
|
|
1629
1725
|
const countryName = country.name[language];
|
|
@@ -1637,7 +1733,15 @@
|
|
|
1637
1733
|
const newOption = document.createElement('option');
|
|
1638
1734
|
newOption.value = countryName;
|
|
1639
1735
|
newOption.textContent = countryName;
|
|
1640
|
-
|
|
1736
|
+
// Vérifier s'il y a d'autres options avant de tout supprimer
|
|
1737
|
+
if (element.options.length > 0) {
|
|
1738
|
+
// Supprimer seulement les options vides ou placeholder
|
|
1739
|
+
Array.from(element.options).forEach(function(opt) {
|
|
1740
|
+
if (!opt.value || opt.value === '') {
|
|
1741
|
+
opt.remove();
|
|
1742
|
+
}
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1641
1745
|
element.appendChild(newOption);
|
|
1642
1746
|
}
|
|
1643
1747
|
const changeEvent = new Event('change', { bubbles: true });
|
|
@@ -1969,7 +2073,23 @@
|
|
|
1969
2073
|
container.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Chargement des vidéos YouTube...</div>';
|
|
1970
2074
|
|
|
1971
2075
|
// Appeler l'API via le Worker
|
|
1972
|
-
|
|
2076
|
+
// Valider l'endpoint et le channelId avant fetch
|
|
2077
|
+
if (!endpoint || typeof endpoint !== 'string') {
|
|
2078
|
+
throw new Error('Endpoint YouTube invalide');
|
|
2079
|
+
}
|
|
2080
|
+
// Vérifier que l'endpoint correspond à la configuration
|
|
2081
|
+
if (bbContents.config.youtubeEndpoint && !endpoint.startsWith(bbContents.config.youtubeEndpoint)) {
|
|
2082
|
+
throw new Error('Endpoint YouTube non autorisé');
|
|
2083
|
+
}
|
|
2084
|
+
// Valider le format de channelId (alphanumérique, tirets, underscores)
|
|
2085
|
+
if (!channelId || !/^[a-zA-Z0-9_-]+$/.test(channelId)) {
|
|
2086
|
+
throw new Error('Channel ID invalide');
|
|
2087
|
+
}
|
|
2088
|
+
// Valider videoCount et allowShorts
|
|
2089
|
+
const safeVideoCount = parseInt(videoCount, 10);
|
|
2090
|
+
const safeAllowShorts = allowShorts === true || allowShorts === 'true';
|
|
2091
|
+
|
|
2092
|
+
fetch(`${endpoint}?channelId=${encodeURIComponent(channelId)}&maxResults=${safeVideoCount}&allowShorts=${safeAllowShorts}`)
|
|
1973
2093
|
.then(response => {
|
|
1974
2094
|
if (!response.ok) {
|
|
1975
2095
|
throw new Error(`HTTP ${response.status}`);
|
|
@@ -2009,7 +2129,7 @@
|
|
|
2009
2129
|
}
|
|
2010
2130
|
}
|
|
2011
2131
|
|
|
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>`;
|
|
2132
|
+
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
2133
|
});
|
|
2014
2134
|
},
|
|
2015
2135
|
|