@bebranded/bb-contents 1.0.1-beta → 1.0.1
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 +931 -630
- 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.1
|
|
4
|
+
* @version 1.0.1
|
|
5
5
|
* @author BeBranded
|
|
6
6
|
* @license MIT
|
|
7
7
|
* @website https://www.bebranded.xyz
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
// Configuration
|
|
19
19
|
const config = {
|
|
20
|
-
version: '1.0.1
|
|
21
|
-
debug:
|
|
20
|
+
version: '1.0.1',
|
|
21
|
+
debug: true, // Activé temporairement pour debug
|
|
22
22
|
prefix: 'bb-', // utilisé pour générer les sélecteurs (data-bb-*)
|
|
23
23
|
i18n: {
|
|
24
24
|
copied: 'Lien copié !'
|
|
@@ -31,11 +31,14 @@
|
|
|
31
31
|
modules: {},
|
|
32
32
|
_observer: null,
|
|
33
33
|
_reinitScheduled: false,
|
|
34
|
+
_initRetryCount: 0,
|
|
35
|
+
_maxInitRetries: 3,
|
|
36
|
+
_performanceBoostDetected: false,
|
|
34
37
|
|
|
35
38
|
// Utilitaires
|
|
36
39
|
utils: {
|
|
37
40
|
log: function(...args) {
|
|
38
|
-
if (config.debug) {
|
|
41
|
+
if (bbContents.config.debug) {
|
|
39
42
|
console.log('[BB Contents]', ...args);
|
|
40
43
|
}
|
|
41
44
|
},
|
|
@@ -59,7 +62,7 @@
|
|
|
59
62
|
}
|
|
60
63
|
},
|
|
61
64
|
|
|
62
|
-
// Helper: construire des sélecteurs d
|
|
65
|
+
// Helper: construire des sélecteurs d'attributs selon le prefix
|
|
63
66
|
_attrSelector: function(name) {
|
|
64
67
|
const p = (this.config.prefix || 'bb-').replace(/-?$/, '-');
|
|
65
68
|
const legacy = name.startsWith('bb-') ? name : (p + name);
|
|
@@ -76,8 +79,25 @@
|
|
|
76
79
|
|
|
77
80
|
// Initialisation
|
|
78
81
|
init: function() {
|
|
82
|
+
// Console simple et épurée
|
|
83
|
+
console.log('bb-contents | v' + this.config.version);
|
|
84
|
+
|
|
79
85
|
this.utils.log('Initialisation v' + this.config.version);
|
|
80
86
|
|
|
87
|
+
// Debug: Analyser l'environnement
|
|
88
|
+
this.utils.log('=== DEBUG ENVIRONNEMENT ===');
|
|
89
|
+
this.utils.log('Body attributes:', Array.from(document.body.attributes).map(attr => attr.name + '=' + attr.value));
|
|
90
|
+
this.utils.log('Scripts chargés:', document.querySelectorAll('script').length);
|
|
91
|
+
this.utils.log('Stylesheets chargés:', document.querySelectorAll('link[rel="stylesheet"]').length);
|
|
92
|
+
this.utils.log('Marquees détectés:', document.querySelectorAll('[bb-marquee]').length);
|
|
93
|
+
this.utils.log('Marquees déjà traités:', document.querySelectorAll('[bb-marquee][data-bb-marquee-processed]').length);
|
|
94
|
+
|
|
95
|
+
// Détection du bb-performance-boost
|
|
96
|
+
this._performanceBoostDetected = document.body.hasAttribute('bb-performance-boost');
|
|
97
|
+
if (this._performanceBoostDetected) {
|
|
98
|
+
this.utils.log('bb-performance-boost détecté - mode de compatibilité activé');
|
|
99
|
+
}
|
|
100
|
+
|
|
81
101
|
// Déterminer la portée
|
|
82
102
|
const scope = document.querySelector('[data-bb-scope]') || document;
|
|
83
103
|
|
|
@@ -97,705 +117,965 @@
|
|
|
97
117
|
|
|
98
118
|
// Activer l'observer DOM pour contenu dynamique
|
|
99
119
|
this.setupObserver();
|
|
120
|
+
|
|
121
|
+
// Vérifier et réinitialiser les éléments non initialisés
|
|
122
|
+
this.checkAndReinitFailedElements();
|
|
100
123
|
},
|
|
101
|
-
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
if (rootNode.closest && rootNode.closest('[data-bb-disable]')) return;
|
|
124
|
+
|
|
125
|
+
// Nouvelle méthode pour vérifier et réinitialiser les éléments échoués
|
|
126
|
+
checkAndReinitFailedElements: function() {
|
|
127
|
+
const scope = document.querySelector('[data-bb-scope]') || document;
|
|
128
|
+
let needsReinit = false;
|
|
107
129
|
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
130
|
+
// Vérifier les marquees non initialisés
|
|
131
|
+
const marqueeElements = scope.querySelectorAll('[bb-marquee]:not([data-bb-marquee-processed])');
|
|
132
|
+
if (marqueeElements.length > 0) {
|
|
133
|
+
bbContents.utils.log('Marquees non initialisés détectés:', marqueeElements.length);
|
|
134
|
+
needsReinit = true;
|
|
111
135
|
}
|
|
112
|
-
this._lastReinitTime = Date.now();
|
|
113
136
|
|
|
137
|
+
// Vérifier les autres modules si nécessaire
|
|
114
138
|
Object.keys(this.modules).forEach(function(moduleName) {
|
|
115
139
|
const module = bbContents.modules[moduleName];
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
console.error('[BB Contents] Erreur reinit dans le module', moduleName, error);
|
|
140
|
+
if (module.checkFailed && module.checkFailed(scope)) {
|
|
141
|
+
bbContents.utils.log('Module', moduleName, 'a des éléments échoués');
|
|
142
|
+
needsReinit = true;
|
|
120
143
|
}
|
|
121
144
|
});
|
|
145
|
+
|
|
146
|
+
// Réinitialiser si nécessaire et si on n'a pas dépassé le nombre max de tentatives
|
|
147
|
+
if (needsReinit && this._initRetryCount < this._maxInitRetries) {
|
|
148
|
+
this._initRetryCount++;
|
|
149
|
+
bbContents.utils.log('Tentative de réinitialisation', this._initRetryCount, '/', this._maxInitRetries);
|
|
150
|
+
|
|
151
|
+
const delay = this._performanceBoostDetected ? 1000 * this._initRetryCount : 500 * this._initRetryCount;
|
|
152
|
+
setTimeout(() => {
|
|
153
|
+
this.init();
|
|
154
|
+
}, delay); // Délai progressif adaptatif
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
// Méthode publique pour forcer la réinitialisation
|
|
159
|
+
reinit: function() {
|
|
160
|
+
this._initRetryCount = 0;
|
|
161
|
+
this.init();
|
|
122
162
|
},
|
|
123
163
|
|
|
124
|
-
//
|
|
164
|
+
// Observer DOM pour contenu dynamique
|
|
125
165
|
setupObserver: function() {
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
166
|
+
if (this._observer) {
|
|
167
|
+
this._observer.disconnect();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this._observer = new MutationObserver((mutations) => {
|
|
171
|
+
let shouldReinit = false;
|
|
172
|
+
|
|
173
|
+
mutations.forEach((mutation) => {
|
|
174
|
+
if (mutation.type === 'childList') {
|
|
175
|
+
mutation.addedNodes.forEach((node) => {
|
|
136
176
|
if (node.nodeType === 1) { // Element node
|
|
177
|
+
// Vérifier si le nouveau nœud ou ses enfants ont des attributs bb-*
|
|
137
178
|
if (node.querySelector && (
|
|
138
|
-
node.querySelector('[bb-]
|
|
139
|
-
node.
|
|
179
|
+
node.querySelector('[bb-]') ||
|
|
180
|
+
node.querySelector('[data-bb-]') ||
|
|
181
|
+
node.matches && (node.matches('[bb-]') || node.matches('[data-bb-]'))
|
|
140
182
|
)) {
|
|
141
|
-
|
|
142
|
-
break;
|
|
183
|
+
shouldReinit = true;
|
|
143
184
|
}
|
|
144
185
|
}
|
|
145
|
-
}
|
|
186
|
+
});
|
|
146
187
|
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (shouldReinit && !this._reinitScheduled) {
|
|
191
|
+
this._reinitScheduled = true;
|
|
192
|
+
const delay = this._performanceBoostDetected ? 200 : 100;
|
|
193
|
+
setTimeout(() => {
|
|
194
|
+
this.init();
|
|
195
|
+
this._reinitScheduled = false;
|
|
196
|
+
}, delay);
|
|
147
197
|
}
|
|
148
|
-
if (!hasRelevantChanges) return;
|
|
149
|
-
if (self._reinitScheduled) return;
|
|
150
|
-
self._reinitScheduled = true;
|
|
151
|
-
setTimeout(function() {
|
|
152
|
-
try {
|
|
153
|
-
self.reinit(document);
|
|
154
|
-
} finally {
|
|
155
|
-
self._reinitScheduled = false;
|
|
156
|
-
}
|
|
157
|
-
}, 200); // Augmenté à 200ms pour réduire la fréquence
|
|
158
198
|
});
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
199
|
+
|
|
200
|
+
this._observer.observe(document.body, {
|
|
201
|
+
childList: true,
|
|
202
|
+
subtree: true
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
this.utils.log('MutationObserver actif');
|
|
165
206
|
}
|
|
166
207
|
};
|
|
167
208
|
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
twitter: function(data) {
|
|
175
|
-
return 'https://twitter.com/intent/tweet?url=' +
|
|
176
|
-
encodeURIComponent(data.url) +
|
|
177
|
-
'&text=' + encodeURIComponent(data.text);
|
|
178
|
-
},
|
|
179
|
-
facebook: function(data) {
|
|
180
|
-
return 'https://facebook.com/sharer/sharer.php?u=' +
|
|
181
|
-
encodeURIComponent(data.url);
|
|
182
|
-
},
|
|
183
|
-
linkedin: function(data) {
|
|
184
|
-
// LinkedIn - URL de partage officielle (2024+)
|
|
185
|
-
return 'https://www.linkedin.com/sharing/share-offsite/?url=' + encodeURIComponent(data.url);
|
|
186
|
-
},
|
|
187
|
-
whatsapp: function(data) {
|
|
188
|
-
return 'https://wa.me/?text=' +
|
|
189
|
-
encodeURIComponent(data.text + ' ' + data.url);
|
|
190
|
-
},
|
|
191
|
-
telegram: function(data) {
|
|
192
|
-
return 'https://t.me/share/url?url=' +
|
|
193
|
-
encodeURIComponent(data.url) +
|
|
194
|
-
'&text=' + encodeURIComponent(data.text);
|
|
209
|
+
// Modules
|
|
210
|
+
bbContents.modules = {
|
|
211
|
+
// Module SEO
|
|
212
|
+
seo: {
|
|
213
|
+
detect: function(scope) {
|
|
214
|
+
return scope.querySelector('[bb-seo]') !== null;
|
|
195
215
|
},
|
|
196
|
-
email: function(data) {
|
|
197
|
-
return 'mailto:?subject=' +
|
|
198
|
-
encodeURIComponent(data.text) +
|
|
199
|
-
'&body=' + encodeURIComponent(data.text + ' ' + data.url);
|
|
200
|
-
},
|
|
201
|
-
copy: function(data) {
|
|
202
|
-
return 'copy:' + data.url;
|
|
203
|
-
},
|
|
204
|
-
native: function(data) {
|
|
205
|
-
return 'native:' + JSON.stringify(data);
|
|
206
|
-
}
|
|
207
|
-
},
|
|
208
|
-
|
|
209
|
-
// Détection
|
|
210
|
-
detect: function(scope) {
|
|
211
|
-
const s = scope || document;
|
|
212
|
-
return s.querySelector(bbContents._attrSelector('share')) !== null;
|
|
213
|
-
},
|
|
214
|
-
|
|
215
|
-
// Initialisation
|
|
216
|
-
init: function(root) {
|
|
217
|
-
const scope = root || document;
|
|
218
|
-
if (scope.closest && scope.closest('[data-bb-disable]')) return;
|
|
219
|
-
const elements = scope.querySelectorAll(bbContents._attrSelector('share'));
|
|
220
216
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (
|
|
224
|
-
element.bbProcessed = true;
|
|
225
|
-
|
|
226
|
-
// Récupérer les données
|
|
227
|
-
const network = bbContents._getAttr(element, 'bb-share');
|
|
228
|
-
const customUrl = bbContents._getAttr(element, 'bb-url');
|
|
229
|
-
const customText = bbContents._getAttr(element, 'bb-text');
|
|
230
|
-
|
|
231
|
-
// Valeurs par défaut sécurisées
|
|
232
|
-
const data = {
|
|
233
|
-
url: bbContents.utils.isValidUrl(customUrl) ? customUrl : window.location.href,
|
|
234
|
-
text: bbContents.utils.sanitize(customText || document.title || 'Découvrez ce site')
|
|
235
|
-
};
|
|
217
|
+
init: function(scope) {
|
|
218
|
+
const elements = scope.querySelectorAll('[bb-seo]');
|
|
219
|
+
if (elements.length === 0) return;
|
|
236
220
|
|
|
237
|
-
|
|
238
|
-
element.addEventListener('click', function(e) {
|
|
239
|
-
e.preventDefault();
|
|
240
|
-
bbContents.modules.share.share(network, data, element);
|
|
241
|
-
});
|
|
221
|
+
bbContents.utils.log('Module détecté: seo');
|
|
242
222
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
element.
|
|
246
|
-
element.setAttribute('tabindex', '0');
|
|
223
|
+
elements.forEach(element => {
|
|
224
|
+
if (element.bbProcessed) return;
|
|
225
|
+
element.bbProcessed = true;
|
|
247
226
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
227
|
+
const title = bbContents._getAttr(element, 'bb-seo-title');
|
|
228
|
+
const description = bbContents._getAttr(element, 'bb-seo-description');
|
|
229
|
+
const keywords = bbContents._getAttr(element, 'bb-seo-keywords');
|
|
230
|
+
|
|
231
|
+
if (title) {
|
|
232
|
+
document.title = title;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (description) {
|
|
236
|
+
let meta = document.querySelector('meta[name="description"]');
|
|
237
|
+
if (!meta) {
|
|
238
|
+
meta = document.createElement('meta');
|
|
239
|
+
meta.name = 'description';
|
|
240
|
+
document.head.appendChild(meta);
|
|
253
241
|
}
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
element.style.cursor = 'pointer';
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
bbContents.utils.log('Module Share initialisé:', elements.length, 'éléments');
|
|
261
|
-
},
|
|
262
|
-
|
|
263
|
-
// Fonction de partage
|
|
264
|
-
share: function(network, data, element) {
|
|
265
|
-
const networkFunc = this.networks[network];
|
|
266
|
-
|
|
267
|
-
if (!networkFunc) {
|
|
268
|
-
console.error('[BB Contents] Réseau non supporté:', network);
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const shareUrl = networkFunc(data);
|
|
273
|
-
|
|
274
|
-
// Cas spécial : copier le lien
|
|
275
|
-
if (shareUrl.startsWith('copy:')) {
|
|
276
|
-
const url = shareUrl.substring(5);
|
|
277
|
-
// Copie silencieuse (pas de feedback visuel)
|
|
278
|
-
this.copyToClipboard(url, element, true);
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Cas spécial : partage natif (Web Share API)
|
|
283
|
-
if (shareUrl.startsWith('native:')) {
|
|
284
|
-
const shareData = JSON.parse(shareUrl.substring(7));
|
|
285
|
-
this.nativeShare(shareData, element);
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Ouvrir popup de partage
|
|
290
|
-
const width = 600;
|
|
291
|
-
const height = 400;
|
|
292
|
-
const left = (window.innerWidth - width) / 2;
|
|
293
|
-
const top = (window.innerHeight - height) / 2;
|
|
294
|
-
|
|
295
|
-
window.open(
|
|
296
|
-
shareUrl,
|
|
297
|
-
'bbshare',
|
|
298
|
-
'width=' + width + ',height=' + height + ',left=' + left + ',top=' + top + ',noopener,noreferrer'
|
|
299
|
-
);
|
|
300
|
-
|
|
301
|
-
bbContents.utils.log('Partage sur', network, data);
|
|
302
|
-
},
|
|
303
|
-
|
|
304
|
-
// Copier dans le presse-papier
|
|
305
|
-
copyToClipboard: function(text, element, silent) {
|
|
306
|
-
const isSilent = !!silent;
|
|
307
|
-
// Méthode moderne
|
|
308
|
-
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
309
|
-
navigator.clipboard.writeText(text).then(function() {
|
|
310
|
-
if (!isSilent) {
|
|
311
|
-
bbContents.modules.share.showFeedback(element, '✓ ' + (bbContents.config.i18n.copied || 'Lien copié !'));
|
|
242
|
+
meta.content = description;
|
|
312
243
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
// Fallback copie
|
|
323
|
-
fallbackCopy: function(text, element, silent) {
|
|
324
|
-
const isSilent = !!silent;
|
|
325
|
-
// Pas de UI si silencieux (exigence produit)
|
|
326
|
-
if (isSilent) return;
|
|
327
|
-
try {
|
|
328
|
-
// Afficher un prompt natif pour permettre à l'utilisateur de copier manuellement
|
|
329
|
-
// (solution universelle sans execCommand)
|
|
330
|
-
window.prompt('Copiez le lien ci-dessous (Ctrl/Cmd+C) :', text);
|
|
331
|
-
} catch (err) {
|
|
332
|
-
// Dernier recours: ne rien faire
|
|
333
|
-
}
|
|
334
|
-
},
|
|
335
|
-
|
|
336
|
-
// Partage natif (Web Share API)
|
|
337
|
-
nativeShare: function(data, element) {
|
|
338
|
-
// Vérifier si Web Share API est disponible
|
|
339
|
-
if (navigator.share) {
|
|
340
|
-
navigator.share({
|
|
341
|
-
title: data.text,
|
|
342
|
-
url: data.url
|
|
343
|
-
}).then(function() {
|
|
344
|
-
bbContents.utils.log('Partage natif réussi');
|
|
345
|
-
}).catch(function(error) {
|
|
346
|
-
if (error.name !== 'AbortError') {
|
|
347
|
-
console.error('[BB Contents] Erreur partage natif:', error);
|
|
348
|
-
// Fallback vers copie si échec
|
|
349
|
-
bbContents.modules.share.copyToClipboard(data.url, element, false);
|
|
244
|
+
|
|
245
|
+
if (keywords) {
|
|
246
|
+
let meta = document.querySelector('meta[name="keywords"]');
|
|
247
|
+
if (!meta) {
|
|
248
|
+
meta = document.createElement('meta');
|
|
249
|
+
meta.name = 'keywords';
|
|
250
|
+
document.head.appendChild(meta);
|
|
251
|
+
}
|
|
252
|
+
meta.content = keywords;
|
|
350
253
|
}
|
|
351
254
|
});
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
bbContents.utils.log('Web Share API non disponible, fallback vers copie');
|
|
355
|
-
this.copyToClipboard(data.url, element, false);
|
|
255
|
+
|
|
256
|
+
bbContents.utils.log('Module SEO initialisé:', elements.length, 'éléments');
|
|
356
257
|
}
|
|
357
258
|
},
|
|
358
|
-
|
|
359
|
-
// Feedback visuel
|
|
360
|
-
showFeedback: function(element, message) {
|
|
361
|
-
const originalText = element.textContent;
|
|
362
|
-
element.textContent = message;
|
|
363
|
-
element.style.pointerEvents = 'none';
|
|
364
|
-
|
|
365
|
-
setTimeout(function() {
|
|
366
|
-
element.textContent = originalText;
|
|
367
|
-
element.style.pointerEvents = '';
|
|
368
|
-
}, 2000);
|
|
369
|
-
}
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
// ========================================
|
|
373
|
-
// MODULE: CURRENT YEAR (Année courante)
|
|
374
|
-
// ========================================
|
|
375
|
-
bbContents.modules.currentYear = {
|
|
376
|
-
detect: function(scope) {
|
|
377
|
-
const s = scope || document;
|
|
378
|
-
return s.querySelector(bbContents._attrSelector('current-year')) !== null;
|
|
379
|
-
},
|
|
380
|
-
init: function(root) {
|
|
381
|
-
const scope = root || document;
|
|
382
|
-
if (scope.closest && scope.closest('[data-bb-disable]')) return;
|
|
383
|
-
const elements = scope.querySelectorAll(bbContents._attrSelector('current-year'));
|
|
384
|
-
|
|
385
|
-
const year = String(new Date().getFullYear());
|
|
386
|
-
elements.forEach(function(element) {
|
|
387
|
-
if (element.bbProcessed) return;
|
|
388
|
-
element.bbProcessed = true;
|
|
389
259
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
element.textContent = year;
|
|
400
|
-
}
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
bbContents.utils.log('Module CurrentYear initialisé:', elements.length, 'éléments');
|
|
404
|
-
}
|
|
405
|
-
};
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
// ========================================
|
|
410
|
-
// MODULE: READING TIME (Temps de lecture)
|
|
411
|
-
// ========================================
|
|
412
|
-
bbContents.modules.readingTime = {
|
|
413
|
-
detect: function(scope) {
|
|
414
|
-
const s = scope || document;
|
|
415
|
-
return s.querySelector(bbContents._attrSelector('reading-time')) !== null;
|
|
416
|
-
},
|
|
417
|
-
init: function(root) {
|
|
418
|
-
const scope = root || document;
|
|
419
|
-
if (scope.closest && scope.closest('[data-bb-disable]')) return;
|
|
420
|
-
const elements = scope.querySelectorAll(bbContents._attrSelector('reading-time'));
|
|
421
|
-
|
|
422
|
-
elements.forEach(function(element) {
|
|
423
|
-
if (element.bbProcessed) return;
|
|
424
|
-
element.bbProcessed = true;
|
|
425
|
-
|
|
426
|
-
const targetSelector = bbContents._getAttr(element, 'bb-reading-time-target');
|
|
427
|
-
const speedAttr = bbContents._getAttr(element, 'bb-reading-time-speed');
|
|
428
|
-
const imageSpeedAttr = bbContents._getAttr(element, 'bb-reading-time-image-speed');
|
|
429
|
-
const format = bbContents._getAttr(element, 'bb-reading-time-format') || '{minutes} min';
|
|
430
|
-
|
|
431
|
-
const wordsPerMinute = Number(speedAttr) > 0 ? Number(speedAttr) : 230;
|
|
432
|
-
const secondsPerImage = Number(imageSpeedAttr) > 0 ? Number(imageSpeedAttr) : 12;
|
|
260
|
+
// Module Images
|
|
261
|
+
images: {
|
|
262
|
+
detect: function(scope) {
|
|
263
|
+
return scope.querySelector('[bb-images]') !== null;
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
init: function(scope) {
|
|
267
|
+
const elements = scope.querySelectorAll('[bb-images]');
|
|
268
|
+
if (elements.length === 0) return;
|
|
433
269
|
|
|
434
|
-
|
|
435
|
-
if (isNaN(wordsPerMinute) || wordsPerMinute <= 0) {
|
|
436
|
-
bbContents.utils.log('Vitesse de lecture invalide, utilisation de la valeur par défaut (230)');
|
|
437
|
-
}
|
|
438
|
-
if (isNaN(secondsPerImage) || secondsPerImage < 0) {
|
|
439
|
-
bbContents.utils.log('Temps par image invalide, utilisation de la valeur par défaut (12)');
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
let sourceNode = element;
|
|
443
|
-
if (targetSelector) {
|
|
444
|
-
const found = document.querySelector(targetSelector);
|
|
445
|
-
if (found) sourceNode = found;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
const text = (sourceNode.textContent || '').trim();
|
|
449
|
-
const wordCount = text ? (text.match(/\b\w+\b/g) || []).length : 0;
|
|
270
|
+
bbContents.utils.log('Module détecté: images');
|
|
450
271
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
272
|
+
elements.forEach(element => {
|
|
273
|
+
if (element.bbProcessed) return;
|
|
274
|
+
element.bbProcessed = true;
|
|
275
|
+
|
|
276
|
+
const lazy = bbContents._getAttr(element, 'bb-images-lazy');
|
|
277
|
+
const webp = bbContents._getAttr(element, 'bb-images-webp');
|
|
278
|
+
|
|
279
|
+
if (lazy === 'true') {
|
|
280
|
+
// Implémentation lazy loading basique
|
|
281
|
+
const images = element.querySelectorAll('img');
|
|
282
|
+
images.forEach(img => {
|
|
283
|
+
if (!img.loading) {
|
|
284
|
+
img.loading = 'lazy';
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (webp === 'true') {
|
|
290
|
+
// Support WebP basique
|
|
291
|
+
const images = element.querySelectorAll('img');
|
|
292
|
+
images.forEach(img => {
|
|
293
|
+
const src = img.src;
|
|
294
|
+
if (src && !src.includes('.webp')) {
|
|
295
|
+
// Logique de conversion WebP (à implémenter selon les besoins)
|
|
296
|
+
bbContents.utils.log('Support WebP activé pour:', src);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
});
|
|
455
301
|
|
|
456
|
-
|
|
457
|
-
let minutes = Math.ceil(minutesFloat);
|
|
458
|
-
|
|
459
|
-
if ((wordCount > 0 || imageCount > 0) && minutes < 1) minutes = 1; // affichage minimal 1 min si contenu non vide
|
|
460
|
-
if (wordCount === 0 && imageCount === 0) minutes = 0;
|
|
461
|
-
|
|
462
|
-
const output = format.replace('{minutes}', String(minutes));
|
|
463
|
-
element.textContent = output;
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
bbContents.utils.log('Module ReadingTime initialisé:', elements.length, 'éléments');
|
|
467
|
-
}
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
// ========================================
|
|
471
|
-
// MODULE: FAVICON (Favicon Dynamique)
|
|
472
|
-
// ========================================
|
|
473
|
-
bbContents.modules.favicon = {
|
|
474
|
-
originalFavicon: null,
|
|
475
|
-
|
|
476
|
-
// Détection
|
|
477
|
-
detect: function(scope) {
|
|
478
|
-
const s = scope || document;
|
|
479
|
-
return s.querySelector(bbContents._attrSelector('favicon')) !== null;
|
|
480
|
-
},
|
|
481
|
-
|
|
482
|
-
// Initialisation
|
|
483
|
-
init: function(root) {
|
|
484
|
-
const scope = root || document;
|
|
485
|
-
if (scope.closest && scope.closest('[data-bb-disable]')) return;
|
|
486
|
-
|
|
487
|
-
// Chercher les éléments avec bb-favicon ou bb-favicon-dark
|
|
488
|
-
const elements = scope.querySelectorAll(bbContents._attrSelector('favicon') + ', ' + bbContents._attrSelector('favicon-dark'));
|
|
489
|
-
if (elements.length === 0) return;
|
|
490
|
-
|
|
491
|
-
// Sauvegarder le favicon original
|
|
492
|
-
const existingLink = document.querySelector("link[rel*='icon']");
|
|
493
|
-
if (existingLink) {
|
|
494
|
-
this.originalFavicon = existingLink.href;
|
|
302
|
+
bbContents.utils.log('Module Images initialisé:', elements.length, 'éléments');
|
|
495
303
|
}
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
// Module Infinite Scroll
|
|
307
|
+
infinite: {
|
|
308
|
+
detect: function(scope) {
|
|
309
|
+
return scope.querySelector('[bb-infinite]') !== null;
|
|
310
|
+
},
|
|
496
311
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
elements.forEach(function(element) {
|
|
502
|
-
const light = bbContents._getAttr(element, 'bb-favicon') || bbContents._getAttr(element, 'favicon');
|
|
503
|
-
const dark = bbContents._getAttr(element, 'bb-favicon-dark') || bbContents._getAttr(element, 'favicon-dark');
|
|
312
|
+
init: function(scope) {
|
|
313
|
+
const elements = scope.querySelectorAll('[bb-infinite]');
|
|
314
|
+
if (elements.length === 0) return;
|
|
504
315
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
316
|
+
bbContents.utils.log('Module détecté: infinite');
|
|
317
|
+
|
|
318
|
+
elements.forEach(element => {
|
|
319
|
+
if (element.bbProcessed) return;
|
|
320
|
+
element.bbProcessed = true;
|
|
321
|
+
|
|
322
|
+
const threshold = bbContents._getAttr(element, 'bb-infinite-threshold') || '0.1';
|
|
323
|
+
const url = bbContents._getAttr(element, 'bb-infinite-url');
|
|
324
|
+
|
|
325
|
+
if (!url) {
|
|
326
|
+
bbContents.utils.log('Erreur: bb-infinite-url manquant');
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Implémentation basique d'infinite scroll
|
|
331
|
+
let loading = false;
|
|
332
|
+
let page = 1;
|
|
333
|
+
|
|
334
|
+
const loadMore = () => {
|
|
335
|
+
if (loading) return;
|
|
336
|
+
loading = true;
|
|
337
|
+
|
|
338
|
+
fetch(`${url}?page=${page}`)
|
|
339
|
+
.then(response => response.json())
|
|
340
|
+
.then(data => {
|
|
341
|
+
if (data.items && data.items.length > 0) {
|
|
342
|
+
// Ajouter le contenu
|
|
343
|
+
element.innerHTML += data.html || '';
|
|
344
|
+
page++;
|
|
345
|
+
loading = false;
|
|
346
|
+
}
|
|
347
|
+
})
|
|
348
|
+
.catch(error => {
|
|
349
|
+
bbContents.utils.log('Erreur infinite scroll:', error);
|
|
350
|
+
loading = false;
|
|
351
|
+
});
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
// Observer d'intersection pour déclencher le chargement
|
|
355
|
+
const observer = new IntersectionObserver((entries) => {
|
|
356
|
+
entries.forEach(entry => {
|
|
357
|
+
if (entry.isIntersecting) {
|
|
358
|
+
loadMore();
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}, { threshold: parseFloat(threshold) });
|
|
362
|
+
|
|
363
|
+
observer.observe(element);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
bbContents.utils.log('Module Infinite Scroll initialisé:', elements.length, 'éléments');
|
|
526
367
|
}
|
|
527
|
-
return favicon;
|
|
528
|
-
},
|
|
529
|
-
|
|
530
|
-
// Changer le favicon
|
|
531
|
-
setFavicon: function(url) {
|
|
532
|
-
if (!url) return;
|
|
533
|
-
|
|
534
|
-
// Ajouter un timestamp pour forcer le rafraîchissement du cache
|
|
535
|
-
const cacheBuster = '?v=' + Date.now();
|
|
536
|
-
const urlWithCacheBuster = url + cacheBuster;
|
|
537
|
-
|
|
538
|
-
const favicon = this.getFaviconElement();
|
|
539
|
-
favicon.href = urlWithCacheBuster;
|
|
540
368
|
},
|
|
541
|
-
|
|
542
|
-
//
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
bbContents.modules.favicon.setFavicon(selectedUrl);
|
|
549
|
-
};
|
|
369
|
+
|
|
370
|
+
// Module Marquee - Version live 1.0.33-beta avec améliorations d'initialisation
|
|
371
|
+
marquee: {
|
|
372
|
+
detect: function(scope) {
|
|
373
|
+
const s = scope || document;
|
|
374
|
+
return s.querySelector(bbContents._attrSelector('marquee')) !== null;
|
|
375
|
+
},
|
|
550
376
|
|
|
551
|
-
//
|
|
552
|
-
|
|
377
|
+
// Nouvelle méthode pour vérifier les éléments échoués
|
|
378
|
+
checkFailed: function(scope) {
|
|
379
|
+
const s = scope || document;
|
|
380
|
+
const failedElements = s.querySelectorAll('[bb-marquee]:not([data-bb-marquee-processed])');
|
|
381
|
+
return failedElements.length > 0;
|
|
382
|
+
},
|
|
553
383
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
} else if (typeof darkModeMediaQuery.addListener === 'function') {
|
|
559
|
-
darkModeMediaQuery.addListener(updateFavicon);
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
};
|
|
384
|
+
init: function(root) {
|
|
385
|
+
const scope = root || document;
|
|
386
|
+
if (scope.closest && scope.closest('[data-bb-disable]')) return;
|
|
387
|
+
const elements = scope.querySelectorAll(bbContents._attrSelector('marquee'));
|
|
563
388
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
return s.querySelector(bbContents._attrSelector('marquee')) !== null;
|
|
572
|
-
},
|
|
573
|
-
|
|
574
|
-
// Initialisation
|
|
575
|
-
init: function(root) {
|
|
576
|
-
const scope = root || document;
|
|
577
|
-
if (scope.closest && scope.closest('[data-bb-disable]')) return;
|
|
578
|
-
const elements = scope.querySelectorAll(bbContents._attrSelector('marquee'));
|
|
579
|
-
|
|
580
|
-
elements.forEach(function(element) {
|
|
581
|
-
if (element.bbProcessed) return;
|
|
582
|
-
element.bbProcessed = true;
|
|
389
|
+
elements.forEach(function(element) {
|
|
390
|
+
// Vérifier si l'élément a déjà été traité par un autre module
|
|
391
|
+
if (element.bbProcessed || element.hasAttribute('data-bb-youtube-processed')) {
|
|
392
|
+
bbContents.utils.log('Élément marquee déjà traité par un autre module, ignoré:', element);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
element.bbProcessed = true;
|
|
583
396
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
397
|
+
// Récupérer les options
|
|
398
|
+
const speed = bbContents._getAttr(element, 'bb-marquee-speed') || '100';
|
|
399
|
+
const direction = bbContents._getAttr(element, 'bb-marquee-direction') || 'left';
|
|
400
|
+
const pauseOnHover = bbContents._getAttr(element, 'bb-marquee-pause');
|
|
401
|
+
const gap = bbContents._getAttr(element, 'bb-marquee-gap') || '50';
|
|
402
|
+
const orientation = bbContents._getAttr(element, 'bb-marquee-orientation') || 'horizontal';
|
|
403
|
+
const height = bbContents._getAttr(element, 'bb-marquee-height') || '300';
|
|
404
|
+
const minHeight = bbContents._getAttr(element, 'bb-marquee-min-height');
|
|
592
405
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
406
|
+
// Sauvegarder le contenu original
|
|
407
|
+
const originalHTML = element.innerHTML;
|
|
408
|
+
|
|
409
|
+
// Créer le conteneur principal
|
|
410
|
+
const mainContainer = document.createElement('div');
|
|
411
|
+
const isVertical = orientation === 'vertical';
|
|
412
|
+
const useAutoHeight = isVertical && height === 'auto';
|
|
413
|
+
|
|
414
|
+
mainContainer.style.cssText = `
|
|
415
|
+
position: relative;
|
|
416
|
+
width: 100%;
|
|
417
|
+
height: ${isVertical ? (height === 'auto' ? 'auto' : height + 'px') : 'auto'};
|
|
418
|
+
overflow: hidden;
|
|
419
|
+
min-height: ${isVertical ? '100px' : '50px'};
|
|
420
|
+
${minHeight ? `min-height: ${minHeight};` : ''}
|
|
421
|
+
`;
|
|
609
422
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
`;
|
|
423
|
+
// Créer le conteneur de défilement
|
|
424
|
+
const scrollContainer = document.createElement('div');
|
|
425
|
+
scrollContainer.style.cssText = `
|
|
426
|
+
${useAutoHeight ? 'position: relative;' : 'position: absolute;'}
|
|
427
|
+
will-change: transform;
|
|
428
|
+
${useAutoHeight ? '' : 'height: 100%; top: 0px; left: 0px;'}
|
|
429
|
+
display: flex;
|
|
430
|
+
${isVertical ? 'flex-direction: column;' : ''}
|
|
431
|
+
align-items: center;
|
|
432
|
+
gap: ${gap}px;
|
|
433
|
+
${isVertical ? '' : 'white-space: nowrap;'}
|
|
434
|
+
flex-shrink: 0;
|
|
435
|
+
transition: transform 0.1s ease-out;
|
|
436
|
+
`;
|
|
625
437
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
438
|
+
// Créer le bloc de contenu principal
|
|
439
|
+
const mainBlock = document.createElement('div');
|
|
440
|
+
mainBlock.innerHTML = originalHTML;
|
|
441
|
+
mainBlock.style.cssText = `
|
|
442
|
+
display: flex;
|
|
443
|
+
${isVertical ? 'flex-direction: column;' : ''}
|
|
444
|
+
align-items: center;
|
|
445
|
+
gap: ${gap}px;
|
|
446
|
+
${isVertical ? '' : 'white-space: nowrap;'}
|
|
447
|
+
flex-shrink: 0;
|
|
448
|
+
${isVertical ? 'min-height: 100px;' : ''}
|
|
449
|
+
`;
|
|
638
450
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
451
|
+
// Créer plusieurs répétitions pour un défilement continu
|
|
452
|
+
const repeatBlock1 = mainBlock.cloneNode(true);
|
|
453
|
+
const repeatBlock2 = mainBlock.cloneNode(true);
|
|
454
|
+
const repeatBlock3 = mainBlock.cloneNode(true);
|
|
455
|
+
|
|
456
|
+
// Assembler la structure
|
|
457
|
+
scrollContainer.appendChild(mainBlock);
|
|
458
|
+
scrollContainer.appendChild(repeatBlock1);
|
|
459
|
+
scrollContainer.appendChild(repeatBlock2);
|
|
460
|
+
scrollContainer.appendChild(repeatBlock3);
|
|
461
|
+
mainContainer.appendChild(scrollContainer);
|
|
462
|
+
|
|
463
|
+
// Vider et remplacer le contenu original
|
|
464
|
+
element.innerHTML = '';
|
|
465
|
+
element.appendChild(mainContainer);
|
|
466
|
+
|
|
467
|
+
// Marquer l'élément comme traité par le module marquee
|
|
468
|
+
element.setAttribute('data-bb-marquee-processed', 'true');
|
|
654
469
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
const
|
|
660
|
-
const contentHeight = mainBlock.offsetHeight;
|
|
470
|
+
// Fonction pour initialiser l'animation avec vérification robuste des dimensions
|
|
471
|
+
const initAnimation = (retryCount = 0) => {
|
|
472
|
+
// Vérifier que les images sont chargées
|
|
473
|
+
const images = mainBlock.querySelectorAll('img');
|
|
474
|
+
const imagesLoaded = Array.from(images).every(img => img.complete && img.naturalHeight > 0);
|
|
661
475
|
|
|
662
|
-
//
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
476
|
+
// Attendre que le contenu soit dans le DOM et que les images soient chargées
|
|
477
|
+
requestAnimationFrame(() => {
|
|
478
|
+
// Calcul plus robuste des dimensions
|
|
479
|
+
const rect = mainBlock.getBoundingClientRect();
|
|
480
|
+
const contentWidth = rect.width || mainBlock.offsetWidth;
|
|
481
|
+
const contentHeight = rect.height || mainBlock.offsetHeight;
|
|
666
482
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
maxHeight = logoHeight;
|
|
671
|
-
}
|
|
672
|
-
});
|
|
483
|
+
// Pour les marquees verticaux, utiliser la largeur du parent si nécessaire
|
|
484
|
+
let finalWidth = contentWidth;
|
|
485
|
+
let finalHeight = contentHeight;
|
|
673
486
|
|
|
674
|
-
if (
|
|
675
|
-
|
|
676
|
-
|
|
487
|
+
if (isVertical && contentWidth < 10) {
|
|
488
|
+
// Si largeur trop petite, utiliser la largeur du parent
|
|
489
|
+
const parentRect = mainBlock.parentElement.getBoundingClientRect();
|
|
490
|
+
finalWidth = parentRect.width || mainBlock.parentElement.offsetWidth;
|
|
491
|
+
bbContents.utils.log('Largeur corrigée pour marquee vertical:', finalWidth, 'px (était:', contentWidth, 'px)');
|
|
677
492
|
}
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
// Debug
|
|
681
|
-
bbContents.utils.log('Debug - Largeur du contenu:', contentWidth, 'px', 'Hauteur:', contentHeight, 'px', 'Enfants:', mainBlock.children.length, 'Vertical:', isVertical, 'Direction:', direction);
|
|
682
|
-
|
|
683
|
-
// Si pas de contenu, réessayer
|
|
684
|
-
if ((isVertical && contentHeight === 0) || (!isVertical && contentWidth === 0)) {
|
|
685
|
-
bbContents.utils.log('Contenu non prêt, nouvelle tentative dans 200ms');
|
|
686
|
-
setTimeout(initAnimation, 200);
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
// Pour le vertical, s'assurer qu'on a une hauteur minimale
|
|
691
|
-
if (isVertical && contentHeight < 50) {
|
|
692
|
-
bbContents.utils.log('Hauteur insuffisante pour le marquee vertical (' + contentHeight + 'px), nouvelle tentative dans 200ms');
|
|
693
|
-
setTimeout(initAnimation, 200);
|
|
694
|
-
return;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
if (isVertical) {
|
|
698
|
-
// Animation JavaScript pour le vertical
|
|
699
|
-
const contentSize = contentHeight;
|
|
700
|
-
const totalSize = contentSize * 4 + parseInt(gap) * 3; // 4 copies au lieu de 3
|
|
701
|
-
scrollContainer.style.height = totalSize + 'px';
|
|
702
493
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
494
|
+
// Debug amélioré avec statut des images
|
|
495
|
+
bbContents.utils.log('Debug - Largeur:', finalWidth, 'px, Hauteur:', finalHeight, 'px, Images chargées:', imagesLoaded, 'Enfants:', mainBlock.children.length, 'Vertical:', isVertical, 'Direction:', direction, 'Tentative:', retryCount + 1);
|
|
496
|
+
|
|
497
|
+
// Vérifications robustes avant initialisation
|
|
498
|
+
const hasValidDimensions = (isVertical && finalHeight > 50) || (!isVertical && finalWidth > 50);
|
|
499
|
+
const maxRetries = 8; // Plus de tentatives pour attendre les images
|
|
500
|
+
|
|
501
|
+
// Si pas de contenu valide ou images pas chargées, réessayer
|
|
502
|
+
if (!hasValidDimensions || !imagesLoaded) {
|
|
503
|
+
if (retryCount < maxRetries) {
|
|
504
|
+
const delay = 300 + retryCount * 200; // Délais plus longs pour attendre les images
|
|
505
|
+
bbContents.utils.log('Contenu/images non prêts, nouvelle tentative dans', delay, 'ms');
|
|
506
|
+
setTimeout(() => initAnimation(retryCount + 1), delay);
|
|
507
|
+
return;
|
|
508
|
+
} else {
|
|
509
|
+
bbContents.utils.log('Échec d\'initialisation après', maxRetries, 'tentatives - dimensions:', finalWidth + 'x' + finalHeight, 'images chargées:', imagesLoaded);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
706
513
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
514
|
+
if (isVertical) {
|
|
515
|
+
// Animation JavaScript pour le vertical
|
|
516
|
+
const contentSize = finalHeight;
|
|
517
|
+
const totalSize = contentSize * 4 + parseInt(gap) * 3; // 4 copies au lieu de 3
|
|
518
|
+
|
|
519
|
+
// Ajuster la hauteur du scrollContainer seulement si pas en mode auto
|
|
520
|
+
if (!useAutoHeight) {
|
|
521
|
+
scrollContainer.style.height = totalSize + 'px';
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
let currentPosition = direction === 'bottom' ? -contentSize - parseInt(gap) : 0;
|
|
525
|
+
const baseStep = (parseFloat(speed) * 2) / 60; // Vitesse de base
|
|
526
|
+
let currentStep = baseStep;
|
|
527
|
+
let isPaused = false;
|
|
528
|
+
let animationId = null;
|
|
529
|
+
let lastTime = 0;
|
|
530
|
+
|
|
531
|
+
// Fonction d'animation JavaScript optimisée
|
|
532
|
+
const animate = (currentTime) => {
|
|
533
|
+
if (!lastTime) lastTime = currentTime;
|
|
534
|
+
const deltaTime = currentTime - lastTime;
|
|
535
|
+
lastTime = currentTime;
|
|
536
|
+
|
|
710
537
|
if (direction === 'bottom') {
|
|
711
|
-
currentPosition +=
|
|
538
|
+
currentPosition += currentStep * (deltaTime / 16.67); // Normaliser à 60fps
|
|
712
539
|
if (currentPosition >= 0) {
|
|
713
540
|
currentPosition = -contentSize - parseInt(gap);
|
|
714
541
|
}
|
|
715
542
|
} else {
|
|
716
|
-
currentPosition -=
|
|
543
|
+
currentPosition -= currentStep * (deltaTime / 16.67);
|
|
717
544
|
if (currentPosition <= -contentSize - parseInt(gap)) {
|
|
718
545
|
currentPosition = 0;
|
|
719
546
|
}
|
|
720
547
|
}
|
|
721
548
|
|
|
722
549
|
scrollContainer.style.transform = `translate3d(0px, ${currentPosition}px, 0px)`;
|
|
550
|
+
animationId = requestAnimationFrame(animate);
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
// Démarrer l'animation
|
|
554
|
+
animationId = requestAnimationFrame(animate);
|
|
555
|
+
|
|
556
|
+
bbContents.utils.log('Marquee vertical créé avec animation JS - direction:', direction, 'taille:', contentSize + 'px', 'total:', totalSize + 'px', 'hauteur-wrapper:', height + 'px');
|
|
557
|
+
|
|
558
|
+
// Pause au survol avec transition fluide CSS + JS
|
|
559
|
+
if (pauseOnHover === 'true') {
|
|
560
|
+
// Transition fluide avec easing naturel
|
|
561
|
+
const transitionSpeed = (targetSpeed, duration = 300) => {
|
|
562
|
+
const startSpeed = currentStep;
|
|
563
|
+
const speedDiff = targetSpeed - startSpeed;
|
|
564
|
+
const startTime = performance.now();
|
|
565
|
+
|
|
566
|
+
// Easing naturel
|
|
567
|
+
const easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
|
|
568
|
+
const easeInCubic = (t) => t * t * t;
|
|
569
|
+
|
|
570
|
+
const animateTransition = (currentTime) => {
|
|
571
|
+
const elapsed = currentTime - startTime;
|
|
572
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
573
|
+
|
|
574
|
+
// Easing différent selon la direction
|
|
575
|
+
const easedProgress = targetSpeed === 0 ?
|
|
576
|
+
easeOutCubic(progress) : easeInCubic(progress);
|
|
577
|
+
|
|
578
|
+
currentStep = startSpeed + speedDiff * easedProgress;
|
|
579
|
+
|
|
580
|
+
if (progress < 1) {
|
|
581
|
+
requestAnimationFrame(animateTransition);
|
|
582
|
+
} else {
|
|
583
|
+
currentStep = targetSpeed;
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
requestAnimationFrame(animateTransition);
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
element.addEventListener('mouseenter', function() {
|
|
591
|
+
transitionSpeed(0); // Ralentir jusqu'à 0
|
|
592
|
+
});
|
|
593
|
+
element.addEventListener('mouseleave', function() {
|
|
594
|
+
transitionSpeed(baseStep); // Revenir à la vitesse normale
|
|
595
|
+
});
|
|
723
596
|
}
|
|
724
|
-
requestAnimationFrame(animate);
|
|
725
|
-
};
|
|
726
|
-
|
|
727
|
-
// Démarrer l'animation
|
|
728
|
-
animate();
|
|
729
|
-
|
|
730
|
-
bbContents.utils.log('Marquee vertical créé avec animation JS - direction:', direction, 'taille:', contentSize + 'px', 'total:', totalSize + 'px', 'hauteur-wrapper:', height + 'px');
|
|
731
|
-
|
|
732
|
-
// Pause au survol
|
|
733
|
-
if (pauseOnHover === 'true') {
|
|
734
|
-
element.addEventListener('mouseenter', function() {
|
|
735
|
-
isPaused = true;
|
|
736
|
-
});
|
|
737
|
-
element.addEventListener('mouseleave', function() {
|
|
738
|
-
isPaused = false;
|
|
739
|
-
});
|
|
740
|
-
}
|
|
741
|
-
} else {
|
|
742
|
-
// Animation CSS pour l'horizontal (modifiée)
|
|
743
|
-
const contentSize = contentWidth;
|
|
744
|
-
const totalSize = contentSize * 4 + parseInt(gap) * 3; // 4 copies au lieu de 3
|
|
745
|
-
scrollContainer.style.width = totalSize + 'px';
|
|
746
|
-
|
|
747
|
-
// Créer l'animation CSS optimisée
|
|
748
|
-
const animationName = 'bb-scroll-' + Math.random().toString(36).substr(2, 9);
|
|
749
|
-
const animationDuration = (totalSize / (parseFloat(speed) * 1.5)).toFixed(2) + 's'; // Vitesse différente
|
|
750
|
-
|
|
751
|
-
// Animation avec translate3d pour hardware acceleration
|
|
752
|
-
let keyframes;
|
|
753
|
-
if (direction === 'right') {
|
|
754
|
-
keyframes = `@keyframes ${animationName} {
|
|
755
|
-
0% { transform: translate3d(-${contentSize + parseInt(gap)}px, 0px, 0px); }
|
|
756
|
-
100% { transform: translate3d(0px, 0px, 0px); }
|
|
757
|
-
}`;
|
|
758
597
|
} else {
|
|
759
|
-
//
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
598
|
+
// Animation JavaScript pour l'horizontal (comme le vertical pour éviter les saccades)
|
|
599
|
+
const contentSize = finalWidth;
|
|
600
|
+
const totalSize = contentSize * 4 + parseInt(gap) * 3;
|
|
601
|
+
scrollContainer.style.width = totalSize + 'px';
|
|
602
|
+
|
|
603
|
+
let currentPosition = direction === 'right' ? -contentSize - parseInt(gap) : 0;
|
|
604
|
+
const baseStep = (parseFloat(speed) * 0.5) / 60; // Vitesse de base
|
|
605
|
+
let currentStep = baseStep;
|
|
606
|
+
let isPaused = false;
|
|
607
|
+
let animationId = null;
|
|
608
|
+
let lastTime = 0;
|
|
609
|
+
|
|
610
|
+
// Fonction d'animation JavaScript optimisée
|
|
611
|
+
const animate = (currentTime) => {
|
|
612
|
+
if (!lastTime) lastTime = currentTime;
|
|
613
|
+
const deltaTime = currentTime - lastTime;
|
|
614
|
+
lastTime = currentTime;
|
|
615
|
+
|
|
616
|
+
if (direction === 'right') {
|
|
617
|
+
currentPosition += currentStep * (deltaTime / 16.67); // Normaliser à 60fps
|
|
618
|
+
if (currentPosition >= 0) {
|
|
619
|
+
currentPosition = -contentSize - parseInt(gap);
|
|
620
|
+
}
|
|
621
|
+
} else {
|
|
622
|
+
currentPosition -= currentStep * (deltaTime / 16.67);
|
|
623
|
+
if (currentPosition <= -contentSize - parseInt(gap)) {
|
|
624
|
+
currentPosition = 0;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
scrollContainer.style.transform = `translate3d(${currentPosition}px, 0px, 0px)`;
|
|
629
|
+
animationId = requestAnimationFrame(animate);
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
// Démarrer l'animation
|
|
633
|
+
animationId = requestAnimationFrame(animate);
|
|
634
|
+
|
|
635
|
+
bbContents.utils.log('Marquee horizontal créé avec animation JS - direction:', direction, 'taille:', contentSize + 'px', 'total:', totalSize + 'px');
|
|
636
|
+
|
|
637
|
+
// Pause au survol avec transition fluide CSS + JS
|
|
638
|
+
if (pauseOnHover === 'true') {
|
|
639
|
+
// Transition fluide avec easing naturel
|
|
640
|
+
const transitionSpeed = (targetSpeed, duration = 300) => {
|
|
641
|
+
const startSpeed = currentStep;
|
|
642
|
+
const speedDiff = targetSpeed - startSpeed;
|
|
643
|
+
const startTime = performance.now();
|
|
644
|
+
|
|
645
|
+
// Easing naturel
|
|
646
|
+
const easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
|
|
647
|
+
const easeInCubic = (t) => t * t * t;
|
|
648
|
+
|
|
649
|
+
const animateTransition = (currentTime) => {
|
|
650
|
+
const elapsed = currentTime - startTime;
|
|
651
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
652
|
+
|
|
653
|
+
// Easing différent selon la direction
|
|
654
|
+
const easedProgress = targetSpeed === 0 ?
|
|
655
|
+
easeOutCubic(progress) : easeInCubic(progress);
|
|
656
|
+
|
|
657
|
+
currentStep = startSpeed + speedDiff * easedProgress;
|
|
658
|
+
|
|
659
|
+
if (progress < 1) {
|
|
660
|
+
requestAnimationFrame(animateTransition);
|
|
661
|
+
} else {
|
|
662
|
+
currentStep = targetSpeed;
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
requestAnimationFrame(animateTransition);
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
element.addEventListener('mouseenter', function() {
|
|
670
|
+
transitionSpeed(0); // Ralentir jusqu'à 0
|
|
671
|
+
});
|
|
672
|
+
element.addEventListener('mouseleave', function() {
|
|
673
|
+
transitionSpeed(baseStep); // Revenir à la vitesse normale
|
|
674
|
+
});
|
|
675
|
+
}
|
|
764
676
|
}
|
|
677
|
+
});
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
// Démarrer l'initialisation avec délai adaptatif - Option 1: Attendre que tout soit prêt
|
|
681
|
+
let initDelay = isVertical ? 500 : 200; // Délais plus longs par défaut
|
|
682
|
+
if (bbContents._performanceBoostDetected) {
|
|
683
|
+
initDelay = isVertical ? 800 : 500; // Délais encore plus longs avec bb-performance-boost
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Attendre window.load si pas encore déclenché
|
|
687
|
+
if (document.readyState !== 'complete') {
|
|
688
|
+
bbContents.utils.log('Attente de window.load pour initialiser le marquee');
|
|
689
|
+
window.addEventListener('load', () => {
|
|
690
|
+
setTimeout(() => initAnimation(0), initDelay);
|
|
691
|
+
});
|
|
692
|
+
} else {
|
|
693
|
+
// window.load déjà déclenché, initialiser directement
|
|
694
|
+
setTimeout(() => initAnimation(0), initDelay);
|
|
695
|
+
}
|
|
696
|
+
});
|
|
765
697
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
document.head.appendChild(style);
|
|
698
|
+
bbContents.utils.log('Module Marquee initialisé:', elements.length, 'éléments');
|
|
699
|
+
}
|
|
700
|
+
},
|
|
770
701
|
|
|
771
|
-
|
|
772
|
-
|
|
702
|
+
// Module YouTube Feed
|
|
703
|
+
youtube: {
|
|
704
|
+
// Détection des bots pour éviter les appels API inutiles
|
|
705
|
+
isBot: function() {
|
|
706
|
+
const userAgent = navigator.userAgent.toLowerCase();
|
|
707
|
+
const botPatterns = [
|
|
708
|
+
'bot', 'crawler', 'spider', 'scraper', 'googlebot', 'bingbot', 'slurp',
|
|
709
|
+
'duckduckbot', 'baiduspider', 'yandexbot', 'facebookexternalhit', 'twitterbot',
|
|
710
|
+
'linkedinbot', 'whatsapp', 'telegrambot', 'discordbot', 'slackbot'
|
|
711
|
+
];
|
|
712
|
+
|
|
713
|
+
return botPatterns.some(pattern => userAgent.includes(pattern)) ||
|
|
714
|
+
navigator.webdriver ||
|
|
715
|
+
!navigator.userAgent;
|
|
716
|
+
},
|
|
717
|
+
|
|
718
|
+
// Gestion du cache localStorage
|
|
719
|
+
cache: {
|
|
720
|
+
get: function(key) {
|
|
721
|
+
try {
|
|
722
|
+
const cached = localStorage.getItem(key);
|
|
723
|
+
if (!cached) return null;
|
|
724
|
+
|
|
725
|
+
const data = JSON.parse(cached);
|
|
726
|
+
const now = Date.now();
|
|
727
|
+
|
|
728
|
+
// Cache expiré après 24h
|
|
729
|
+
if (now - data.timestamp > 24 * 60 * 60 * 1000) {
|
|
730
|
+
localStorage.removeItem(key);
|
|
731
|
+
return null;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
return data.value;
|
|
735
|
+
} catch (e) {
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
},
|
|
739
|
+
|
|
740
|
+
set: function(key, value) {
|
|
741
|
+
try {
|
|
742
|
+
const data = {
|
|
743
|
+
value: value,
|
|
744
|
+
timestamp: Date.now()
|
|
745
|
+
};
|
|
746
|
+
localStorage.setItem(key, JSON.stringify(data));
|
|
747
|
+
} catch (e) {
|
|
748
|
+
// Ignorer les erreurs de localStorage
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
},
|
|
752
|
+
|
|
753
|
+
detect: function(scope) {
|
|
754
|
+
return scope.querySelector('[bb-youtube-channel]') !== null;
|
|
755
|
+
},
|
|
756
|
+
|
|
757
|
+
init: function(scope) {
|
|
758
|
+
// Vérifier si c'est un bot - pas d'appel API
|
|
759
|
+
if (this.isBot()) {
|
|
760
|
+
bbContents.utils.log('Bot détecté, pas de chargement YouTube (économie API)');
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Nettoyer le cache expiré au démarrage
|
|
765
|
+
this.cleanCache();
|
|
766
|
+
|
|
767
|
+
const elements = scope.querySelectorAll('[bb-youtube-channel]');
|
|
768
|
+
if (elements.length === 0) return;
|
|
769
|
+
|
|
770
|
+
bbContents.utils.log('Module détecté: youtube');
|
|
771
|
+
|
|
772
|
+
elements.forEach(element => {
|
|
773
|
+
// Vérifier si l'élément a déjà été traité par un autre module
|
|
774
|
+
if (element.bbProcessed || element.hasAttribute('data-bb-marquee-processed')) {
|
|
775
|
+
bbContents.utils.log('Élément youtube déjà traité par un autre module, ignoré:', element);
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
element.bbProcessed = true;
|
|
779
|
+
|
|
780
|
+
const channelId = bbContents._getAttr(element, 'bb-youtube-channel');
|
|
781
|
+
const videoCount = bbContents._getAttr(element, 'bb-youtube-video-count') || '10';
|
|
782
|
+
const allowShorts = bbContents._getAttr(element, 'bb-youtube-allow-shorts') === 'true';
|
|
783
|
+
const language = bbContents._getAttr(element, 'bb-youtube-language') || 'fr';
|
|
784
|
+
const endpoint = bbContents.config.youtubeEndpoint;
|
|
785
|
+
|
|
786
|
+
if (!channelId) {
|
|
787
|
+
bbContents.utils.log('Erreur: bb-youtube-channel manquant');
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (!endpoint) {
|
|
792
|
+
bbContents.utils.log('Erreur: youtubeEndpoint non configuré. Utilisez bbContents.config.youtubeEndpoint = "votre-worker-url"');
|
|
793
|
+
element.innerHTML = '<div style="padding: 20px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; color: #dc2626;"><strong>Configuration YouTube manquante</strong><br>Ajoutez : bbContents.config.youtubeEndpoint = "votre-worker-url"</div>';
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// Chercher le template pour une vidéo (directement dans l'élément ou dans un conteneur)
|
|
798
|
+
let template = element.querySelector('[bb-youtube-item]');
|
|
799
|
+
let container = element;
|
|
800
|
+
|
|
801
|
+
// Si pas de template direct, chercher dans un conteneur
|
|
802
|
+
if (!template) {
|
|
803
|
+
const containerElement = element.querySelector('[bb-youtube-container]');
|
|
804
|
+
if (containerElement) {
|
|
805
|
+
container = containerElement;
|
|
806
|
+
template = containerElement.querySelector('[bb-youtube-item]');
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
if (!template) {
|
|
811
|
+
bbContents.utils.log('Erreur: élément [bb-youtube-item] manquant');
|
|
812
|
+
element.innerHTML = '<div style="padding: 20px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; color: #dc2626;"><strong>Template manquant</strong><br>Ajoutez un élément avec l\'attribut bb-youtube-item</div>';
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Cacher le template original
|
|
817
|
+
template.style.display = 'none';
|
|
818
|
+
|
|
819
|
+
// Marquer l'élément comme traité par le module YouTube
|
|
820
|
+
element.setAttribute('data-bb-youtube-processed', 'true');
|
|
821
|
+
|
|
822
|
+
// Vérifier le cache d'abord
|
|
823
|
+
const cacheKey = `youtube_${channelId}_${videoCount}_${allowShorts}_${language}`;
|
|
824
|
+
const cachedData = this.cache.get(cacheKey);
|
|
825
|
+
|
|
826
|
+
if (cachedData) {
|
|
827
|
+
bbContents.utils.log('Données YouTube récupérées du cache (économie API)');
|
|
828
|
+
this.generateYouTubeFeed(container, template, cachedData, allowShorts, language);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// Afficher un loader
|
|
833
|
+
container.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Chargement des vidéos YouTube...</div>';
|
|
834
|
+
|
|
835
|
+
// Appeler l'API via le Worker
|
|
836
|
+
fetch(`${endpoint}?channelId=${channelId}&maxResults=${videoCount}&allowShorts=${allowShorts}`)
|
|
837
|
+
.then(response => {
|
|
838
|
+
if (!response.ok) {
|
|
839
|
+
throw new Error(`HTTP ${response.status}`);
|
|
840
|
+
}
|
|
841
|
+
return response.json();
|
|
842
|
+
})
|
|
843
|
+
.then(data => {
|
|
844
|
+
if (data.error) {
|
|
845
|
+
throw new Error(data.error.message || 'Erreur API YouTube');
|
|
846
|
+
}
|
|
773
847
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
848
|
+
// Sauvegarder en cache pour 24h
|
|
849
|
+
this.cache.set(cacheKey, data);
|
|
850
|
+
bbContents.utils.log('Données YouTube mises en cache pour 24h (économie API)');
|
|
851
|
+
|
|
852
|
+
this.generateYouTubeFeed(container, template, data, allowShorts, language);
|
|
853
|
+
})
|
|
854
|
+
.catch(error => {
|
|
855
|
+
bbContents.utils.log('Erreur dans le module youtube:', error);
|
|
856
|
+
|
|
857
|
+
// En cas d'erreur, essayer de récupérer du cache même expiré
|
|
858
|
+
const expiredCache = localStorage.getItem(cacheKey);
|
|
859
|
+
if (expiredCache) {
|
|
860
|
+
try {
|
|
861
|
+
const cachedData = JSON.parse(expiredCache);
|
|
862
|
+
bbContents.utils.log('Utilisation du cache expiré en cas d\'erreur API');
|
|
863
|
+
this.generateYouTubeFeed(container, template, cachedData.value, allowShorts);
|
|
864
|
+
return;
|
|
865
|
+
} catch (e) {
|
|
866
|
+
// Ignorer les erreurs de parsing
|
|
867
|
+
}
|
|
784
868
|
}
|
|
869
|
+
|
|
870
|
+
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>`;
|
|
871
|
+
});
|
|
872
|
+
});
|
|
873
|
+
},
|
|
874
|
+
|
|
875
|
+
generateYouTubeFeed: function(container, template, data, allowShorts, language = 'fr') {
|
|
876
|
+
if (!data.items || data.items.length === 0) {
|
|
877
|
+
container.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Aucune vidéo trouvée</div>';
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// Les vidéos sont déjà filtrées par l'API YouTube selon allowShorts
|
|
882
|
+
let videos = data.items;
|
|
883
|
+
bbContents.utils.log(`Vidéos reçues de l'API: ${videos.length} (allowShorts: ${allowShorts})`);
|
|
884
|
+
|
|
885
|
+
// Vider le conteneur (en préservant les éléments marquee)
|
|
886
|
+
const marqueeElements = container.querySelectorAll('[data-bb-marquee-processed]');
|
|
887
|
+
container.innerHTML = '';
|
|
888
|
+
|
|
889
|
+
// Restaurer les éléments marquee si présents
|
|
890
|
+
marqueeElements.forEach(marqueeEl => {
|
|
891
|
+
container.appendChild(marqueeEl);
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
// Cloner le template pour chaque vidéo
|
|
895
|
+
videos.forEach(item => {
|
|
896
|
+
const videoId = item.id.videoId;
|
|
897
|
+
const snippet = item.snippet;
|
|
898
|
+
|
|
899
|
+
// Cloner le template
|
|
900
|
+
const clone = template.cloneNode(true);
|
|
901
|
+
clone.style.display = ''; // Rendre visible
|
|
902
|
+
|
|
903
|
+
// Remplir les données
|
|
904
|
+
this.fillVideoData(clone, videoId, snippet, language);
|
|
905
|
+
|
|
906
|
+
// Ajouter au conteneur
|
|
907
|
+
container.appendChild(clone);
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
bbContents.utils.log(`YouTube Feed généré: ${videos.length} vidéos`);
|
|
911
|
+
},
|
|
912
|
+
|
|
913
|
+
fillVideoData: function(element, videoId, snippet, language = 'fr') {
|
|
914
|
+
// Remplir le lien directement sur l'élément (link block)
|
|
915
|
+
if (element.tagName === 'A' || element.hasAttribute('bb-youtube-item')) {
|
|
916
|
+
element.href = `https://www.youtube.com/watch?v=${videoId}`;
|
|
917
|
+
element.target = '_blank';
|
|
918
|
+
element.rel = 'noopener noreferrer';
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// Remplir la thumbnail (qualité optimisée)
|
|
922
|
+
const thumbnail = element.querySelector('[bb-youtube-thumbnail]');
|
|
923
|
+
if (thumbnail) {
|
|
924
|
+
// Logique optimisée pour la meilleure qualité disponible
|
|
925
|
+
let bestThumbnailUrl = null;
|
|
926
|
+
let bestQuality = 'unknown';
|
|
927
|
+
|
|
928
|
+
// Priorité 1: maxres (1280x720) - qualité maximale
|
|
929
|
+
if (snippet.thumbnails.maxres?.url) {
|
|
930
|
+
bestThumbnailUrl = snippet.thumbnails.maxres.url;
|
|
931
|
+
bestQuality = 'maxres (1280x720)';
|
|
932
|
+
}
|
|
933
|
+
// Priorité 2: high (480x360) - bonne qualité pour l'affichage
|
|
934
|
+
else if (snippet.thumbnails.high?.url) {
|
|
935
|
+
bestThumbnailUrl = snippet.thumbnails.high.url;
|
|
936
|
+
bestQuality = 'high (480x360)';
|
|
937
|
+
}
|
|
938
|
+
// Priorité 3: medium (320x180) - qualité acceptable en dernier recours
|
|
939
|
+
else if (snippet.thumbnails.medium?.url) {
|
|
940
|
+
bestThumbnailUrl = snippet.thumbnails.medium.url;
|
|
941
|
+
bestQuality = 'medium (320x180)';
|
|
942
|
+
}
|
|
943
|
+
// Fallback: default (120x90) - seulement si rien d'autre
|
|
944
|
+
else if (snippet.thumbnails.default?.url) {
|
|
945
|
+
bestThumbnailUrl = snippet.thumbnails.default.url;
|
|
946
|
+
bestQuality = 'default (120x90)';
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// Appliquer la meilleure thumbnail trouvée
|
|
950
|
+
if (bestThumbnailUrl) {
|
|
951
|
+
thumbnail.src = bestThumbnailUrl;
|
|
952
|
+
thumbnail.alt = snippet.title;
|
|
953
|
+
|
|
954
|
+
// Debug: logger la qualité utilisée (en mode debug seulement)
|
|
955
|
+
if (bbContents.config.debug) {
|
|
956
|
+
bbContents.utils.log(`Thumbnail optimisée pour ${snippet.title}: ${bestQuality}`);
|
|
785
957
|
}
|
|
786
|
-
}
|
|
958
|
+
} else {
|
|
959
|
+
bbContents.utils.log('Aucune thumbnail disponible pour:', snippet.title);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// Remplir le titre (avec décodage HTML)
|
|
964
|
+
const title = element.querySelector('[bb-youtube-title]');
|
|
965
|
+
if (title) {
|
|
966
|
+
title.textContent = this.decodeHtmlEntities(snippet.title);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// Remplir la description (avec décodage HTML)
|
|
970
|
+
const description = element.querySelector('[bb-youtube-description]');
|
|
971
|
+
if (description) {
|
|
972
|
+
description.textContent = this.decodeHtmlEntities(snippet.description);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// Remplir la date
|
|
976
|
+
const date = element.querySelector('[bb-youtube-date]');
|
|
977
|
+
if (date) {
|
|
978
|
+
date.textContent = this.formatDate(snippet.publishedAt, language);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// Remplir le nom de la chaîne
|
|
982
|
+
const channel = element.querySelector('[bb-youtube-channel]');
|
|
983
|
+
if (channel) {
|
|
984
|
+
channel.textContent = snippet.channelTitle;
|
|
985
|
+
}
|
|
986
|
+
},
|
|
987
|
+
|
|
988
|
+
formatDate: function(dateString, language = 'fr') {
|
|
989
|
+
const date = new Date(dateString);
|
|
990
|
+
const now = new Date();
|
|
991
|
+
const diffTime = Math.abs(now - date);
|
|
992
|
+
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
993
|
+
|
|
994
|
+
// Traductions
|
|
995
|
+
const translations = {
|
|
996
|
+
fr: {
|
|
997
|
+
day: 'jour',
|
|
998
|
+
days: 'jours',
|
|
999
|
+
week: 'semaine',
|
|
1000
|
+
weeks: 'semaines',
|
|
1001
|
+
month: 'mois',
|
|
1002
|
+
months: 'mois',
|
|
1003
|
+
year: 'an',
|
|
1004
|
+
years: 'ans',
|
|
1005
|
+
ago: 'Il y a'
|
|
1006
|
+
},
|
|
1007
|
+
en: {
|
|
1008
|
+
day: 'day',
|
|
1009
|
+
days: 'days',
|
|
1010
|
+
week: 'week',
|
|
1011
|
+
weeks: 'weeks',
|
|
1012
|
+
month: 'month',
|
|
1013
|
+
months: 'months',
|
|
1014
|
+
year: 'year',
|
|
1015
|
+
years: 'years',
|
|
1016
|
+
ago: 'ago'
|
|
1017
|
+
}
|
|
787
1018
|
};
|
|
788
1019
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
1020
|
+
const t = translations[language] || translations.fr;
|
|
1021
|
+
|
|
1022
|
+
if (diffDays === 1) return `${t.ago} 1 ${t.day}`;
|
|
1023
|
+
if (diffDays < 7) return `${t.ago} ${diffDays} ${t.days}`;
|
|
1024
|
+
|
|
1025
|
+
const weeks = Math.floor(diffDays / 7);
|
|
1026
|
+
if (weeks === 1) return `${t.ago} 1 ${t.week}`;
|
|
1027
|
+
if (diffDays < 30) return `${t.ago} ${weeks} ${t.weeks}`;
|
|
1028
|
+
|
|
1029
|
+
const months = Math.floor(diffDays / 30);
|
|
1030
|
+
if (months === 1) return `${t.ago} 1 ${t.month}`;
|
|
1031
|
+
if (diffDays < 365) return `${t.ago} ${months} ${t.months}`;
|
|
1032
|
+
|
|
1033
|
+
const years = Math.floor(diffDays / 365);
|
|
1034
|
+
if (years === 1) return `${t.ago} 1 ${t.year}`;
|
|
1035
|
+
return `${t.ago} ${years} ${t.years}`;
|
|
1036
|
+
},
|
|
1037
|
+
|
|
1038
|
+
// Fonction pour décoder les entités HTML
|
|
1039
|
+
decodeHtmlEntities: function(text) {
|
|
1040
|
+
if (!text) return '';
|
|
1041
|
+
const textarea = document.createElement('textarea');
|
|
1042
|
+
textarea.innerHTML = text;
|
|
1043
|
+
return textarea.value;
|
|
1044
|
+
},
|
|
1045
|
+
|
|
1046
|
+
// Nettoyer le cache expiré
|
|
1047
|
+
cleanCache: function() {
|
|
1048
|
+
try {
|
|
1049
|
+
const keys = Object.keys(localStorage);
|
|
1050
|
+
const now = Date.now();
|
|
1051
|
+
let cleaned = 0;
|
|
1052
|
+
|
|
1053
|
+
keys.forEach(key => {
|
|
1054
|
+
if (key.startsWith('youtube_')) {
|
|
1055
|
+
try {
|
|
1056
|
+
const cached = JSON.parse(localStorage.getItem(key));
|
|
1057
|
+
if (now - cached.timestamp > 24 * 60 * 60 * 1000) {
|
|
1058
|
+
localStorage.removeItem(key);
|
|
1059
|
+
cleaned++;
|
|
1060
|
+
}
|
|
1061
|
+
} catch (e) {
|
|
1062
|
+
// Supprimer les clés corrompues
|
|
1063
|
+
localStorage.removeItem(key);
|
|
1064
|
+
cleaned++;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
if (cleaned > 0) {
|
|
1070
|
+
bbContents.utils.log(`Cache YouTube nettoyé: ${cleaned} entrées supprimées`);
|
|
1071
|
+
}
|
|
1072
|
+
} catch (e) {
|
|
1073
|
+
// Ignorer les erreurs de nettoyage
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
794
1076
|
}
|
|
795
1077
|
};
|
|
796
1078
|
|
|
797
|
-
|
|
798
|
-
|
|
799
1079
|
// Exposer globalement
|
|
800
1080
|
window.bbContents = bbContents;
|
|
801
1081
|
|
|
@@ -805,24 +1085,45 @@
|
|
|
805
1085
|
if (document.readyState === 'loading') {
|
|
806
1086
|
document.addEventListener('DOMContentLoaded', function() {
|
|
807
1087
|
// Délai pour éviter le blocage du rendu
|
|
1088
|
+
const delay = document.body.hasAttribute('bb-performance-boost') ? 300 : 100;
|
|
808
1089
|
setTimeout(function() {
|
|
809
1090
|
bbContents.init();
|
|
810
|
-
},
|
|
1091
|
+
}, delay);
|
|
811
1092
|
});
|
|
812
1093
|
} else {
|
|
813
1094
|
// Délai pour éviter le blocage du rendu
|
|
1095
|
+
const delay = document.body.hasAttribute('bb-performance-boost') ? 300 : 100;
|
|
814
1096
|
setTimeout(function() {
|
|
815
1097
|
bbContents.init();
|
|
816
|
-
},
|
|
1098
|
+
}, delay);
|
|
817
1099
|
}
|
|
1100
|
+
|
|
1101
|
+
// Initialisation différée supplémentaire pour les cas difficiles - Option 1: Attendre que tout soit vraiment prêt
|
|
1102
|
+
window.addEventListener('load', function() {
|
|
1103
|
+
const loadDelay = document.body.hasAttribute('bb-performance-boost') ? 3000 : 1500; // Délais plus longs
|
|
1104
|
+
setTimeout(function() {
|
|
1105
|
+
// Vérifier s'il y a des éléments non initialisés
|
|
1106
|
+
const unprocessedMarquees = document.querySelectorAll('[bb-marquee]:not([data-bb-marquee-processed])');
|
|
1107
|
+
if (unprocessedMarquees.length > 0) {
|
|
1108
|
+
bbContents.utils.log('Éléments marquee non initialisés détectés après load, réinitialisation...');
|
|
1109
|
+
bbContents.reinit();
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// Vérification supplémentaire des images chargées
|
|
1113
|
+
const allImages = document.querySelectorAll('img');
|
|
1114
|
+
const unloadedImages = Array.from(allImages).filter(img => !img.complete || img.naturalHeight === 0);
|
|
1115
|
+
if (unloadedImages.length > 0) {
|
|
1116
|
+
bbContents.utils.log('Images non chargées détectées:', unloadedImages.length, '- attente supplémentaire...');
|
|
1117
|
+
setTimeout(() => {
|
|
1118
|
+
bbContents.reinit();
|
|
1119
|
+
}, 1000);
|
|
1120
|
+
}
|
|
1121
|
+
}, loadDelay);
|
|
1122
|
+
});
|
|
818
1123
|
}
|
|
819
1124
|
|
|
820
1125
|
// Initialisation
|
|
821
1126
|
initBBContents();
|
|
822
1127
|
|
|
823
|
-
// Message de confirmation
|
|
824
|
-
console.log(
|
|
825
|
-
'%cBeBranded Contents v' + config.version + ' chargé avec succès !',
|
|
826
|
-
'color: #422eff; font-weight: bold; font-size: 14px;'
|
|
827
|
-
);
|
|
1128
|
+
// Message de confirmation supprimé pour une console plus propre
|
|
828
1129
|
})();
|