@bebranded/bb-contents 1.0.0 → 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 +934 -611
- 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.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.
|
|
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,683 +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);
|
|
209
|
+
// Modules
|
|
210
|
+
bbContents.modules = {
|
|
211
|
+
// Module SEO
|
|
212
|
+
seo: {
|
|
213
|
+
detect: function(scope) {
|
|
214
|
+
return scope.querySelector('[bb-seo]') !== null;
|
|
190
215
|
},
|
|
191
|
-
telegram: function(data) {
|
|
192
|
-
return 'https://t.me/share/url?url=' +
|
|
193
|
-
encodeURIComponent(data.url) +
|
|
194
|
-
'&text=' + encodeURIComponent(data.text);
|
|
195
|
-
},
|
|
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
|
-
|
|
223
|
+
elements.forEach(element => {
|
|
224
|
+
if (element.bbProcessed) return;
|
|
225
|
+
element.bbProcessed = true;
|
|
226
|
+
|
|
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');
|
|
247
230
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
259
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
if (customFormat && customFormat.includes('{year}')) {
|
|
395
|
-
element.textContent = customFormat.replace('{year}', year);
|
|
396
|
-
} else if (prefix || suffix) {
|
|
397
|
-
element.textContent = prefix + year + suffix;
|
|
398
|
-
} else {
|
|
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
|
-
|
|
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');
|
|
591
405
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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
|
+
`;
|
|
605
422
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
`;
|
|
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
|
+
`;
|
|
621
437
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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
|
+
`;
|
|
634
450
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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');
|
|
650
469
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
const
|
|
656
|
-
const contentHeight = mainBlock.offsetHeight;
|
|
657
|
-
|
|
658
|
-
// Debug
|
|
659
|
-
bbContents.utils.log('Debug - Largeur du contenu:', contentWidth, 'px', 'Hauteur:', contentHeight, 'px', 'Enfants:', mainBlock.children.length, 'Vertical:', isVertical, 'Direction:', direction);
|
|
660
|
-
|
|
661
|
-
// Si pas de contenu, réessayer
|
|
662
|
-
if ((isVertical && contentHeight === 0) || (!isVertical && contentWidth === 0)) {
|
|
663
|
-
bbContents.utils.log('Contenu non prêt, nouvelle tentative dans 200ms');
|
|
664
|
-
setTimeout(initAnimation, 200);
|
|
665
|
-
return;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
// Pour le vertical, s'assurer qu'on a une hauteur minimale
|
|
669
|
-
if (isVertical && contentHeight < 50) {
|
|
670
|
-
bbContents.utils.log('Hauteur insuffisante pour le marquee vertical (' + contentHeight + 'px), nouvelle tentative dans 200ms');
|
|
671
|
-
setTimeout(initAnimation, 200);
|
|
672
|
-
return;
|
|
673
|
-
}
|
|
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);
|
|
674
475
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
const
|
|
679
|
-
|
|
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;
|
|
680
482
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
let
|
|
483
|
+
// Pour les marquees verticaux, utiliser la largeur du parent si nécessaire
|
|
484
|
+
let finalWidth = contentWidth;
|
|
485
|
+
let finalHeight = contentHeight;
|
|
684
486
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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)');
|
|
492
|
+
}
|
|
493
|
+
|
|
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
|
+
}
|
|
513
|
+
|
|
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
|
+
|
|
688
537
|
if (direction === 'bottom') {
|
|
689
|
-
currentPosition +=
|
|
538
|
+
currentPosition += currentStep * (deltaTime / 16.67); // Normaliser à 60fps
|
|
690
539
|
if (currentPosition >= 0) {
|
|
691
540
|
currentPosition = -contentSize - parseInt(gap);
|
|
692
541
|
}
|
|
693
542
|
} else {
|
|
694
|
-
currentPosition -=
|
|
543
|
+
currentPosition -= currentStep * (deltaTime / 16.67);
|
|
695
544
|
if (currentPosition <= -contentSize - parseInt(gap)) {
|
|
696
545
|
currentPosition = 0;
|
|
697
546
|
}
|
|
698
547
|
}
|
|
699
548
|
|
|
700
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
|
+
});
|
|
701
596
|
}
|
|
702
|
-
requestAnimationFrame(animate);
|
|
703
|
-
};
|
|
704
|
-
|
|
705
|
-
// Démarrer l'animation
|
|
706
|
-
animate();
|
|
707
|
-
|
|
708
|
-
bbContents.utils.log('Marquee vertical créé avec animation JS - direction:', direction, 'taille:', contentSize + 'px', 'total:', totalSize + 'px', 'hauteur-wrapper:', height + 'px');
|
|
709
|
-
|
|
710
|
-
// Pause au survol
|
|
711
|
-
if (pauseOnHover === 'true') {
|
|
712
|
-
element.addEventListener('mouseenter', function() {
|
|
713
|
-
isPaused = true;
|
|
714
|
-
});
|
|
715
|
-
element.addEventListener('mouseleave', function() {
|
|
716
|
-
isPaused = false;
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
} else {
|
|
720
|
-
// Animation CSS pour l'horizontal (modifiée)
|
|
721
|
-
const contentSize = contentWidth;
|
|
722
|
-
const totalSize = contentSize * 4 + parseInt(gap) * 3; // 4 copies au lieu de 3
|
|
723
|
-
scrollContainer.style.width = totalSize + 'px';
|
|
724
|
-
|
|
725
|
-
// Créer l'animation CSS optimisée
|
|
726
|
-
const animationName = 'bb-scroll-' + Math.random().toString(36).substr(2, 9);
|
|
727
|
-
const animationDuration = (totalSize / (parseFloat(speed) * 1.5)).toFixed(2) + 's'; // Vitesse différente
|
|
728
|
-
|
|
729
|
-
// Animation avec translate3d pour hardware acceleration
|
|
730
|
-
let keyframes;
|
|
731
|
-
if (direction === 'right') {
|
|
732
|
-
keyframes = `@keyframes ${animationName} {
|
|
733
|
-
0% { transform: translate3d(-${contentSize + parseInt(gap)}px, 0px, 0px); }
|
|
734
|
-
100% { transform: translate3d(0px, 0px, 0px); }
|
|
735
|
-
}`;
|
|
736
597
|
} else {
|
|
737
|
-
//
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
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
|
+
}
|
|
742
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
|
+
});
|
|
743
697
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
document.head.appendChild(style);
|
|
698
|
+
bbContents.utils.log('Module Marquee initialisé:', elements.length, 'éléments');
|
|
699
|
+
}
|
|
700
|
+
},
|
|
748
701
|
|
|
749
|
-
|
|
750
|
-
|
|
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
|
+
}
|
|
751
847
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
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
|
+
}
|
|
762
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}`);
|
|
763
957
|
}
|
|
764
|
-
}
|
|
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
|
+
}
|
|
765
1018
|
};
|
|
766
1019
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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
|
+
}
|
|
772
1076
|
}
|
|
773
1077
|
};
|
|
774
1078
|
|
|
775
|
-
|
|
776
|
-
|
|
777
1079
|
// Exposer globalement
|
|
778
1080
|
window.bbContents = bbContents;
|
|
779
1081
|
|
|
@@ -783,24 +1085,45 @@
|
|
|
783
1085
|
if (document.readyState === 'loading') {
|
|
784
1086
|
document.addEventListener('DOMContentLoaded', function() {
|
|
785
1087
|
// Délai pour éviter le blocage du rendu
|
|
1088
|
+
const delay = document.body.hasAttribute('bb-performance-boost') ? 300 : 100;
|
|
786
1089
|
setTimeout(function() {
|
|
787
1090
|
bbContents.init();
|
|
788
|
-
},
|
|
1091
|
+
}, delay);
|
|
789
1092
|
});
|
|
790
1093
|
} else {
|
|
791
1094
|
// Délai pour éviter le blocage du rendu
|
|
1095
|
+
const delay = document.body.hasAttribute('bb-performance-boost') ? 300 : 100;
|
|
792
1096
|
setTimeout(function() {
|
|
793
1097
|
bbContents.init();
|
|
794
|
-
},
|
|
1098
|
+
}, delay);
|
|
795
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
|
+
});
|
|
796
1123
|
}
|
|
797
1124
|
|
|
798
1125
|
// Initialisation
|
|
799
1126
|
initBBContents();
|
|
800
1127
|
|
|
801
|
-
// Message de confirmation
|
|
802
|
-
console.log(
|
|
803
|
-
'%cBeBranded Contents v' + config.version + ' chargé avec succès !',
|
|
804
|
-
'color: #422eff; font-weight: bold; font-size: 14px;'
|
|
805
|
-
);
|
|
1128
|
+
// Message de confirmation supprimé pour une console plus propre
|
|
806
1129
|
})();
|