1/* eslint-disable import/extensions */ 2/* eslint-disable no-restricted-syntax */ 3/* eslint-disable no-console */ 4import ffetch from '../../scripts/ffetch.js'; 5import { 6 extractElementsFromString, 7 formatingDate, 8 estimateTime, 9 updateHostname, 10 formatToISO8601, // ADDED: import the ISO converter 11 shouldShowAnzeige, 12 createAnzeigeFlag, 13} from '../../scripts/shared.js'; 14import { resolveArticleGatingFlags, shouldBeGated } from '../../scripts/authentication/helpers.js'; 15import { createCategorySlug } from '../../scripts/url-utils.js'; 16import { fetchPlaceholders, getMetadata } from '../../scripts/aem.js'; 17 18/** 19 * Creates a title element for the article with a corresponding link 20 * @param {object} article The article object 21 * @param {Object<string, {gated: string|null, forcegate: boolean}>} [metadataCache] 22 * @returns {Promise<HTMLElement>} The title element with a link 23 */ 24async function createArticleTitle(article, metadataCache = {}) { 25 // article-query-index omits gated/forcegate; resolve from the article page so 26 // gated=off articles do not inherit the listing page's gated=on metadata. 27 const { articleGated, forceGate } = await resolveArticleGatingFlags(article, metadataCache); 28 const isLock = shouldBeGated( 29 article.publisheddate, 30 article.type, 31 forceGate, 32 articleGated, 33 article.path 34 ); 35 36 const articleTitle = document.createElement('div'); 37 articleTitle.className = `article-title ${isLock ? 'lock' : ''}`; 38 39 // create an anchor tag for the title 40 const articleLink = document.createElement('a'); 41 articleLink.href = article.path; 42 articleLink.setAttribute('aria-label', `Read more about: ${article.title}`); 43 articleLink.textContent = article.title; 44 articleTitle.appendChild(articleLink); 45 46 return articleTitle; // ensure we return the node 47} 48 49/** 50 * Creates a published date element for the article 51 * @param {string} date The UNIX timestamp of the article's published date 52 * @param {number} wordcount The word count used for reading time estimation 53 * @returns {HTMLElement} The published date element 54 */ 55function createPublishedDate(date, wordcount) { 56 // MINIMAL CHANGE: Convert date to ISO for display formatting. 57 const publishedDate = document.createElement('div'); 58 publishedDate.className = 'article-date'; 59 publishedDate.innerHTML = ` 60 <img class="article-clock" alt="pub date" src="/icons/clock.svg"/> 61 ${formatingDate(formatToISO8601(date))}${estimateTime(wordcount)} 62 `; 63 return publishedDate; 64} 65 66/** 67 * Creates a category element for the article 68 * @param {object} article The article object 69 * @returns {HTMLElement} The category element with a link 70 */ 71function createArticleCategory(article) { 72 const articleCategory = document.createElement('div'); 73 articleCategory.className = 'article-category'; 74 const firstCategory = extractElementsFromString(article.category)[0]; 75 const articleCategoryLink = document.createElement('a'); 76 articleCategoryLink.href = `/category/${createCategorySlug(firstCategory)}`; 77 articleCategoryLink.textContent = firstCategory; 78 articleCategoryLink.setAttribute('aria-label', `Read more stories on ${firstCategory}`); 79 articleCategory.appendChild(articleCategoryLink); 80 return articleCategory; 81} 82 83/** 84 * Generates an article card with the article's title, author, image, 85 * published date, and description 86 * @param {object} article The article object 87 * @param {HTMLElement} [container] Optional container to append to (defaults to .article-cards-wrapper > div) 88 */ 89function getArticlesContainer() { 90 return document.querySelector('.article-cards-wrapper > div'); 91} 92 93async function generateCard(article, container, metadataCache = {}) { 94 const target = container || getArticlesContainer(); 95 if (!target) return null; 96 const articleCard = document.createElement('div'); 97 articleCard.className = 'article-card'; 98 if (shouldShowAnzeige(article)) { 99 articleCard.classList.add('anzeige-card'); 100 } 101 const contextWrapper = document.createElement('div'); 102 contextWrapper.className = 'article-context-wrapper'; 103 if (shouldShowAnzeige(article)) { 104 contextWrapper.appendChild(createAnzeigeFlag('inline')); 105 } 106 contextWrapper.appendChild(createArticleCategory(article)); 107 contextWrapper.appendChild(await createArticleTitle(article, metadataCache)); 108 contextWrapper.appendChild(createPublishedDate(article.publisheddate, article.wordcount)); 109 articleCard.appendChild(contextWrapper); 110 return { articleCard, target }; 111} 112 113function appendGeneratedCard(generated) { 114 if (!generated?.articleCard || !generated?.target) return; 115 generated.target.appendChild(generated.articleCard); 116 updateHostname(generated.target); 117} 118 119/** Homepage only: stored so we can adjust visible card count after ad/side-reg load */ 120const homepageArticleCardsState = { articles: [], container: null, metadataCache: {} }; 121 122/** 123 * Sets how many article cards are visible on the homepage (10 when logged in, 16 for 250px ad, 18 for 650px ad). 124 * Call from delayed.js after right-ad is fitted and side-reg presence is known. 125 * @param {number} count Desired number of cards (10, 16, or 18) 126 */ 127export async function setHomepageArticleCardCount(count) { 128 const { articles, container, metadataCache } = homepageArticleCardsState; 129 if (!articles.length || !container) return; 130 const cards = container.querySelectorAll('.article-card'); 131 const current = cards.length; 132 const max = Math.min(count, articles.length); 133 if (max > current) { 134 const generated = await Promise.all( 135 articles.slice(current, max).map((article) => generateCard(article, container, metadataCache)) 136 ); 137 generated.forEach(appendGeneratedCard); 138 } else if (max < current) { 139 for (let i = current - 1; i >= max; i -= 1) { 140 cards[i].remove(); 141 } 142 } 143} 144 145async function appendArticlesToPage(articles, maxCount = 10) { 146 const container = getArticlesContainer(); 147 if (!container) return; 148 const isHomepage = getMetadata('template')?.toLowerCase() === 'homepage'; 149 const metadataCache = homepageArticleCardsState.metadataCache || {}; 150 if (isHomepage) { 151 homepageArticleCardsState.articles = articles; 152 homepageArticleCardsState.container = container; 153 homepageArticleCardsState.metadataCache = metadataCache; 154 } 155 const generated = await Promise.all( 156 articles.slice(0, maxCount).map((item) => generateCard(item, container, metadataCache)) 157 ); 158 generated.forEach(appendGeneratedCard); 159} 160 161/** 162 * In allArticlesByPath and allArticlesGenerator, update the publisheddate using formatToISO8601. 163 */ 164async function allArticlesByPath(generator, articlePaths) { 165 // First, collect all articles from the generator into a map for efficient lookup 166 const articlesMap = new Map(); 167 for await (const item of generator) { 168 if (item.publisheddate) { 169 item.publisheddate = formatToISO8601(item.publisheddate); 170 } 171 // Store articles by their pathname for quick lookup 172 const itemPathname = new URL(item.path, window.location.origin).pathname; 173 articlesMap.set(itemPathname, item); 174 } 175 176 // Now build the result array in the same order as articlePaths 177 const result = []; 178 for (const path of articlePaths) { 179 const article = articlesMap.get(path); 180 if (article) { 181 result.push(article); 182 } 183 } 184 return result; 185} 186 187async function allArticlesGenerator(generator, topStoriesArticles) { 188 const result = []; 189 const { ignore } = await fetchPlaceholders(); 190 const ignoreArray = ignore ? ignore.split(', ') : []; 191 192 for await (const item of generator) { 193 if (item.publisheddate) { 194 item.publisheddate = formatToISO8601(item.publisheddate); 195 } 196 let isTopStorie = false;
197 for (const art of topStoriesArticles) { 198 if (art.title === item.title) { 199 isTopStorie = true; 200 break; 201 } 202 } 203 if (item.publisheddate && !isTopStorie && !ignoreArray.includes(item.type.toLowerCase()) && !ignoreArray.includes(item.category.toLowerCase())) { 204 result.push(item); 205 } 206 } 207 return result; 208} 209 210async function fetchArticles(url, hasArticlePaths, articlePaths, topStoriesArticles) { 211 try { 212 let articlesGenerator; 213 let articles; 214 const template = getMetadata('template'); 215 const isNotHomepage = template && template.toLowerCase() !== 'homepage'; 216 217 if (hasArticlePaths) { 218 articlesGenerator = ffetch(url); 219 articles = await allArticlesByPath(articlesGenerator, articlePaths); 220 221 // If not homepage and we have fewer than 10 articles, fill with latest news 222 if (isNotHomepage && articles.length < 10) { 223 // Fetch all articles (excluding top stories and ignored types/categories) 224 const allArticlesGen = ffetch(url).limit(120); 225 const allArticles = await allArticlesGenerator(allArticlesGen, topStoriesArticles); 226 227 // Get pathnames of already displayed articles for exclusion 228 const existingPathnames = new Set( 229 articles.map(article => new URL(article.path, window.location.origin).pathname) 230 ); 231 232 // Filter out already displayed articles 233 const additionalArticles = allArticles.filter(article => { 234 const articlePathname = new URL(article.path, window.location.origin).pathname; 235 return !existingPathnames.has(articlePathname); 236 }); 237 238 // Sort by publish date (most recent first) 239 additionalArticles.sort((a, b) => new Date(b.publisheddate) - new Date(a.publisheddate)); 240 241 // Add articles up to 10 total (preserve order: manually added first, then latest news) 242 const remainingSlots = 10 - articles.length; 243 articles = articles.concat(additionalArticles.slice(0, remainingSlots)); 244 } 245 // For manually added articles, preserve their order (don't sort) 246 } else { 247 articlesGenerator = ffetch(url).limit(120); 248 articles = await allArticlesGenerator(articlesGenerator, topStoriesArticles); 249 // Sort articles by publish date (most recent first). 250 articles.sort((a, b) => new Date(b.publisheddate) - new Date(a.publisheddate)); 251 } 252 await appendArticlesToPage(articles); 253 } catch (error) { 254 console.error('Error fetching articles', error); 255 } 256} 257 258function getArticlesPaths(li) { 259 const arrayCollection = li; 260 const paths = []; 261 for (const item of arrayCollection) { 262 const path = new URL(item.querySelector('a').title); 263 paths.push(path.pathname); 264 } 265 return paths; 266} 267 268function getTopStoriesArticles() { 269 const topStoriesFirstArticle = document.querySelector('.content-section .top-stories .first-wrap .text-wrap'); 270 const topStoriesFirstArticleTitle = topStoriesFirstArticle?.querySelector('.top-stories-title a'); 271 const topStoriesFirstArticleAuthor = topStoriesFirstArticle?.querySelector( 272 '.bottom-wrap .top-stories-author a:nth-child(2)' 273 ); 274 275 const topStoriesSecoundArticles = document.querySelectorAll( 276 '.content-section .top-stories .second-wrap .top-stories' 277 ); 278 279 const topStoriesArticles = []; 280 const topStoriesFirstObj = {}; 281 282 topStoriesFirstObj.title = topStoriesFirstArticleTitle?.textContent; 283 topStoriesFirstObj.author = topStoriesFirstArticleAuthor?.textContent; 284 285 topStoriesArticles.push(topStoriesFirstObj); 286 287 topStoriesSecoundArticles?.forEach((article) => { 288 const topStoriesSecoundArticleTitle = article.querySelector( 289 '.text-wrap .title-category-wrapper .top-stories-title a' 290 ); 291 const topStoriesSecoundArticleAuthor = article.querySelector('.bottom-wrap .top-stories-author a:nth-child(2)'); 292 const topStoriesObj = {}; 293 294 topStoriesObj.title = topStoriesSecoundArticleTitle?.textContent; 295 topStoriesObj.author = topStoriesSecoundArticleAuthor?.textContent; 296 297 topStoriesArticles.push(topStoriesObj); 298 }); 299 300 return topStoriesArticles; 301} 302 303export default function decorate(block) { 304 const topStoriesArticles = getTopStoriesArticles(); 305 const ul = block.querySelector('div div ul'); 306 let hasArticlePaths = false;
307 let articlePaths = []; 308 309 if (ul) { 310 const li = ul.getElementsByTagName('li'); 311 articlePaths = getArticlesPaths(li); 312 hasArticlePaths = true; 313 } 314 // fetch and display articles 315 fetchArticles('/article-query-index.json', hasArticlePaths, articlePaths, topStoriesArticles); 316 block.querySelector('div').remove(); 317}
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.