@bebranded/bb-contents 1.0.5-beta → 1.0.7-beta
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 +425 -771
- 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.6-beta
|
|
5
5
|
* @author BeBranded
|
|
6
6
|
* @license MIT
|
|
7
7
|
* @website https://www.bebranded.xyz
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
// Configuration
|
|
19
19
|
const config = {
|
|
20
|
-
version: '1.0.
|
|
20
|
+
version: '1.0.6-beta',
|
|
21
21
|
debug: window.location.hostname === 'localhost' || window.location.hostname.includes('webflow.io'),
|
|
22
22
|
prefix: 'bb-', // utilisé pour générer les sélecteurs (data-bb-*)
|
|
23
23
|
i18n: {
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
}
|
|
60
60
|
},
|
|
61
61
|
|
|
62
|
-
// Helper: construire des sélecteurs d
|
|
62
|
+
// Helper: construire des sélecteurs d'attributs selon le prefix
|
|
63
63
|
_attrSelector: function(name) {
|
|
64
64
|
const p = (this.config.prefix || 'bb-').replace(/-?$/, '-');
|
|
65
65
|
const legacy = name.startsWith('bb-') ? name : (p + name);
|
|
@@ -99,833 +99,487 @@
|
|
|
99
99
|
this.setupObserver();
|
|
100
100
|
},
|
|
101
101
|
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (rootNode.closest && rootNode.closest('[data-bb-disable]')) return;
|
|
107
|
-
|
|
108
|
-
// Éviter les ré-initialisations multiples sur le même scope
|
|
109
|
-
if (rootNode === document && this._lastReinitTime && (Date.now() - this._lastReinitTime) < 1000) {
|
|
110
|
-
return; // Éviter les reinit trop fréquents sur document
|
|
102
|
+
// Observer DOM pour contenu dynamique
|
|
103
|
+
setupObserver: function() {
|
|
104
|
+
if (this._observer) {
|
|
105
|
+
this._observer.disconnect();
|
|
111
106
|
}
|
|
112
|
-
this._lastReinitTime = Date.now();
|
|
113
|
-
|
|
114
|
-
Object.keys(this.modules).forEach(function(moduleName) {
|
|
115
|
-
const module = bbContents.modules[moduleName];
|
|
116
|
-
try {
|
|
117
|
-
module.init(rootNode);
|
|
118
|
-
} catch (error) {
|
|
119
|
-
console.error('[BB Contents] Erreur reinit dans le module', moduleName, error);
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
},
|
|
123
107
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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];
|
|
108
|
+
this._observer = new MutationObserver((mutations) => {
|
|
109
|
+
let shouldReinit = false;
|
|
110
|
+
|
|
111
|
+
mutations.forEach((mutation) => {
|
|
112
|
+
if (mutation.type === 'childList') {
|
|
113
|
+
mutation.addedNodes.forEach((node) => {
|
|
136
114
|
if (node.nodeType === 1) { // Element node
|
|
115
|
+
// Vérifier si le nouveau nœud ou ses enfants ont des attributs bb-*
|
|
137
116
|
if (node.querySelector && (
|
|
138
|
-
node.querySelector('[bb-]
|
|
139
|
-
node.
|
|
117
|
+
node.querySelector('[bb-]') ||
|
|
118
|
+
node.querySelector('[data-bb-]') ||
|
|
119
|
+
node.matches && (node.matches('[bb-]') || node.matches('[data-bb-]'))
|
|
140
120
|
)) {
|
|
141
|
-
|
|
142
|
-
break;
|
|
121
|
+
shouldReinit = true;
|
|
143
122
|
}
|
|
144
123
|
}
|
|
145
|
-
}
|
|
124
|
+
});
|
|
146
125
|
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (shouldReinit && !this._reinitScheduled) {
|
|
129
|
+
this._reinitScheduled = true;
|
|
130
|
+
setTimeout(() => {
|
|
131
|
+
this.init();
|
|
132
|
+
this._reinitScheduled = false;
|
|
133
|
+
}, 100);
|
|
147
134
|
}
|
|
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
135
|
});
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
136
|
+
|
|
137
|
+
this._observer.observe(document.body, {
|
|
138
|
+
childList: true,
|
|
139
|
+
subtree: true
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
this.utils.log('MutationObserver actif');
|
|
165
143
|
}
|
|
166
144
|
};
|
|
167
145
|
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
twitter: function(data) {
|
|
175
|
-
return 'https://twitter.com/intent/tweet?url=' +
|
|
176
|
-
encodeURIComponent(data.url) +
|
|
177
|
-
'&text=' + encodeURIComponent(data.text);
|
|
178
|
-
},
|
|
179
|
-
facebook: function(data) {
|
|
180
|
-
return 'https://facebook.com/sharer/sharer.php?u=' +
|
|
181
|
-
encodeURIComponent(data.url);
|
|
182
|
-
},
|
|
183
|
-
linkedin: function(data) {
|
|
184
|
-
// LinkedIn - URL de partage officielle (2024+)
|
|
185
|
-
return 'https://www.linkedin.com/sharing/share-offsite/?url=' + encodeURIComponent(data.url);
|
|
186
|
-
},
|
|
187
|
-
whatsapp: function(data) {
|
|
188
|
-
return 'https://wa.me/?text=' +
|
|
189
|
-
encodeURIComponent(data.text + ' ' + data.url);
|
|
190
|
-
},
|
|
191
|
-
telegram: function(data) {
|
|
192
|
-
return 'https://t.me/share/url?url=' +
|
|
193
|
-
encodeURIComponent(data.url) +
|
|
194
|
-
'&text=' + encodeURIComponent(data.text);
|
|
146
|
+
// Modules
|
|
147
|
+
bbContents.modules = {
|
|
148
|
+
// Module SEO
|
|
149
|
+
seo: {
|
|
150
|
+
detect: function(scope) {
|
|
151
|
+
return scope.querySelector('[bb-seo]') !== null;
|
|
195
152
|
},
|
|
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
153
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (
|
|
224
|
-
element.bbProcessed = true;
|
|
154
|
+
init: function(scope) {
|
|
155
|
+
const elements = scope.querySelectorAll('[bb-seo]');
|
|
156
|
+
if (elements.length === 0) return;
|
|
225
157
|
|
|
226
|
-
|
|
227
|
-
const network = bbContents._getAttr(element, 'bb-share');
|
|
228
|
-
const customUrl = bbContents._getAttr(element, 'bb-url');
|
|
229
|
-
const customText = bbContents._getAttr(element, 'bb-text');
|
|
158
|
+
bbContents.utils.log('Module détecté: seo');
|
|
230
159
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
element.addEventListener('keydown', function(e) {
|
|
250
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
251
|
-
e.preventDefault();
|
|
252
|
-
bbContents.modules.share.share(network, data, element);
|
|
160
|
+
elements.forEach(element => {
|
|
161
|
+
if (element.bbProcessed) return;
|
|
162
|
+
element.bbProcessed = true;
|
|
163
|
+
|
|
164
|
+
const title = bbContents._getAttr(element, 'bb-seo-title');
|
|
165
|
+
const description = bbContents._getAttr(element, 'bb-seo-description');
|
|
166
|
+
const keywords = bbContents._getAttr(element, 'bb-seo-keywords');
|
|
167
|
+
|
|
168
|
+
if (title) {
|
|
169
|
+
document.title = title;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (description) {
|
|
173
|
+
let meta = document.querySelector('meta[name="description"]');
|
|
174
|
+
if (!meta) {
|
|
175
|
+
meta = document.createElement('meta');
|
|
176
|
+
meta.name = 'description';
|
|
177
|
+
document.head.appendChild(meta);
|
|
253
178
|
}
|
|
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é !'));
|
|
179
|
+
meta.content = description;
|
|
312
180
|
}
|
|
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);
|
|
181
|
+
|
|
182
|
+
if (keywords) {
|
|
183
|
+
let meta = document.querySelector('meta[name="keywords"]');
|
|
184
|
+
if (!meta) {
|
|
185
|
+
meta = document.createElement('meta');
|
|
186
|
+
meta.name = 'keywords';
|
|
187
|
+
document.head.appendChild(meta);
|
|
188
|
+
}
|
|
189
|
+
meta.content = keywords;
|
|
350
190
|
}
|
|
351
191
|
});
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
bbContents.utils.log('Web Share API non disponible, fallback vers copie');
|
|
355
|
-
this.copyToClipboard(data.url, element, false);
|
|
192
|
+
|
|
193
|
+
bbContents.utils.log('Module SEO initialisé:', elements.length, 'éléments');
|
|
356
194
|
}
|
|
357
195
|
},
|
|
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
196
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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;
|
|
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;
|
|
433
|
-
|
|
434
|
-
// Validation des valeurs
|
|
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;
|
|
197
|
+
// Module Images
|
|
198
|
+
images: {
|
|
199
|
+
detect: function(scope) {
|
|
200
|
+
return scope.querySelector('[bb-images]') !== null;
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
init: function(scope) {
|
|
204
|
+
const elements = scope.querySelectorAll('[bb-images]');
|
|
205
|
+
if (elements.length === 0) return;
|
|
450
206
|
|
|
451
|
-
|
|
452
|
-
const images = sourceNode.querySelectorAll('img');
|
|
453
|
-
const imageCount = images.length;
|
|
454
|
-
const imageTimeInMinutes = (imageCount * secondsPerImage) / 60;
|
|
207
|
+
bbContents.utils.log('Module détecté: images');
|
|
455
208
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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');
|
|
209
|
+
elements.forEach(element => {
|
|
210
|
+
if (element.bbProcessed) return;
|
|
211
|
+
element.bbProcessed = true;
|
|
212
|
+
|
|
213
|
+
const lazy = bbContents._getAttr(element, 'bb-images-lazy');
|
|
214
|
+
const webp = bbContents._getAttr(element, 'bb-images-webp');
|
|
215
|
+
|
|
216
|
+
if (lazy === 'true') {
|
|
217
|
+
// Implémentation lazy loading basique
|
|
218
|
+
const images = element.querySelectorAll('img');
|
|
219
|
+
images.forEach(img => {
|
|
220
|
+
if (!img.loading) {
|
|
221
|
+
img.loading = 'lazy';
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (webp === 'true') {
|
|
227
|
+
// Support WebP basique
|
|
228
|
+
const images = element.querySelectorAll('img');
|
|
229
|
+
images.forEach(img => {
|
|
230
|
+
const src = img.src;
|
|
231
|
+
if (src && !src.includes('.webp')) {
|
|
232
|
+
// Logique de conversion WebP (à implémenter selon les besoins)
|
|
233
|
+
bbContents.utils.log('Support WebP activé pour:', src);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
});
|
|
504
238
|
|
|
505
|
-
|
|
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);
|
|
239
|
+
bbContents.utils.log('Module Images initialisé:', elements.length, 'éléments');
|
|
526
240
|
}
|
|
527
|
-
return favicon;
|
|
528
241
|
},
|
|
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();
|
|
553
|
-
|
|
554
|
-
// Écouter les changements du mode sombre
|
|
555
|
-
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
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) {
|
|
570
|
-
const s = scope || document;
|
|
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;
|
|
583
|
-
|
|
584
|
-
// Récupérer les options
|
|
585
|
-
const speed = bbContents._getAttr(element, 'bb-marquee-speed') || '100';
|
|
586
|
-
const direction = bbContents._getAttr(element, 'bb-marquee-direction') || 'left';
|
|
587
|
-
const pauseOnHover = bbContents._getAttr(element, 'bb-marquee-pause') || 'true';
|
|
588
|
-
const gap = bbContents._getAttr(element, 'bb-marquee-gap') || '50';
|
|
589
|
-
const orientation = bbContents._getAttr(element, 'bb-marquee-orientation') || 'horizontal';
|
|
590
|
-
const height = bbContents._getAttr(element, 'bb-marquee-height') || '300';
|
|
591
|
-
const isVertical = orientation === 'vertical';
|
|
592
|
-
const minHeight = bbContents._getAttr(element, 'bb-marquee-min-height') || (isVertical ? '100px' : 'auto');
|
|
593
|
-
|
|
594
|
-
// Sauvegarder le contenu original
|
|
595
|
-
const originalHTML = element.innerHTML;
|
|
596
|
-
|
|
597
|
-
// Créer le conteneur principal
|
|
598
|
-
const mainContainer = document.createElement('div');
|
|
599
|
-
// Pour le marquee horizontal, on va détecter automatiquement la hauteur des logos
|
|
600
|
-
const autoHeight = !isVertical && !bbContents._getAttr(element, 'bb-marquee-height');
|
|
601
|
-
|
|
602
|
-
mainContainer.style.cssText = `
|
|
603
|
-
position: relative;
|
|
604
|
-
width: 100%;
|
|
605
|
-
height: ${isVertical ? height + 'px' : (autoHeight ? 'auto' : height + 'px')};
|
|
606
|
-
overflow: hidden;
|
|
607
|
-
min-height: ${minHeight};
|
|
608
|
-
`;
|
|
609
|
-
|
|
610
|
-
// Créer le conteneur de défilement
|
|
611
|
-
const scrollContainer = document.createElement('div');
|
|
612
|
-
scrollContainer.style.cssText = `
|
|
613
|
-
position: absolute;
|
|
614
|
-
will-change: transform;
|
|
615
|
-
height: 100%;
|
|
616
|
-
top: 0px;
|
|
617
|
-
left: 0px;
|
|
618
|
-
display: flex;
|
|
619
|
-
${isVertical ? 'flex-direction: column;' : ''}
|
|
620
|
-
align-items: center;
|
|
621
|
-
gap: ${gap}px;
|
|
622
|
-
${isVertical ? '' : 'white-space: nowrap;'}
|
|
623
|
-
flex-shrink: 0;
|
|
624
|
-
`;
|
|
625
242
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
flex-shrink: 0;
|
|
636
|
-
${isVertical ? 'min-height: 100px;' : ''}
|
|
637
|
-
`;
|
|
638
|
-
|
|
639
|
-
// Créer plusieurs répétitions pour un défilement continu
|
|
640
|
-
const repeatBlock1 = mainBlock.cloneNode(true);
|
|
641
|
-
const repeatBlock2 = mainBlock.cloneNode(true);
|
|
642
|
-
const repeatBlock3 = mainBlock.cloneNode(true);
|
|
243
|
+
// Module Infinite Scroll
|
|
244
|
+
infinite: {
|
|
245
|
+
detect: function(scope) {
|
|
246
|
+
return scope.querySelector('[bb-infinite]') !== null;
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
init: function(scope) {
|
|
250
|
+
const elements = scope.querySelectorAll('[bb-infinite]');
|
|
251
|
+
if (elements.length === 0) return;
|
|
643
252
|
|
|
644
|
-
|
|
645
|
-
scrollContainer.appendChild(mainBlock);
|
|
646
|
-
scrollContainer.appendChild(repeatBlock1);
|
|
647
|
-
scrollContainer.appendChild(repeatBlock2);
|
|
648
|
-
scrollContainer.appendChild(repeatBlock3);
|
|
649
|
-
mainContainer.appendChild(scrollContainer);
|
|
253
|
+
bbContents.utils.log('Module détecté: infinite');
|
|
650
254
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
255
|
+
elements.forEach(element => {
|
|
256
|
+
if (element.bbProcessed) return;
|
|
257
|
+
element.bbProcessed = true;
|
|
258
|
+
|
|
259
|
+
const threshold = bbContents._getAttr(element, 'bb-infinite-threshold') || '0.1';
|
|
260
|
+
const url = bbContents._getAttr(element, 'bb-infinite-url');
|
|
261
|
+
|
|
262
|
+
if (!url) {
|
|
263
|
+
bbContents.utils.log('Erreur: bb-infinite-url manquant');
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Implémentation basique d'infinite scroll
|
|
268
|
+
let loading = false;
|
|
269
|
+
let page = 1;
|
|
270
|
+
|
|
271
|
+
const loadMore = () => {
|
|
272
|
+
if (loading) return;
|
|
273
|
+
loading = true;
|
|
661
274
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
maxHeight = logoHeight;
|
|
275
|
+
fetch(`${url}?page=${page}`)
|
|
276
|
+
.then(response => response.json())
|
|
277
|
+
.then(data => {
|
|
278
|
+
if (data.items && data.items.length > 0) {
|
|
279
|
+
// Ajouter le contenu
|
|
280
|
+
element.innerHTML += data.html || '';
|
|
281
|
+
page++;
|
|
282
|
+
loading = false;
|
|
671
283
|
}
|
|
284
|
+
})
|
|
285
|
+
.catch(error => {
|
|
286
|
+
bbContents.utils.log('Erreur infinite scroll:', error);
|
|
287
|
+
loading = false;
|
|
672
288
|
});
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// Observer d'intersection pour déclencher le chargement
|
|
292
|
+
const observer = new IntersectionObserver((entries) => {
|
|
293
|
+
entries.forEach(entry => {
|
|
294
|
+
if (entry.isIntersecting) {
|
|
295
|
+
loadMore();
|
|
677
296
|
}
|
|
297
|
+
});
|
|
298
|
+
}, { threshold: parseFloat(threshold) });
|
|
299
|
+
|
|
300
|
+
observer.observe(element);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
bbContents.utils.log('Module Infinite Scroll initialisé:', elements.length, 'éléments');
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
// Module Marquee
|
|
308
|
+
marquee: {
|
|
309
|
+
detect: function(scope) {
|
|
310
|
+
return scope.querySelector('[bb-marquee]') !== null;
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
init: function(scope) {
|
|
314
|
+
const elements = scope.querySelectorAll('[bb-marquee]');
|
|
315
|
+
if (elements.length === 0) return;
|
|
316
|
+
|
|
317
|
+
bbContents.utils.log('Module détecté: marquee');
|
|
318
|
+
|
|
319
|
+
elements.forEach(element => {
|
|
320
|
+
if (element.bbProcessed) return;
|
|
321
|
+
element.bbProcessed = true;
|
|
322
|
+
|
|
323
|
+
const speed = bbContents._getAttr(element, 'bb-marquee-speed') || '30';
|
|
324
|
+
const direction = bbContents._getAttr(element, 'bb-marquee-direction') || 'left';
|
|
325
|
+
const pause = bbContents._getAttr(element, 'bb-marquee-pause') || 'true';
|
|
326
|
+
const gap = bbContents._getAttr(element, 'bb-marquee-gap') || '50';
|
|
327
|
+
const orientation = bbContents._getAttr(element, 'bb-marquee-orientation') || 'horizontal';
|
|
328
|
+
const height = bbContents._getAttr(element, 'bb-marquee-height');
|
|
329
|
+
const minHeight = bbContents._getAttr(element, 'bb-marquee-min-height');
|
|
330
|
+
|
|
331
|
+
// Créer le conteneur principal
|
|
332
|
+
const mainContainer = document.createElement('div');
|
|
333
|
+
mainContainer.style.cssText = `
|
|
334
|
+
overflow: hidden;
|
|
335
|
+
position: relative;
|
|
336
|
+
width: 100%;
|
|
337
|
+
${height ? `height: ${height};` : ''}
|
|
338
|
+
${minHeight ? `min-height: ${minHeight};` : ''}
|
|
339
|
+
`;
|
|
340
|
+
|
|
341
|
+
// Créer le conteneur de défilement
|
|
342
|
+
const scrollContainer = document.createElement('div');
|
|
343
|
+
scrollContainer.style.cssText = `
|
|
344
|
+
display: flex;
|
|
345
|
+
align-items: center;
|
|
346
|
+
${orientation === 'vertical' ? 'flex-direction: column;' : ''}
|
|
347
|
+
gap: ${gap}px;
|
|
348
|
+
animation: marquee ${speed}s linear infinite;
|
|
349
|
+
${direction === 'right' ? 'animation-direction: reverse;' : ''}
|
|
350
|
+
${orientation === 'vertical' ? 'animation-name: marquee-vertical;' : ''}
|
|
351
|
+
`;
|
|
352
|
+
|
|
353
|
+
// Déplacer le contenu original
|
|
354
|
+
const originalContent = element.innerHTML;
|
|
355
|
+
scrollContainer.innerHTML = originalContent + originalContent; // Dupliquer pour l'effet infini
|
|
356
|
+
|
|
357
|
+
// Ajouter les styles CSS
|
|
358
|
+
const style = document.createElement('style');
|
|
359
|
+
style.textContent = `
|
|
360
|
+
@keyframes marquee {
|
|
361
|
+
0% { transform: translateX(0); }
|
|
362
|
+
100% { transform: translateX(-50%); }
|
|
678
363
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
// Si pas de contenu, réessayer
|
|
684
|
-
if ((isVertical && contentHeight === 0) || (!isVertical && contentWidth === 0)) {
|
|
685
|
-
bbContents.utils.log('Contenu non prêt, nouvelle tentative dans 200ms');
|
|
686
|
-
setTimeout(initAnimation, 200);
|
|
687
|
-
return;
|
|
364
|
+
@keyframes marquee-vertical {
|
|
365
|
+
0% { transform: translateY(0); }
|
|
366
|
+
100% { transform: translateY(-50%); }
|
|
688
367
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
368
|
+
`;
|
|
369
|
+
document.head.appendChild(style);
|
|
370
|
+
|
|
371
|
+
// Pause au survol
|
|
372
|
+
if (pause === 'true') {
|
|
373
|
+
mainContainer.addEventListener('mouseenter', () => {
|
|
374
|
+
scrollContainer.style.animationPlayState = 'paused';
|
|
375
|
+
});
|
|
376
|
+
mainContainer.addEventListener('mouseleave', () => {
|
|
377
|
+
scrollContainer.style.animationPlayState = 'running';
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Auto-height pour les logos horizontaux
|
|
382
|
+
if (orientation === 'horizontal' && !height && !minHeight) {
|
|
383
|
+
const logos = element.querySelectorAll('.bb-marquee_logo, img, svg');
|
|
384
|
+
let maxHeight = 0;
|
|
385
|
+
logos.forEach(logo => {
|
|
386
|
+
const rect = logo.getBoundingClientRect();
|
|
387
|
+
if (rect.height > maxHeight) maxHeight = rect.height;
|
|
388
|
+
});
|
|
389
|
+
if (maxHeight > 0) {
|
|
390
|
+
mainContainer.style.height = maxHeight + 'px';
|
|
695
391
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
if (!isPaused) {
|
|
710
|
-
if (direction === 'bottom') {
|
|
711
|
-
currentPosition += step;
|
|
712
|
-
if (currentPosition >= 0) {
|
|
713
|
-
currentPosition = -contentSize - parseInt(gap);
|
|
714
|
-
}
|
|
715
|
-
} else {
|
|
716
|
-
currentPosition -= step;
|
|
717
|
-
if (currentPosition <= -contentSize - parseInt(gap)) {
|
|
718
|
-
currentPosition = 0;
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
scrollContainer.style.transform = `translate3d(0px, ${currentPosition}px, 0px)`;
|
|
723
|
-
}
|
|
724
|
-
requestAnimationFrame(animate);
|
|
725
|
-
};
|
|
726
|
-
|
|
727
|
-
// Démarrer l'animation
|
|
728
|
-
animate();
|
|
729
|
-
|
|
730
|
-
bbContents.utils.log('Marquee vertical créé avec animation JS - direction:', direction, 'taille:', contentSize + 'px', 'total:', totalSize + 'px', 'hauteur-wrapper:', height + 'px');
|
|
731
|
-
|
|
732
|
-
// Pause au survol
|
|
733
|
-
if (pauseOnHover === 'true') {
|
|
734
|
-
element.addEventListener('mouseenter', function() {
|
|
735
|
-
isPaused = true;
|
|
736
|
-
});
|
|
737
|
-
element.addEventListener('mouseleave', function() {
|
|
738
|
-
isPaused = false;
|
|
739
|
-
});
|
|
740
|
-
}
|
|
741
|
-
} else {
|
|
742
|
-
// Animation CSS pour l'horizontal (modifiée)
|
|
743
|
-
const contentSize = contentWidth;
|
|
744
|
-
const totalSize = contentSize * 4 + parseInt(gap) * 3; // 4 copies au lieu de 3
|
|
745
|
-
scrollContainer.style.width = totalSize + 'px';
|
|
746
|
-
|
|
747
|
-
// Créer l'animation CSS optimisée
|
|
748
|
-
const animationName = 'bb-scroll-' + Math.random().toString(36).substr(2, 9);
|
|
749
|
-
const animationDuration = (totalSize / (parseFloat(speed) * 1.5)).toFixed(2) + 's'; // Vitesse différente
|
|
750
|
-
|
|
751
|
-
// Animation avec translate3d pour hardware acceleration
|
|
752
|
-
let keyframes;
|
|
753
|
-
if (direction === 'right') {
|
|
754
|
-
keyframes = `@keyframes ${animationName} {
|
|
755
|
-
0% { transform: translate3d(-${contentSize + parseInt(gap)}px, 0px, 0px); }
|
|
756
|
-
100% { transform: translate3d(0px, 0px, 0px); }
|
|
757
|
-
}`;
|
|
758
|
-
} else {
|
|
759
|
-
// Direction 'left' par défaut
|
|
760
|
-
keyframes = `@keyframes ${animationName} {
|
|
761
|
-
0% { transform: translate3d(0px, 0px, 0px); }
|
|
762
|
-
100% { transform: translate3d(-${contentSize + parseInt(gap)}px, 0px, 0px); }
|
|
763
|
-
}`;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
// Ajouter les styles
|
|
767
|
-
const style = document.createElement('style');
|
|
768
|
-
style.textContent = keyframes;
|
|
769
|
-
document.head.appendChild(style);
|
|
770
|
-
|
|
771
|
-
// Appliquer l'animation
|
|
772
|
-
scrollContainer.style.animation = `${animationName} ${animationDuration} linear infinite`;
|
|
773
|
-
|
|
774
|
-
bbContents.utils.log('Marquee horizontal créé:', animationName, 'durée:', animationDuration + 's', 'direction:', direction, 'taille:', contentSize + 'px', 'total:', totalSize + 'px');
|
|
775
|
-
|
|
776
|
-
// Pause au survol
|
|
777
|
-
if (pauseOnHover === 'true') {
|
|
778
|
-
element.addEventListener('mouseenter', function() {
|
|
779
|
-
scrollContainer.style.animationPlayState = 'paused';
|
|
780
|
-
});
|
|
781
|
-
element.addEventListener('mouseleave', function() {
|
|
782
|
-
scrollContainer.style.animationPlayState = 'running';
|
|
783
|
-
});
|
|
784
|
-
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Assembler
|
|
395
|
+
mainContainer.appendChild(scrollContainer);
|
|
396
|
+
element.innerHTML = '';
|
|
397
|
+
element.appendChild(mainContainer);
|
|
398
|
+
|
|
399
|
+
// Délai pour l'animation
|
|
400
|
+
const isVertical = orientation === 'vertical';
|
|
401
|
+
setTimeout(() => {
|
|
402
|
+
scrollContainer.style.animation = `marquee${isVertical ? '-vertical' : ''} ${speed}s linear infinite`;
|
|
403
|
+
if (direction === 'right') {
|
|
404
|
+
scrollContainer.style.animationDirection = 'reverse';
|
|
785
405
|
}
|
|
786
|
-
});
|
|
787
|
-
};
|
|
406
|
+
}, isVertical ? 300 : 100);
|
|
407
|
+
});
|
|
788
408
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
bbContents.utils.log('Module Marquee initialisé:', elements.length, 'éléments');
|
|
794
|
-
}
|
|
795
|
-
},
|
|
409
|
+
bbContents.utils.log('Module Marquee initialisé:', elements.length, 'éléments');
|
|
410
|
+
}
|
|
411
|
+
},
|
|
796
412
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
bbContents.utils.log('Module détecté: youtube');
|
|
413
|
+
// Module YouTube Feed
|
|
414
|
+
youtube: {
|
|
415
|
+
detect: function(scope) {
|
|
416
|
+
return scope.querySelector('[bb-youtube-channel]') !== null;
|
|
417
|
+
},
|
|
804
418
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
419
|
+
init: function(scope) {
|
|
420
|
+
const elements = scope.querySelectorAll('[bb-youtube-channel]');
|
|
421
|
+
if (elements.length === 0) return;
|
|
808
422
|
|
|
809
|
-
|
|
810
|
-
const videoCount = bbContents._getAttr(element, 'bb-youtube-video-count') || '10';
|
|
811
|
-
const endpoint = bbContents.config.youtubeEndpoint;
|
|
423
|
+
bbContents.utils.log('Module détecté: youtube');
|
|
812
424
|
|
|
813
|
-
|
|
814
|
-
|
|
425
|
+
elements.forEach(element => {
|
|
426
|
+
if (element.bbProcessed) return;
|
|
427
|
+
element.bbProcessed = true;
|
|
428
|
+
|
|
429
|
+
const channelId = bbContents._getAttr(element, 'bb-youtube-channel');
|
|
430
|
+
const videoCount = bbContents._getAttr(element, 'bb-youtube-video-count') || '10';
|
|
431
|
+
const allowShorts = bbContents._getAttr(element, 'bb-youtube-allow-shorts') === 'true';
|
|
432
|
+
const endpoint = bbContents.config.youtubeEndpoint;
|
|
433
|
+
|
|
434
|
+
if (!channelId) {
|
|
435
|
+
bbContents.utils.log('Erreur: bb-youtube-channel manquant');
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (!endpoint) {
|
|
440
|
+
bbContents.utils.log('Erreur: youtubeEndpoint non configuré. Utilisez bbContents.config.youtubeEndpoint = "votre-worker-url"');
|
|
441
|
+
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>';
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Chercher le conteneur pour les vidéos
|
|
446
|
+
const container = element.querySelector('[bb-youtube-container]');
|
|
447
|
+
if (!container) {
|
|
448
|
+
bbContents.utils.log('Erreur: élément [bb-youtube-container] manquant');
|
|
449
|
+
element.innerHTML = '<div style="padding: 20px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; color: #dc2626;"><strong>Structure manquante</strong><br>Ajoutez un élément avec l\'attribut bb-youtube-container</div>';
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Chercher le template pour une vidéo
|
|
454
|
+
const template = container.querySelector('[bb-youtube-item]');
|
|
455
|
+
if (!template) {
|
|
456
|
+
bbContents.utils.log('Erreur: élément [bb-youtube-item] manquant');
|
|
457
|
+
container.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>';
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Cacher le template original
|
|
462
|
+
template.style.display = 'none';
|
|
463
|
+
|
|
464
|
+
// Afficher un loader
|
|
465
|
+
container.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Chargement des vidéos YouTube...</div>';
|
|
466
|
+
|
|
467
|
+
// Appeler l'API via le Worker
|
|
468
|
+
fetch(`${endpoint}?channelId=${channelId}&maxResults=${videoCount}`)
|
|
469
|
+
.then(response => {
|
|
470
|
+
if (!response.ok) {
|
|
471
|
+
throw new Error(`HTTP ${response.status}`);
|
|
472
|
+
}
|
|
473
|
+
return response.json();
|
|
474
|
+
})
|
|
475
|
+
.then(data => {
|
|
476
|
+
if (data.error) {
|
|
477
|
+
throw new Error(data.error.message || 'Erreur API YouTube');
|
|
478
|
+
}
|
|
479
|
+
this.generateYouTubeFeed(container, template, data, allowShorts);
|
|
480
|
+
})
|
|
481
|
+
.catch(error => {
|
|
482
|
+
bbContents.utils.log('Erreur dans le module youtube:', error);
|
|
483
|
+
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>`;
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
},
|
|
487
|
+
|
|
488
|
+
generateYouTubeFeed: function(container, template, data, allowShorts) {
|
|
489
|
+
if (!data.items || data.items.length === 0) {
|
|
490
|
+
container.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Aucune vidéo trouvée</div>';
|
|
815
491
|
return;
|
|
816
492
|
}
|
|
817
493
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
494
|
+
// Filtrer les shorts si nécessaire
|
|
495
|
+
let videos = data.items;
|
|
496
|
+
if (!allowShorts) {
|
|
497
|
+
videos = videos.filter(item => {
|
|
498
|
+
// Filtrer les shorts (vidéos de moins de 60 secondes ou avec #shorts dans le titre)
|
|
499
|
+
const title = item.snippet.title.toLowerCase();
|
|
500
|
+
const description = item.snippet.description.toLowerCase();
|
|
501
|
+
return !title.includes('#shorts') && !description.includes('#shorts');
|
|
502
|
+
});
|
|
503
|
+
bbContents.utils.log(`Filtrage des shorts: ${data.items.length} → ${videos.length} vidéos`);
|
|
822
504
|
}
|
|
823
505
|
|
|
824
|
-
//
|
|
825
|
-
|
|
506
|
+
// Vider le conteneur
|
|
507
|
+
container.innerHTML = '';
|
|
826
508
|
|
|
827
|
-
//
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
bbContents.utils.log('Erreur dans le module youtube:', error);
|
|
843
|
-
element.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>`;
|
|
844
|
-
});
|
|
845
|
-
});
|
|
846
|
-
},
|
|
847
|
-
|
|
848
|
-
generateYouTubeFeed: function(container, data) {
|
|
849
|
-
if (!data.items || data.items.length === 0) {
|
|
850
|
-
container.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Aucune vidéo trouvée</div>';
|
|
851
|
-
return;
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
// Créer la grille de vidéos
|
|
855
|
-
let html = '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px;">';
|
|
856
|
-
|
|
857
|
-
data.items.forEach(item => {
|
|
858
|
-
const videoId = item.id.videoId;
|
|
859
|
-
const snippet = item.snippet;
|
|
509
|
+
// Cloner le template pour chaque vidéo
|
|
510
|
+
videos.forEach(item => {
|
|
511
|
+
const videoId = item.id.videoId;
|
|
512
|
+
const snippet = item.snippet;
|
|
513
|
+
|
|
514
|
+
// Cloner le template
|
|
515
|
+
const clone = template.cloneNode(true);
|
|
516
|
+
clone.style.display = ''; // Rendre visible
|
|
517
|
+
|
|
518
|
+
// Remplir les données
|
|
519
|
+
this.fillVideoData(clone, videoId, snippet);
|
|
520
|
+
|
|
521
|
+
// Ajouter au conteneur
|
|
522
|
+
container.appendChild(clone);
|
|
523
|
+
});
|
|
860
524
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
<div class="bb-youtube-thumbnail" style="position: relative;">
|
|
864
|
-
<img src="${snippet.thumbnails.medium.url}" alt="${snippet.title}" style="width: 100%; height: auto; display: block;">
|
|
865
|
-
<div class="bb-youtube-duration" style="position: absolute; bottom: 8px; right: 8px; background: rgba(0,0,0,0.8); color: white; padding: 2px 6px; border-radius: 4px; font-size: 12px;">YouTube</div>
|
|
866
|
-
</div>
|
|
867
|
-
<div class="bb-youtube-content" style="padding: 16px;">
|
|
868
|
-
<div class="bb-youtube-title" style="font-weight: 600; margin-bottom: 8px; line-height: 1.4;">${snippet.title}</div>
|
|
869
|
-
<div class="bb-youtube-channel" style="color: #6b7280; font-size: 14px; margin-bottom: 8px;">${snippet.channelTitle}</div>
|
|
870
|
-
<div class="bb-youtube-date" style="color: #9ca3af; font-size: 12px;">${this.formatDate(snippet.publishedAt)}</div>
|
|
871
|
-
</div>
|
|
872
|
-
</div>
|
|
873
|
-
`;
|
|
874
|
-
});
|
|
875
|
-
|
|
876
|
-
html += '</div>';
|
|
877
|
-
container.innerHTML = html;
|
|
525
|
+
bbContents.utils.log(`YouTube Feed généré: ${videos.length} vidéos`);
|
|
526
|
+
},
|
|
878
527
|
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
if (data.items[index]) {
|
|
887
|
-
element.textContent = data.items[index].snippet.title;
|
|
528
|
+
fillVideoData: function(element, videoId, snippet) {
|
|
529
|
+
// Remplir le lien
|
|
530
|
+
const link = element.querySelector('[bb-youtube-link]');
|
|
531
|
+
if (link) {
|
|
532
|
+
link.href = `https://www.youtube.com/watch?v=${videoId}`;
|
|
533
|
+
link.target = '_blank';
|
|
534
|
+
link.rel = 'noopener noreferrer';
|
|
888
535
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
536
|
+
|
|
537
|
+
// Remplir la thumbnail
|
|
538
|
+
const thumbnail = element.querySelector('[bb-youtube-thumbnail]');
|
|
539
|
+
if (thumbnail) {
|
|
540
|
+
thumbnail.src = snippet.thumbnails.medium.url;
|
|
541
|
+
thumbnail.alt = snippet.title;
|
|
895
542
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
element.textContent = 'Vues non disponibles';
|
|
543
|
+
|
|
544
|
+
// Remplir le titre
|
|
545
|
+
const title = element.querySelector('[bb-youtube-title]');
|
|
546
|
+
if (title) {
|
|
547
|
+
title.textContent = snippet.title;
|
|
902
548
|
}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
element.textContent = this.formatDate(data.items[index].snippet.publishedAt);
|
|
549
|
+
|
|
550
|
+
// Remplir la description
|
|
551
|
+
const description = element.querySelector('[bb-youtube-description]');
|
|
552
|
+
if (description) {
|
|
553
|
+
description.textContent = snippet.description;
|
|
909
554
|
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
555
|
+
|
|
556
|
+
// Remplir la date
|
|
557
|
+
const date = element.querySelector('[bb-youtube-date]');
|
|
558
|
+
if (date) {
|
|
559
|
+
date.textContent = this.formatDate(snippet.publishedAt);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Remplir le nom de la chaîne
|
|
563
|
+
const channel = element.querySelector('[bb-youtube-channel]');
|
|
564
|
+
if (channel) {
|
|
565
|
+
channel.textContent = snippet.channelTitle;
|
|
566
|
+
}
|
|
567
|
+
},
|
|
918
568
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
569
|
+
formatDate: function(dateString) {
|
|
570
|
+
const date = new Date(dateString);
|
|
571
|
+
const now = new Date();
|
|
572
|
+
const diffTime = Math.abs(now - date);
|
|
573
|
+
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
574
|
+
|
|
575
|
+
if (diffDays === 1) return 'Il y a 1 jour';
|
|
576
|
+
if (diffDays < 7) return `Il y a ${diffDays} jours`;
|
|
577
|
+
if (diffDays < 30) return `Il y a ${Math.floor(diffDays / 7)} semaines`;
|
|
578
|
+
if (diffDays < 365) return `Il y a ${Math.floor(diffDays / 30)} mois`;
|
|
579
|
+
return `Il y a ${Math.floor(diffDays / 365)} ans`;
|
|
580
|
+
}
|
|
924
581
|
}
|
|
925
|
-
}
|
|
926
|
-
};
|
|
927
|
-
|
|
928
|
-
|
|
582
|
+
};
|
|
929
583
|
|
|
930
584
|
// Exposer globalement
|
|
931
585
|
window.bbContents = bbContents;
|