@bebranded/bb-contents 1.0.4-beta → 1.0.6-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 +393 -775
- 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,451 @@
|
|
|
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
|
-
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');
|
|
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');
|
|
247
167
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
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
196
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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);
|
|
239
|
+
bbContents.utils.log('Module Images initialisé:', elements.length, 'éléments');
|
|
515
240
|
}
|
|
516
241
|
},
|
|
517
|
-
|
|
518
|
-
// Helper: Récupérer ou créer un élément favicon
|
|
519
|
-
getFaviconElement: function() {
|
|
520
|
-
let favicon = document.querySelector('link[rel="icon"]') ||
|
|
521
|
-
document.querySelector('link[rel="shortcut icon"]');
|
|
522
|
-
if (!favicon) {
|
|
523
|
-
favicon = document.createElement('link');
|
|
524
|
-
favicon.rel = 'icon';
|
|
525
|
-
document.head.appendChild(favicon);
|
|
526
|
-
}
|
|
527
|
-
return favicon;
|
|
528
|
-
},
|
|
529
|
-
|
|
530
|
-
// Changer le favicon
|
|
531
|
-
setFavicon: function(url) {
|
|
532
|
-
if (!url) return;
|
|
533
|
-
|
|
534
|
-
// Ajouter un timestamp pour forcer le rafraîchissement du cache
|
|
535
|
-
const cacheBuster = '?v=' + Date.now();
|
|
536
|
-
const urlWithCacheBuster = url + cacheBuster;
|
|
537
|
-
|
|
538
|
-
const favicon = this.getFaviconElement();
|
|
539
|
-
favicon.href = urlWithCacheBuster;
|
|
540
|
-
},
|
|
541
|
-
|
|
542
|
-
// Support dark mode (méthode simplifiée et directe)
|
|
543
|
-
setupDarkMode: function(lightUrl, darkUrl) {
|
|
544
|
-
// Fonction pour mettre à jour le favicon selon le mode sombre
|
|
545
|
-
const updateFavicon = function(e) {
|
|
546
|
-
const darkModeOn = e ? e.matches : window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
547
|
-
const selectedUrl = darkModeOn ? darkUrl : lightUrl;
|
|
548
|
-
bbContents.modules.favicon.setFavicon(selectedUrl);
|
|
549
|
-
};
|
|
550
|
-
|
|
551
|
-
// Initialiser le favicon au chargement de la page
|
|
552
|
-
updateFavicon();
|
|
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
242
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
const
|
|
588
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 endpoint = bbContents.config.youtubeEndpoint;
|
|
432
|
+
|
|
433
|
+
if (!channelId) {
|
|
434
|
+
bbContents.utils.log('Erreur: bb-youtube-channel manquant');
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (!endpoint) {
|
|
439
|
+
bbContents.utils.log('Erreur: youtubeEndpoint non configuré. Utilisez bbContents.config.youtubeEndpoint = "votre-worker-url"');
|
|
440
|
+
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>';
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Afficher un loader
|
|
445
|
+
element.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Chargement des vidéos YouTube...</div>';
|
|
446
|
+
|
|
447
|
+
// Appeler l'API via le Worker
|
|
448
|
+
fetch(`${endpoint}?channelId=${channelId}&maxResults=${videoCount}`)
|
|
449
|
+
.then(response => {
|
|
450
|
+
if (!response.ok) {
|
|
451
|
+
throw new Error(`HTTP ${response.status}`);
|
|
452
|
+
}
|
|
453
|
+
return response.json();
|
|
454
|
+
})
|
|
455
|
+
.then(data => {
|
|
456
|
+
if (data.error) {
|
|
457
|
+
throw new Error(data.error.message || 'Erreur API YouTube');
|
|
458
|
+
}
|
|
459
|
+
this.generateYouTubeFeed(element, data);
|
|
460
|
+
})
|
|
461
|
+
.catch(error => {
|
|
462
|
+
bbContents.utils.log('Erreur dans le module youtube:', error);
|
|
463
|
+
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>`;
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
},
|
|
467
|
+
|
|
468
|
+
generateYouTubeFeed: function(container, data) {
|
|
469
|
+
if (!data.items || data.items.length === 0) {
|
|
470
|
+
container.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Aucune vidéo trouvée</div>';
|
|
815
471
|
return;
|
|
816
472
|
}
|
|
817
473
|
|
|
818
|
-
|
|
819
|
-
|
|
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;
|
|
822
|
-
}
|
|
474
|
+
// Créer la grille de vidéos
|
|
475
|
+
let html = '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px;">';
|
|
823
476
|
|
|
824
|
-
|
|
825
|
-
|
|
477
|
+
data.items.forEach(item => {
|
|
478
|
+
const videoId = item.id.videoId;
|
|
479
|
+
const snippet = item.snippet;
|
|
480
|
+
|
|
481
|
+
html += `
|
|
482
|
+
<div class="bb-youtube-video" style="border: 1px solid #e5e7eb; border-radius: 8px; overflow: hidden;">
|
|
483
|
+
<div class="bb-youtube-thumbnail" style="position: relative;">
|
|
484
|
+
<img src="${snippet.thumbnails.medium.url}" alt="${snippet.title}" style="width: 100%; height: auto; display: block;">
|
|
485
|
+
<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>
|
|
486
|
+
</div>
|
|
487
|
+
<div class="bb-youtube-content" style="padding: 16px;">
|
|
488
|
+
<div class="bb-youtube-title" style="font-weight: 600; margin-bottom: 8px; line-height: 1.4;">${snippet.title}</div>
|
|
489
|
+
<div class="bb-youtube-channel" style="color: #6b7280; font-size: 14px; margin-bottom: 8px;">${snippet.channelTitle}</div>
|
|
490
|
+
<div class="bb-youtube-date" style="color: #9ca3af; font-size: 12px;">${this.formatDate(snippet.publishedAt)}</div>
|
|
491
|
+
</div>
|
|
492
|
+
</div>
|
|
493
|
+
`;
|
|
494
|
+
});
|
|
826
495
|
|
|
827
|
-
|
|
828
|
-
|
|
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;
|
|
496
|
+
html += '</div>';
|
|
497
|
+
container.innerHTML = html;
|
|
860
498
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
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;
|
|
878
|
-
|
|
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;
|
|
888
|
-
}
|
|
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;
|
|
895
|
-
}
|
|
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';
|
|
902
|
-
}
|
|
903
|
-
});
|
|
499
|
+
// Traiter les éléments avec des attributes spécifiques
|
|
500
|
+
this.processYouTubeElements(container, data);
|
|
501
|
+
},
|
|
904
502
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
503
|
+
processYouTubeElements: function(container, data) {
|
|
504
|
+
// Traiter bb-youtube-show-title
|
|
505
|
+
container.querySelectorAll('[bb-youtube-show-title]').forEach((element, index) => {
|
|
506
|
+
if (data.items[index]) {
|
|
507
|
+
element.textContent = data.items[index].snippet.title;
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
// Traiter bb-youtube-show-description
|
|
512
|
+
container.querySelectorAll('[bb-youtube-show-description]').forEach((element, index) => {
|
|
513
|
+
if (data.items[index]) {
|
|
514
|
+
element.textContent = data.items[index].snippet.description;
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
// Traiter bb-youtube-show-views (nécessite une requête supplémentaire)
|
|
519
|
+
container.querySelectorAll('[bb-youtube-show-views]').forEach((element, index) => {
|
|
520
|
+
if (data.items[index]) {
|
|
521
|
+
element.textContent = 'Vues non disponibles';
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
// Traiter bb-youtube-show-date
|
|
526
|
+
container.querySelectorAll('[bb-youtube-show-date]').forEach((element, index) => {
|
|
527
|
+
if (data.items[index]) {
|
|
528
|
+
element.textContent = this.formatDate(data.items[index].snippet.publishedAt);
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
},
|
|
918
532
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
533
|
+
formatDate: function(dateString) {
|
|
534
|
+
const date = new Date(dateString);
|
|
535
|
+
const now = new Date();
|
|
536
|
+
const diffTime = Math.abs(now - date);
|
|
537
|
+
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
538
|
+
|
|
539
|
+
if (diffDays === 1) return 'Il y a 1 jour';
|
|
540
|
+
if (diffDays < 7) return `Il y a ${diffDays} jours`;
|
|
541
|
+
if (diffDays < 30) return `Il y a ${Math.floor(diffDays / 7)} semaines`;
|
|
542
|
+
if (diffDays < 365) return `Il y a ${Math.floor(diffDays / 30)} mois`;
|
|
543
|
+
return `Il y a ${Math.floor(diffDays / 365)} ans`;
|
|
544
|
+
}
|
|
924
545
|
}
|
|
925
|
-
}
|
|
926
|
-
};
|
|
927
|
-
|
|
928
|
-
|
|
546
|
+
};
|
|
929
547
|
|
|
930
548
|
// Exposer globalement
|
|
931
549
|
window.bbContents = bbContents;
|