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