1// Menu 2const hamburger = document.querySelector(".hamburger"); 3const nav = document.querySelector(".nav"); 4const backdrop = document.querySelector(".backdrop"); 5hamburger.addEventListener("click", function () { 6 hamburger.classList.toggle("active"); 7 nav.classList.toggle("active"); 8 backdrop.classList.toggle("active"); 9}); 10 11document.querySelectorAll("nav a").forEach((link) => { 12 link.addEventListener("click", function () { 13 hamburger.classList.remove("active"); 14 nav.classList.remove("active"); 15 backdrop.classList.remove("active"); 16 }); 17}); 18 19// News carousel 20const newsContainer = document.querySelector(".news-container"); 21const newsViewport = document.querySelector(".news-viewport"); 22const newsPrevious = document.querySelector(".news-prev"); 23const newsNext = document.querySelector(".news-next"); 24 25function escapeHtml(value) { 26 const element = document.createElement("div"); 27 element.textContent = value || ""; 28 return element.innerHTML; 29} 30 31function formatDate(value) { 32 const date = new Date(value); 33 if (Number.isNaN(date.getTime())) return ""; 34 35 const pad = (num) => String(num).padStart(2, "0"); 36 37 return `${date.getFullYear()}.${pad(date.getMonth() + 1)}.${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`; 38} 39 40function articleMarkup(article, showMoreOverlay = false) { 41 const thumbnailUrl = article.thumbnailUrl; 42 43 return ` 44 <article class="news-card"> 45 <a href="${escapeHtml(article.url)}" target="_blank" rel="noopener noreferrer"> 46 <div class="news-card-image"> 47 <img src="${escapeHtml(thumbnailUrl)}" alt="${escapeHtml(article.title)}" loading="lazy" /> 48 </div> 49 <div class="news-card-content"> 50 <time datetime="${escapeHtml(article.datetime)}">${formatDate(article.datetime)}</time> 51 <h3>${escapeHtml(article.title)}</h3> 52 <p>${escapeHtml(article.description)}</p> 53 </div> 54 </a> 55 ${ 56 showMoreOverlay 57 ? `<a class="news-more-overlay" href="https://news.cts.com.tw/topic/8567fecd-5090-4054-9e5c-337d709ef9cb" target="_blank" rel="noopener noreferrer"><span>çæ´å¤</span><span>åå¤å±äºéæ°è</span></a>` 58 : "" 59 } 60 </article>`; 61} 62 63function adMarkup() { 64 return ` 65 <article class="news-card"> 66 <div id="div-gpt-ad-news-ad-0" class="news-ad" data-ad data-ad-format="square" aria-label="廣å"> 67 <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-7608737321945861" 68 crossorigin="anonymous"></script> 69<ins class="adsbygoogle" 70 style="display:block" 71 data-ad-format="fluid" 72 data-ad-layout-key="-79+ez-1k-3m+cp" 73 data-ad-client="ca-pub-7608737321945861" 74 data-ad-slot="9763806309"></ins> 75<script> 76 (adsbygoogle = window.adsbygoogle || []).push({}); 77</script> 78 </div> 79 <div class="ad-note">廣å/ADVERTISING</div> 80 </article>`; 81} 82 83async function loadNews() { 84 try { 85 const response = await fetch( 86 `json/news-articles.json?refresh=${Date.now()}`, 87 { 88 cache: "no-store", 89 }, 90 ); 91 if (!response.ok) 92 throw new Error(`News request failed: ${response.status}`); 93 94 const payload = await response.json(); 95 const articles = Array.isArray(payload) ? payload : payload.data; 96 // console.log(articles); 97 if (!Array.isArray(articles)) throw new Error("News data is not an array"); 98 const latestArticles = articles 99 .filter((article) => article && article.title && article.url) 100 .sort( 101 (first, second) => new Date(second.datetime) - new Date(first.datetime), 102 ) 103 .slice(0, 7); 104 105 newsContainer.innerHTML = latestArticles 106 .slice(0, 6) 107 .map((article) => articleMarkup(article)) 108 .join(""); 109 newsContainer.insertAdjacentHTML("beforeend", adMarkup()); 110 newsContainer.insertAdjacentHTML( 111 "beforeend", 112 latestArticles[6] ? articleMarkup(latestArticles[6], true) : "", 113 ); 114 115 // initializeAds(newsContainer); 116 117 const embla = EmblaCarousel(newsViewport, { 118 align: "start", 119 containScroll: "trimSnaps", 120 loop: latestArticles.length > 8, 121 }); 122 newsPrevious.addEventListener("click", () => embla.scrollPrev()); 123 newsNext.addEventListener("click", () => embla.scrollNext()); 124 const updateControls = () => { 125 newsPrevious.disabled = !embla.canScrollPrev(); 126 newsNext.disabled = !embla.canScrollNext(); 127 }; 128 embla.on("select", updateControls); 129 embla.on("reInit", updateControls); 130 updateControls(); 131 } catch (error) { 132 newsContainer.innerHTML = 133 '<p class="news-state">æ°èæ«æç¡æ³è¼å ¥ï¼è«ç¨å¾å試ã</p>'; 134 console.error(error); 135 } 136} 137 138loadNews(); 139 140// initializeAds();
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.