PageSourceSearch

https://cts-strasbourg.eu/export/system/modules/eu.cts.module.actu…/js/carrousel-actualites.js_1297493484.js

js cts-strasbourg.eu collected 2026-09-24 09:06:12 UTC 7,755 bytes, 240 lines download raw bytes

1// Cache des éléments pour éviter les requêtes répétées
2let emblaApiInstance = null;
3let hasTextBeenTruncated = false; // Flag pour éviter les troncatures multiples
4
5// Configuration responsive
6const CAROUSEL_CONFIG = {
7    desktop: {
8        slideWidth: 359,
9        padding: 60,
10        gap: 20
11    },
12    mobile: {
13        slideWidth: 325,
14        padding: 40,
15        gap: 15
16    }
17};
18
19// Fonction optimisée pour tronquer le texte - UNE SEULE FOIS
20function truncateText() {
21    if (hasTextBeenTruncated) return; // Évite les exécutions multiples
22
23    const newsTeasers = document.querySelectorAll('.carrousel-actu-list .teaser-text');
24
25    newsTeasers.forEach(teaser => {
26        // Vérifier si déjà tronqué
27        if (teaser.hasAttribute('data-truncated')) return;
28
29        const text = teaser.textContent.trim();
30
31        if (text.length > 160) {
32            teaser.textContent = text.substring(0, 160) + '...';
33            teaser.setAttribute('data-truncated', 'true');
34        }
35    });
36
37    hasTextBeenTruncated = true;
38}
39
40// Fonction optimisée pour injecter les liens
41function injectLinks(container) {
42    const cards = container.querySelectorAll(".teaser-tile");
43
44    cards.forEach((card) => {
45        const bodyElement = card.querySelector(".body");
46        if (!bodyElement || bodyElement.querySelector(".injected-link")) return;
47
48        const originalLink = card.querySelector("a.teaser-link");
49        const linkUrl = originalLink?.getAttribute("href") || "#";
50        const linkText = window.actualiteParams.labels.readArticle;
51        const linkContainer = document.createElement("div");
52        linkContainer.className = "injected-link";
53        linkContainer.innerHTML = `<a href="${linkUrl}" class="btn piece-btn teaser-btn" title="${linkText}">${linkText}</a>`;
54
55        bodyElement.appendChild(linkContainer);
56    });
57}
58
59// Fonction optimisée pour créer le wrapper Embla
60function createEmblaWrapper(emblaNode) {
61    const wrapper = document.createElement('div');
62    wrapper.className = 'embla-wrapper';
63
64    emblaNode.parentNode.insertBefore(wrapper, emblaNode);
65    wrapper.appendChild(emblaNode);
66
67    return wrapper;
68}
69
70// Fonction optimisée pour styler les slides
71function styleSlides(slides, config) {
72    const slideStyle = `
73    flex: 0 0 ${config.slideWidth}px !important;
74    min-width: ${config.slideWidth}px !important;
75    max-width: ${config.slideWidth}px !important;
76    width: ${config.slideWidth}px !important;
77    margin: 0 !important;
78    padding: 0 !important;
79  `;
80
81    slides.forEach(slide => {
82        slide.style.cssText = slideStyle;
83    });
84}
85
86// Fonction optimisée pour créer les boutons
87function createNavigationButtons() {
88    const nav = document.createElement("div");
89    nav.className = "embla__navigation";
90    nav.innerHTML = `
91    <button class="embla__button embla__button--prev" type="button" aria-label="Previous">
92      <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
93        <path d="M15 18L9 12L15 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
94      </svg>
95    </button>
96    <button class="embla__button embla__button--next" type="button" aria-label="Next">
97      <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
98        <path d="M9 18L15 12L9 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
99      </svg>
100    </button>
101  `;
102    return nav;
103}
104
105// Fonction optimisée pour gérer les événements
106function setupEventListeners(emblaApi, prevButton, nextButton) {
107    const updateButtons = () => {
108        prevButton.disabled = !emblaApi.canScrollPrev();
109        let buttonDisabled = isLastElementFullyVisible();
110        nextButton.disabled = buttonDisabled;
111    };
112
113    prevButton.addEventListener("click", (e) => {
114        e.preventDefault();
115        emblaApi.scrollPrev();
116        //updateButtons();
117    }, { passive: false });
118
119    nextButton.addEventListener("click", (e) => {
120        e.preventDefault();
121        emblaApi.scrollNext();
122        //updateButtons();
123    }, { passive: false });
124
125    emblaApi.on("select", updateButtons);
126    emblaApi.on("settle", updateButtons);
127    emblaApi.on("init", updateButtons);
128//    emblaApi.on("settle", updateButtons);
129//    emblaApi.on("select", updateButtons);
130
131
132    return updateButtons;
133}
134
135function isLastElementFullyVisible() {
136    let actuList=document.querySelector(".carrousel-actu-list.type-dynamic-list").querySelector(".list-box").querySelector(".embla-wrapper").querySelector(".list-entries");
137    let derniereActu = actuList.children[actuList.children.length - 1];
138    const rect = derniereActu.getBoundingClientRect();
139
140    return (
141        //rect.top >= 0 &&
142        rect.left >= 0 &&
143        //rect.bottom <= window.innerHeight &&
144        rect.right <= window.innerWidth
145    );
146}
147
148// Fonction principale optimisée
149function initNewsCarousel() {
150    const container = document.querySelector(".carrousel-actu-list.type-dynamic-list");
151    if (!container) return;
152
153    // Injection des liens
154    injectLinks(container);
155
156    // Tronquer le texte UNE SEULE FOIS après l'injection des liens
157    truncateText();
158
159    const emblaNode = container.querySelector(".list-entries.row");
160    const slides = emblaNode?.querySelectorAll(".list-entry");
161
162    if (!emblaNode || !slides.length) return;
163
164    const isMobile = window.innerWidth <= 768;
165    const config = isMobile ? CAROUSEL_CONFIG.mobile : CAROUSEL_CONFIG.desktop;
166
167    const emblaWrapper = createEmblaWrapper(emblaNode);
168
169    emblaNode.style.cssText = `
170    display: flex !important;
171    flex-wrap: nowrap !important;
172    margin: 0 !important;
173    padding: 0 ${config.padding}px ${config.padding}px ${config.padding}px !important;
174    gap: ${config.gap}px !important;
175  `;
176
177    styleSlides(slides, config);
178
179    const options = {
180        align: "start",
181        containScroll: false,
182        dragFree: false,
183        loop: false,
184        slidesToScroll: 1,
185        duration : 20
186    };
187
188    requestAnimationFrame(() => {
189        try {
190            if (emblaApiInstance) {
191                emblaApiInstance.destroy();
192            }
193
194            emblaApiInstance = EmblaCarousel(emblaWrapper, options);
195
196            const navigationContainer = createNavigationButtons();
197            const listBox = container.querySelector(".list-box.list-dynamic");
198            listBox.parentNode.insertBefore(navigationContainer, listBox.nextSibling);
199
200            const prevButton = navigationContainer.querySelector(".embla__button--prev");
201            const nextButton = navigationContainer.querySelector(".embla__button--next");
202            const updateButtons = setupEventListeners(emblaApiInstance, prevButton, nextButton);
203
204            // Double rAF: first frame applies styles, second confirms reflow
205            requestAnimationFrame(() => {
206                requestAnimationFrame(() => {
207                    emblaApiInstance.reInit();
208                    updateButtons();
209                });
210            });
211
212        } catch (error) {
213            console.error("Erreur lors de l'initialisation du carousel:", error);
214        }
215    });
216}
217
218// Optimisation avec debounce pour le resize
219let resizeTimeout;
220function handleResize() {
221    clearTimeout(resizeTimeout);
222    resizeTimeout = setTimeout(() => {
223        if (emblaApiInstance) {
224            emblaApiInstance.reInit();
225        }
226    }, 10);
227}
228
229// Initialisation optimisée
230document.addEventListener("DOMContentLoaded", () => {
231    setTimeout(initNewsCarousel, 100);
232    window.addEventListener("resize", handleResize, { passive: true });
233});
234
235// Nettoyage au déchargement de la page
236window.addEventListener("beforeunload", () => {
237    if (emblaApiInstance) {
238        emblaApiInstance.destroy();
239    }
240});

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.