PageSourceSearch

https://www.top80radio.com/wp-content/themes/top80-radio-modern/assets/ctkoi.js?ver=1.1.0

js top80radio.com collected 2026-10-02 21:27:24 UTC 16,188 bytes, 448 lines download raw bytes

1/* TOP 80 Radio — C'ÉTAIT QUOI ? AJAX-safe page controller */
2(function () {
3    'use strict';
4
5    function initCTKOI() {
6
7const page = document.querySelector('.top80-ctkoi-page');
8
9    if (!page || page.dataset.ready === '1') {
10        return;
11    }
12
13    page.dataset.ready = '1';
14
15    const searchForm = page.querySelector(
16        '#top80-ctkoi-search-form'
17    );
18    const searchStatus = page.querySelector(
19        '#top80-ctkoi-search-status'
20    );
21    const searchResults = page.querySelector(
22        '#top80-ctkoi-search-results'
23    );
24    const fallbackCover = page.dataset.ctkoiFallback || '';
25    const pageEndpoint = page.dataset.ctkoiEndpoint || window.location.href;
26
27    if (searchForm && searchStatus && searchResults) {
28        searchForm.addEventListener('submit', async function (event) {
29            event.preventDefault();
30
31            const date = searchForm.querySelector(
32                '#top80-ctkoi-search-date'
33            ).value;
34            const time = searchForm.querySelector(
35                '#top80-ctkoi-search-time'
36            ).value;
37
38            if (!date || !time) {
39                return;
40            }
41
42            searchStatus.textContent = 'Recherche en cours…';
43            searchResults.replaceChildren();
44
45            const endpoint = new URL(pageEndpoint, window.location.origin);
46
47            endpoint.searchParams.set('top80_ctkoi_history', '1');
48            endpoint.searchParams.set('date', date);
49            endpoint.searchParams.set('time', time);
50            endpoint.searchParams.set('_', Date.now());
51
52            try {
53                const response = await fetch(endpoint.toString(), {
54                    cache: 'no-store',
55                    credentials: 'same-origin'
56                });
57
58                if (!response.ok) {
59                    throw new Error('HTTP ' + response.status);
60                }
61
62                const json = await response.json();
63
64                if (!json.success || !json.data) {
65                    throw new Error(
66                        json.data?.message || 'Réponse incorrecte.'
67                    );
68                }
69
70                if (!json.data.items.length) {
71                    searchStatus.textContent =
72                        'Aucun titre trouvé autour de cette heure.';
73                    return;
74                }
75
76                searchStatus.textContent =
77                    json.data.items.length
78                    + ' titre(s) trouvé(s) autour de '
79                    + json.data.time
80                    + ' le '
81                    + json.data.date
82                    + '.';
83
84                json.data.items.forEach(function (item) {
85                    const card = document.createElement('article');
86                    card.className =
87                        'top80-ctkoi-item '
88                        + 'top80-ctkoi-search-result-item';
89
90                    const panelId =
91                        'top80-ctkoi-search-details-'
92                        + String(item.numero)
93                        + '-'
94                        + Math.random().toString(36).slice(2);
95
96                    const summary = document.createElement('button');
97                    summary.type = 'button';
98                    summary.className = 'top80-ctkoi-summary';
99                    summary.setAttribute('aria-expanded', 'false');
100                    summary.setAttribute('aria-controls', panelId);
101
102                    const timeBlock = document.createElement('span');
103                    timeBlock.className = 'top80-ctkoi-time';
104
105                    const clock = document.createElement('i');
106                    clock.className = 'fa-regular fa-clock';
107                    clock.setAttribute('aria-hidden', 'true');
108
109                    const timeText = document.createTextNode(
110                        String(item.diffusion).slice(0, 5)
111                    );
112
113                    timeBlock.append(clock, timeText);
114
115                    const image = document.createElement('img');
116                    image.src = item.cover || fallbackCover;
117                    image.alt = '';
118                    image.loading = 'lazy';
119                    image.addEventListener('error', function () {
120                        image.src = fallbackCover;
121                    }, { once: true });
122
123                    const cover = document.createElement('span');
124                    cover.className = 'top80-ctkoi-cover';
125                    cover.append(image);
126
127                    const info = document.createElement('span');
128                    info.className = 'top80-ctkoi-track';
129                    const artist = document.createElement('strong');
130                    artist.textContent = item.artist;
131                    const title = document.createElement('small');
132                    title.textContent = item.title;
133                    info.append(artist, title);
134
135                    const votes = document.createElement('span');
136                    votes.className = 'top80-ctkoi-votes';
137                    votes.innerHTML =
138                        '<i class="fa-solid fa-heart" '
139                        + 'aria-hidden="true"></i>'
140                        + '<b></b><small>voix</small>';
141                    votes.querySelector('b').textContent =
142                        String(item.votes || 0);
143
144                    const toggle = document.createElement('span');
145                    toggle.className = 'top80-ctkoi-toggle';
146                    toggle.setAttribute('aria-hidden', 'true');
147                    toggle.textContent = '+';
148
149                    summary.append(
150                        timeBlock,
151                        cover,
152                        info,
153                        votes,
154                        toggle
155                    );
156
157                    const details = document.createElement('div');
158                    details.id = panelId;
159                    details.className = 'top80-ctkoi-details';
160                    details.hidden = true;
161
162                    const detailsHeading =
163                        document.createElement('div');
164                    detailsHeading.className =
165                        'top80-ctkoi-details-heading';
166                    detailsHeading.innerHTML =
167                        '<span aria-hidden="true">'
168                        + '<i class="fa-solid fa-music"></i></span>'
169                        + '<div><small>INFORMATIONS SUR LE TITRE</small>'
170                        + '<h3></h3></div>';
171                    detailsHeading.querySelector('h3').textContent =
172                        item.artist + ' — ' + item.title;
173
174                    const biographies =
175                        document.createElement('div');
176                    biographies.className =
177                        'top80-ctkoi-biographies';
178                    biographies.dataset.biographies = '';
179                    biographies.dataset.artists = JSON.stringify(
180                        item.biographyArtists || [item.artist]
181                    );
182                    biographies.innerHTML =
183                        '<p class="top80-ctkoi-bio-loading">'
184                        + 'La biographie sera chargée ici.</p>';
185
186                    const actions = document.createElement('div');
187                    actions.className = 'top80-ctkoi-actions';
188
189                    function createVoteButton(type, label, iconClass) {
190                        const button = document.createElement('button');
191                        button.type = 'button';
192                        button.className =
193                            'top80-ctkoi-vote is-' + type;
194                        button.dataset.vote = type;
195                        button.dataset.numero = String(item.numero);
196                        button.dataset.artiste = item.artist;
197                        button.dataset.titre = item.title;
198                        button.innerHTML =
199                            '<i class="' + iconClass
200                            + '" aria-hidden="true"></i>';
201                        button.append(
202                            document.createTextNode(' ' + label)
203                        );
204                        return button;
205                    }
206
207                    const plusButton = createVoteButton(
208                        'plus',
209                        'J\'AIME',
210                        'fa-solid fa-heart'
211                    );
212                    const minusButton = createVoteButton(
213                        'minus',
214                        'J\'AIME PAS',
215                        'fa-solid fa-heart-crack'
216                    );
217
218                    const buyLink = document.createElement('a');
219                    buyLink.className = 'top80-ctkoi-buy';
220                    buyLink.href =
221                        'https://www.amazon.fr/s?k='
222                        + encodeURIComponent(
223                            item.artist + ' ' + item.title
224                        ).replace(/%20/g, '+')
225                        + '&camp=1642&creative=6746';
226                    buyLink.target = '_blank';
227                    buyLink.rel = 'noopener';
228                    buyLink.innerHTML =
229                        '<i class="fa-solid fa-cart-shopping" '
230                        + 'aria-hidden="true"></i> ACHETER';
231
232                    actions.append(
233                        plusButton,
234                        minusButton,
235                        buyLink
236                    );
237
238                    const notice = document.createElement('p');
239                    notice.className = 'top80-ctkoi-notice';
240                    notice.setAttribute('aria-live', 'polite');
241
242                    details.append(
243                        detailsHeading,
244                        biographies,
245                        actions,
246                        notice
247                    );
248
249                    card.append(summary, details);
250                    searchResults.append(card);
251                });
252            } catch (error) {
253                searchStatus.textContent =
254                    'Impossible d\'effectuer la recherche.';
255            }
256        });
257    }
258
259    async function loadBiographies(panel) {
260        const zone = panel.querySelector('[data-biographies]');
261
262        if (!zone || zone.dataset.loaded === '1') {
263            return;
264        }
265
266        zone.dataset.loaded = '1';
267
268        let artists = [];
269
270        try {
271            artists = JSON.parse(zone.dataset.artists || '[]');
272        } catch (error) {
273            artists = [];
274        }
275
276        if (!artists.length) {
277            zone.replaceChildren();
278            return;
279        }
280
281        const loading = document.createElement('p');
282        loading.className = 'top80-ctkoi-bio-loading';
283        loading.textContent = 'Chargement de la biographie…';
284        zone.replaceChildren(loading);
285
286        const requests = artists.map(async function (artist) {
287            const endpoint = new URL(pageEndpoint, window.location.origin);
288
289            endpoint.searchParams.set('top80_ctkoi_bio', '1');
290            endpoint.searchParams.set('artist', artist);
291            endpoint.searchParams.set('_', Date.now());
292
293            const response = await fetch(endpoint.toString(), {
294                cache: 'no-store',
295                credentials: 'same-origin'
296            });
297
298            if (!response.ok) {
299                throw new Error('HTTP ' + response.status);
300            }
301
302            return response.json();
303        });
304
305        try {
306            const responses = await Promise.all(requests);
307            zone.replaceChildren();
308
309            responses.forEach(function (response, index) {
310                const data = response.success
311                    ? response.data
312                    : null;
313
314                const card = document.createElement('article');
315                card.className = 'top80-ctkoi-bio';
316
317                const title = document.createElement('h4');
318                title.textContent = data?.artist || artists[index];
319                card.append(title);
320
321                const excerpt = document.createElement('p');
322
323                if (data?.found) {
324                    excerpt.textContent = data.excerpt;
325                    card.append(excerpt);
326
327                    const link = document.createElement('a');
328                    link.href = data.url;
329                    link.textContent = 'LIRE LA BIOGRAPHIE ›';
330                    card.append(link);
331                } else {
332                    excerpt.textContent =
333                        'Biographie prochainement disponible.';
334                    card.append(excerpt);
335                }
336
337                zone.append(card);
338            });
339        } catch (error) {
340            loading.textContent =
341                'La biographie est momentanément indisponible.';
342        }
343    }
344
345    page.addEventListener('click', async function (event) {
346        const summary = event.target.closest('.top80-ctkoi-summary');
347
348        if (summary) {
349            const panel = document.getElementById(
350                summary.getAttribute('aria-controls')
351            );
352
353            if (!panel) {
354                return;
355            }
356
357            const isOpen = summary.getAttribute('aria-expanded') === 'true';
358            summary.setAttribute('aria-expanded', String(!isOpen));
359
360            if (isOpen) {
361                panel.classList.remove('is-open');
362
363                window.setTimeout(function () {
364                    if (!panel.classList.contains('is-open')) {
365                        panel.hidden = true;
366                    }
367                }, 360);
368            } else {
369                panel.hidden = false;
370
371                window.requestAnimationFrame(function () {
372                    panel.classList.add('is-open');
373                });
374
375                loadBiographies(panel);
376            }
377
378            return;
379        }
380
381        const voteButton = event.target.closest('.top80-ctkoi-vote');
382
383        if (!voteButton || voteButton.disabled) {
384            return;
385        }
386
387        const details = voteButton.closest('.top80-ctkoi-details');
388        const notice = details?.querySelector('.top80-ctkoi-notice');
389
390        voteButton.disabled = true;
391
392        const params = new URLSearchParams({
393            type: voteButton.dataset.vote,
394            numero: voteButton.dataset.numero,
395            artiste: voteButton.dataset.artiste,
396            titre: voteButton.dataset.titre
397        });
398
399        try {
400            const response = await fetch(
401                '/api/vote-proxy.php?' + params.toString(),
402                {
403                    cache: 'no-store',
404                    credentials: 'same-origin'
405                }
406            );
407
408            const result = (await response.text()).trim();
409
410            if (result === 'vote_ok') {
411                notice.textContent = 'Merci, votre vote est enregistré !';
412                notice.className =
413                    'top80-ctkoi-notice is-success';
414
415                details
416                    .closest('.top80-ctkoi-item')
417                    ?.querySelectorAll('.top80-ctkoi-vote')
418                    .forEach(function (button) {
419                        button.disabled = true;
420                    });
421            } else if (result === 'already_voted') {
422                notice.textContent =
423                    'Vous avez déjà voté aujourd\'hui pour ce titre.';
424                notice.className =
425                    'top80-ctkoi-notice is-error';
426                voteButton.disabled = false;
427            } else {
428                throw new Error('Réponse incorrecte');
429            }
430        } catch (error) {
431            notice.textContent =
432                'Impossible d\'enregistrer votre vote.';
433            notice.className = 'top80-ctkoi-notice is-error';
434            voteButton.disabled = false;
435        }
436    });
437    }
438
439    document.addEventListener('DOMContentLoaded', initCTKOI);
440    document.addEventListener('top80:page-loaded', function (event) {
441        if (event && event.detail && event.detail.url) {
442            var main = document.querySelector('.top80-ctkoi-page');
443            if (main) {
444                initCTKOI();
445            }
446        }
447    });
448})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.