@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.
Files changed (2) hide show
  1. package/bb-contents.js +425 -771
  2. 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.3-beta
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.3-beta',
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 dattributs selon le prefix
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
- // Ré-initialiser une sous-arborescence DOM (pour contenus ajoutés dynamiquement)
103
- reinit: function(root) {
104
- const rootNode = root && root.nodeType ? root : document;
105
- // Ne pas traiter les sous-arbres marqués en disable
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
- // 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];
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-], [data-bb-]') ||
139
- node.matches && node.matches('[bb-], [data-bb-]')
117
+ node.querySelector('[bb-]') ||
118
+ node.querySelector('[data-bb-]') ||
119
+ node.matches && (node.matches('[bb-]') || node.matches('[data-bb-]'))
140
120
  )) {
141
- hasRelevantChanges = true;
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
- 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
- }
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
- // 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);
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
- elements.forEach(function(element) {
222
- // Vérifier si déjà traité
223
- if (element.bbProcessed) return;
224
- element.bbProcessed = true;
154
+ init: function(scope) {
155
+ const elements = scope.querySelectorAll('[bb-seo]');
156
+ if (elements.length === 0) return;
225
157
 
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');
158
+ bbContents.utils.log('Module détecté: seo');
230
159
 
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
- };
236
-
237
- // Gestionnaire de clic
238
- element.addEventListener('click', function(e) {
239
- e.preventDefault();
240
- bbContents.modules.share.share(network, data, element);
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);
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
- }).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
- }
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
- } 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);
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
- // 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;
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
- // Compter les images dans le contenu ciblé
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
- 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;
464
- });
465
-
466
- bbContents.utils.log('Module ReadingTime initialisé:', elements.length, 'éléments');
467
- }
468
- };
469
-
470
- // ========================================
471
- // MODULE: FAVICON (Favicon Dynamique)
472
- // ========================================
473
- bbContents.modules.favicon = {
474
- originalFavicon: null,
475
-
476
- // Détection
477
- detect: function(scope) {
478
- const s = scope || document;
479
- return s.querySelector(bbContents._attrSelector('favicon')) !== null;
480
- },
481
-
482
- // Initialisation
483
- init: function(root) {
484
- const scope = root || document;
485
- if (scope.closest && scope.closest('[data-bb-disable]')) return;
486
-
487
- // Chercher les éléments avec bb-favicon ou bb-favicon-dark
488
- const elements = scope.querySelectorAll(bbContents._attrSelector('favicon') + ', ' + bbContents._attrSelector('favicon-dark'));
489
- if (elements.length === 0) return;
490
-
491
- // Sauvegarder le favicon original
492
- const existingLink = document.querySelector("link[rel*='icon']");
493
- if (existingLink) {
494
- this.originalFavicon = existingLink.href;
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
- 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);
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
- // Créer le bloc de contenu principal
627
- const mainBlock = document.createElement('div');
628
- mainBlock.innerHTML = originalHTML;
629
- mainBlock.style.cssText = `
630
- display: flex;
631
- ${isVertical ? 'flex-direction: column;' : ''}
632
- align-items: center;
633
- gap: ${gap}px;
634
- ${isVertical ? '' : 'white-space: nowrap;'}
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
- // Assembler la structure
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
- // Vider et remplacer le contenu original
652
- element.innerHTML = '';
653
- element.appendChild(mainContainer);
654
-
655
- // Fonction pour initialiser l'animation
656
- const initAnimation = () => {
657
- // Attendre que le contenu soit dans le DOM
658
- requestAnimationFrame(() => {
659
- const contentWidth = mainBlock.offsetWidth;
660
- const contentHeight = mainBlock.offsetHeight;
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
- // Si auto-height est activé, ajuster la hauteur du conteneur
663
- if (autoHeight && !isVertical) {
664
- const logoElements = mainBlock.querySelectorAll('.bb-marquee_logo, img, svg');
665
- let maxHeight = 0;
666
-
667
- logoElements.forEach(logo => {
668
- const logoHeight = logo.offsetHeight || logo.getBoundingClientRect().height;
669
- if (logoHeight > maxHeight) {
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
- if (maxHeight > 0) {
675
- mainContainer.style.height = maxHeight + 'px';
676
- bbContents.utils.log('Auto-height détecté:', maxHeight + 'px');
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
- // Debug
681
- bbContents.utils.log('Debug - Largeur du contenu:', contentWidth, 'px', 'Hauteur:', contentHeight, 'px', 'Enfants:', mainBlock.children.length, 'Vertical:', isVertical, 'Direction:', direction);
682
-
683
- // Si pas de contenu, réessayer
684
- if ((isVertical && contentHeight === 0) || (!isVertical && contentWidth === 0)) {
685
- bbContents.utils.log('Contenu non prêt, nouvelle tentative dans 200ms');
686
- setTimeout(initAnimation, 200);
687
- return;
364
+ @keyframes marquee-vertical {
365
+ 0% { transform: translateY(0); }
366
+ 100% { transform: translateY(-50%); }
688
367
  }
689
-
690
- // Pour le vertical, s'assurer qu'on a une hauteur minimale
691
- if (isVertical && contentHeight < 50) {
692
- bbContents.utils.log('Hauteur insuffisante pour le marquee vertical (' + contentHeight + 'px), nouvelle tentative dans 200ms');
693
- setTimeout(initAnimation, 200);
694
- return;
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
- if (isVertical) {
698
- // Animation JavaScript pour le vertical
699
- const contentSize = contentHeight;
700
- const totalSize = contentSize * 4 + parseInt(gap) * 3; // 4 copies au lieu de 3
701
- scrollContainer.style.height = totalSize + 'px';
702
-
703
- let currentPosition = direction === 'bottom' ? -contentSize - parseInt(gap) : 0;
704
- const step = (parseFloat(speed) * 2) / 60; // Vitesse différente
705
- let isPaused = false;
706
-
707
- // Fonction d'animation JavaScript
708
- const animate = () => {
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
- // Démarrer l'initialisation
790
- setTimeout(initAnimation, isVertical ? 300 : 100);
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
- // Module YouTube Feed
798
- youtube: {
799
- init: function() {
800
- const elements = document.querySelectorAll('[bb-youtube-channel]');
801
- if (elements.length === 0) return;
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
- elements.forEach(element => {
806
- if (element.bbProcessed) return;
807
- element.bbProcessed = true;
419
+ init: function(scope) {
420
+ const elements = scope.querySelectorAll('[bb-youtube-channel]');
421
+ if (elements.length === 0) return;
808
422
 
809
- const channelId = bbContents._getAttr(element, 'bb-youtube-channel');
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
- if (!channelId) {
814
- bbContents.utils.log('Erreur: bb-youtube-channel manquant');
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
- if (!endpoint) {
819
- bbContents.utils.log('Erreur: youtubeEndpoint non configuré. Utilisez bbContents.config.youtubeEndpoint = "votre-worker-url"');
820
- 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>';
821
- return;
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
- // Afficher un loader
825
- element.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Chargement des vidéos YouTube...</div>';
506
+ // Vider le conteneur
507
+ container.innerHTML = '';
826
508
 
827
- // Appeler l'API via le Worker
828
- fetch(`${endpoint}?channelId=${channelId}&maxResults=${videoCount}`)
829
- .then(response => {
830
- if (!response.ok) {
831
- throw new Error(`HTTP ${response.status}`);
832
- }
833
- return response.json();
834
- })
835
- .then(data => {
836
- if (data.error) {
837
- throw new Error(data.error.message || 'Erreur API YouTube');
838
- }
839
- this.generateYouTubeFeed(element, data);
840
- })
841
- .catch(error => {
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
- html += `
862
- <div class="bb-youtube-video" style="border: 1px solid #e5e7eb; border-radius: 8px; overflow: hidden;">
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
- // Traiter les éléments avec des attributes spécifiques
880
- this.processYouTubeElements(container, data);
881
- },
882
-
883
- processYouTubeElements: function(container, data) {
884
- // Traiter bb-youtube-show-title
885
- container.querySelectorAll('[bb-youtube-show-title]').forEach((element, index) => {
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
- // Traiter bb-youtube-show-description
892
- container.querySelectorAll('[bb-youtube-show-description]').forEach((element, index) => {
893
- if (data.items[index]) {
894
- element.textContent = data.items[index].snippet.description;
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
- // Traiter bb-youtube-show-views (nécessite une requête supplémentaire)
899
- container.querySelectorAll('[bb-youtube-show-views]').forEach((element, index) => {
900
- if (data.items[index]) {
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
- // Traiter bb-youtube-show-date
906
- container.querySelectorAll('[bb-youtube-show-date]').forEach((element, index) => {
907
- if (data.items[index]) {
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
- formatDate: function(dateString) {
914
- const date = new Date(dateString);
915
- const now = new Date();
916
- const diffTime = Math.abs(now - date);
917
- const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
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
- if (diffDays === 1) return 'Il y a 1 jour';
920
- if (diffDays < 7) return `Il y a ${diffDays} jours`;
921
- if (diffDays < 30) return `Il y a ${Math.floor(diffDays / 7)} semaines`;
922
- if (diffDays < 365) return `Il y a ${Math.floor(diffDays / 30)} mois`;
923
- return `Il y a ${Math.floor(diffDays / 365)} ans`;
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bebranded/bb-contents",
3
- "version": "1.0.5-beta",
3
+ "version": "1.0.7-beta",
4
4
  "description": "Contenus additionnels français pour Webflow",
5
5
  "main": "bb-contents.js",
6
6
  "scripts": {