PageSourceSearch

https://www.hotelslitteraires.fr/res/js/template/blog/articles/item/ui.js?q=1790256808660

js hotelslitteraires.fr collected 2026-09-24 18:48:44 UTC 7,324 bytes, 245 lines download raw bytes

1var initBlogArticlesItem = async function() {
2
3  const list = document.getElementById("articles-list");
4  const loadMoreBtn = document.getElementById("load-more");
5
6  if (!list) return;
7
8  let page = parseInt(list.dataset.page, 10) || 1;
9  const limit = parseInt(list.dataset.limit, 10) || 21;
10  const lang = list.dataset.lang || "fr";
11  const blogUrl = list.dataset.blogUrl || "";
12  const mediaUrl = "https://api.pulse-cdn.com";
13  const readLabel = list.dataset.readLabel || "";
14
15  let currentSearch = "";
16
17  const arrow = loadMoreBtn?.querySelector(".load-more-arrow");
18  const spinner = loadMoreBtn?.querySelector(".load-more-spinner");
19
20  const setLoading = (loading) => {
21    if (!loadMoreBtn) return;
22    loadMoreBtn.disabled = loading;
23    arrow?.classList.toggle("hidden", loading);
24    spinner?.classList.toggle("hidden", !loading);
25  };
26
27  const fetchArticles = async (p, search, replace = false) => {
28    setLoading(true);
29
30    let url = `/res/php/blog-articles.php?page=${p}&limit=${limit}&lang=${lang}`;
31
32    if (search) {
33      url += `&search=${encodeURIComponent(search)}&fields=contenu`;
34    }
35
36    try {
37      const res = await fetch(url, {
38        headers: { "X-Requested-With": "XMLHttpRequest" }
39      });
40
41      const data = await res.json();
42
43      if (replace) {
44        list.innerHTML = "";
45      }
46
47      if (!Array.isArray(data) || data.length === 0) {
48        if (loadMoreBtn) loadMoreBtn.style.display = "none";
49        setLoading(false);
50        return;
51      }
52
53      data.forEach(article => list.appendChild(buildArticleEl(article)));
54
55      page = p;
56      list.dataset.page = p;
57
58      if (loadMoreBtn) {
59        loadMoreBtn.style.display = data.length < limit ? "none" : "";
60      }
61
62      setLoading(false);
63
64    } catch (e) {
65      setLoading(false);
66    }
67  };
68
69  const initSearch = () => {
70    const searchEl = document.getElementById("search-articles");
71    const clearBtn = document.getElementById("clear-search-articles");
72    const loupe = document.getElementById("loupe");
73
74    if (!searchEl) return;
75
76    let debounceTimer;
77
78    const toggleClearBtn = () => {
79      const hasValue = searchEl.value.trim() !== "";
80
81      clearBtn?.classList.toggle("hidden", !hasValue);
82      loupe?.classList.toggle("hidden", hasValue);
83    };
84
85    const runSearch = () => {
86      currentSearch = searchEl.value.trim();
87
88      if (currentSearch) {
89        sessionStorage.setItem("blogSearch", currentSearch);
90      } else {
91        sessionStorage.removeItem("blogSearch");
92      }
93
94      toggleClearBtn();
95      fetchArticles(1, currentSearch, true);
96    };
97
98    const savedSearch = sessionStorage.getItem("blogSearch");
99
100    if (savedSearch) {
101      searchEl.value = savedSearch;
102      currentSearch = savedSearch;
103      toggleClearBtn();
104      fetchArticles(1, currentSearch, true);
105    }
106
107    searchEl.addEventListener("input", () => {
108      clearTimeout(debounceTimer);
109      toggleClearBtn();
110
111      debounceTimer = setTimeout(() => {
112        runSearch();
113      }, 400);
114    });
115
116    clearBtn?.addEventListener("click", () => {
117      clearTimeout(debounceTimer);
118
119      searchEl.value = "";
120      currentSearch = "";
121      sessionStorage.removeItem("blogSearch");
122
123      toggleClearBtn();
124      fetchArticles(1, "", true);
125
126      searchEl.focus();
127    });
128
129    toggleClearBtn();
130  };
131
132  const dropDown = () => {
133    const dropDown = document.querySelector(".dropdown");
134    const dropDownSvg = document.querySelector(".dropdown svg");
135    const dropdownMenu = document.querySelector(".dropdown-menu");
136
137    if (dropDown && dropdownMenu) {
138      dropDown.addEventListener("click", () => {
139        dropdownMenu.classList.toggle("open");
140        dropDownSvg?.classList.toggle("rotate-180");
141      });
142    }
143  };
144
145  const loadMore = () => {
146    if (!loadMoreBtn) return;
147
148    loadMoreBtn.addEventListener("click", () => {
149      fetchArticles(page + 1, currentSearch);
150    });
151  };
152
153  function buildArticleEl(article) {
154    const slugify = s => (s || "").toString()
155      .normalize("NFD").replace(/[\u0300-\u036f]/g, "")
156      .toLowerCase()
157      .replace(/[^a-z0-9]+/g, "-")
158      .replace(/-+/g, "-")
159      .replace(/^-+|-+$/g, "");
160
161    const slug = article.url || (slugify(article["titre-seo"] || article.nom || "item") + "-" + article.id);
162    const itemUrl = `${blogUrl}/${slug}`;
163
164    const div = document.createElement("div");
165    div.className = "article-item";
166    div.dataset.title = (article.nom || "").toLowerCase();
167    div.dataset.category = (article.categories?.items?.[0]?.nom || "").toLowerCase();
168    div.dataset.content = (article.resume || "").replace(/<[^>]+>/g, "").toLowerCase();
169
170    const cat = article.categories?.items?.[0] || {};
171    const image = article.image || {};
172    const alt = (image.alt || article.nom || "").replace(/"/g, "&quot;");
173
174    const resizeUrl = (w, h, mode) => {
175      const m = mode === "auto" ? "resize" : mode === "thumbnail" ? "crop" : mode;
176
177      if (image.api_id) {
178        return `${mediaUrl}/api/v1/images/${image.api_id}/resize?w=${w}&h=${h}&mode=${m}&q=90&f=webp`;
179      }
180
181      const src = image.src || image.url || "";
182
183      if (!src || !mediaUrl) return src;
184      if (/\.svg(\?.*)?$/i.test(src)) return src;
185
186      const idM = src.match(/\/api\/v1\/images\/([^/]+)\/(original|resize)/);
187      if (idM) {
188        return `${mediaUrl}/api/v1/images/${idM[1]}/resize?w=${w}&h=${h}&mode=${m}&q=90&f=webp`;
189      }
190
191      const upM = src.match(/\/uploads\/(.+)/);
192      if (upM) {
193        return `${mediaUrl}/api/v1/resize/uploads/${upM[1]}-${m}-${w}-${h}-90-webp`;
194      }
195
196      return src;
197    };
198
199    const imgMob = resizeUrl(290, 251, "auto");
200    const imgMob2 = resizeUrl(580, 502, "auto");
201    const imgCrop = resizeUrl(290, 251, "crop");
202    const imgCrop2 = resizeUrl(580, 502, "crop");
203
204    div.innerHTML = `
205      <a href="${itemUrl}">
206        <picture class="block">
207          <source srcset="${imgMob}, ${imgMob2} 2x" media="(max-width: 767px)">
208          <source srcset="${imgCrop}, ${imgCrop2} 2x" media="(min-width: 768px) and (max-width: 1024px)">
209          <img src="${imgCrop}" srcset="${imgCrop}, ${imgCrop2} 2x" alt="${alt}" class="object-cover w-[286px] mx-auto lg:w-auto h-[251px] lg:w-auto border-white border-12 shadow-lg" loading="lazy">
210        </picture>
211      </a>
212
213      <a href="${blogUrl}/${cat.url || ""}" class="block font-title mt-3 text-sm md:text-base">
214        ${cat.nom || ""}
215      </a>
216
217      <time datetime="${article.publishedAt || ""}" itemprop="datePublished" class="mt-1 text-sm">
218        ${article.publishedAt ? new Date(article.publishedAt).toLocaleDateString("fr-FR") : ""}
219      </time>
220
221      <div class="mt-3">
222        <a href="${itemUrl}">
223          <h2 class="text-base! font-semibold">${(article.nom || "").toUpperCase()}</h2>
224        </a>
225      </div>
226
227      <div class="mt-3 [&_h2]:text-xl">
228        ${article.resume || ""}
229      </div>
230
231      <a href="${itemUrl}" class="btn-discover mt-6 text-sm p-3 lg:text-base">
232        ${readLabel}
233        <svg class="ml-2" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
234          <path d="M6.25 3.75V5H14.1187L3.75 15.3687L4.63125 16.25L15 5.88125V13.75H16.25V3.75H6.25Z" fill="#8EBEBB"/>
235        </svg>
236      </a>
237    `;
238
239    return div;
240  }
241
242  initSearch();
243  dropDown();
244  loadMore();
245};

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.