@bebranded/bb-contents 1.0.134 → 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 +157 -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.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,8 +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;';
|
|
1594
|
+
// Appliquer uniquement font-size et font-family du select natif (pas la couleur)
|
|
1595
|
+
// Échapper les valeurs CSS pour éviter l'injection
|
|
1596
|
+
if (selectFontSize) {
|
|
1597
|
+
itemStyle += ' font-size: ' + bbContents.utils.escapeCss(selectFontSize) + ';';
|
|
1598
|
+
}
|
|
1599
|
+
if (selectFontFamily) {
|
|
1600
|
+
itemStyle += ' font-family: ' + bbContents.utils.escapeCss(selectFontFamily) + ';';
|
|
1601
|
+
}
|
|
1602
|
+
if (isSelected) {
|
|
1603
|
+
itemStyle += ' background-color: #f3f4f6;';
|
|
1604
|
+
}
|
|
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>';
|
|
1507
1606
|
}).join('');
|
|
1508
1607
|
|
|
1509
1608
|
// Ajouter hover effect
|
|
@@ -1546,11 +1645,13 @@
|
|
|
1546
1645
|
document.querySelectorAll('.bb-country-select-popover').forEach(function(otherPopover) {
|
|
1547
1646
|
if (otherPopover !== popover && otherPopover.style.display === 'block') {
|
|
1548
1647
|
otherPopover.style.display = 'none';
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
otherTrigger
|
|
1552
|
-
|
|
1553
|
-
|
|
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
|
+
}
|
|
1554
1655
|
}
|
|
1555
1656
|
}
|
|
1556
1657
|
});
|
|
@@ -1602,17 +1703,23 @@
|
|
|
1602
1703
|
if (!item) return;
|
|
1603
1704
|
|
|
1604
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
|
+
}
|
|
1605
1710
|
const country = self.countries.find(function(c) {
|
|
1606
|
-
return c.alpha2 === countryCode;
|
|
1711
|
+
return c.alpha2.toLowerCase() === countryCode.toLowerCase();
|
|
1607
1712
|
});
|
|
1608
1713
|
if (!country) return;
|
|
1609
1714
|
|
|
1610
1715
|
// Mettre à jour le pays sélectionné
|
|
1611
1716
|
currentSelectedCountry = country;
|
|
1612
1717
|
|
|
1613
|
-
// Mettre à jour l'affichage
|
|
1614
|
-
|
|
1615
|
-
|
|
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
|
+
}
|
|
1616
1723
|
|
|
1617
1724
|
// Mettre à jour le select natif avec le nom du pays (pas le code ISO)
|
|
1618
1725
|
const countryName = country.name[language];
|
|
@@ -1626,7 +1733,15 @@
|
|
|
1626
1733
|
const newOption = document.createElement('option');
|
|
1627
1734
|
newOption.value = countryName;
|
|
1628
1735
|
newOption.textContent = countryName;
|
|
1629
|
-
|
|
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
|
+
}
|
|
1630
1745
|
element.appendChild(newOption);
|
|
1631
1746
|
}
|
|
1632
1747
|
const changeEvent = new Event('change', { bubbles: true });
|
|
@@ -1958,7 +2073,23 @@
|
|
|
1958
2073
|
container.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Chargement des vidéos YouTube...</div>';
|
|
1959
2074
|
|
|
1960
2075
|
// Appeler l'API via le Worker
|
|
1961
|
-
|
|
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}`)
|
|
1962
2093
|
.then(response => {
|
|
1963
2094
|
if (!response.ok) {
|
|
1964
2095
|
throw new Error(`HTTP ${response.status}`);
|
|
@@ -1998,7 +2129,7 @@
|
|
|
1998
2129
|
}
|
|
1999
2130
|
}
|
|
2000
2131
|
|
|
2001
|
-
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>`;
|
|
2002
2133
|
});
|
|
2003
2134
|
},
|
|
2004
2135
|
|