PageSourceSearch

https://event.cts.com.tw/2026asiangames/js/schedule.a9b24c1.js

js cts.com.tw collected 2026-09-24 08:41:01 UTC 7,415 bytes, 198 lines download raw bytes

1const mobileSchedule = document.querySelector(".schedule-mobile");
2
3// Footer padding
4const updateMobileScheduleHeight = () => {
5  document.documentElement.style.setProperty(
6    "--schedule-mobile-height",
7    `${mobileSchedule.offsetHeight}px`,
8  );
9};
10const scheduleResizeObserver = new ResizeObserver(() => {
11  updateMobileScheduleHeight();
12});
13scheduleResizeObserver.observe(mobileSchedule);
14updateMobileScheduleHeight();
15
16// Upcoming program (all broadcast times are in Taiwan time).
17const scheduleBlocks = document.querySelectorAll(".schedule");
18const scheduleWeekdays = ["日", "一", "二", "三", "四", "五", "六"];
19const taiwanDateFormatter = new Intl.DateTimeFormat("en-CA", {
20  timeZone: "Asia/Taipei",
21  year: "numeric",
22  month: "2-digit",
23  day: "2-digit",
24});
25let schedulePrograms = [];
26let displayedScheduleKey;
27let scheduleRequest;
28
29const expandedSchedule = document.querySelector(".schedule-expanded");
30const scheduleToggle = expandedSchedule?.querySelector(".schedule-toggle");
31const expandedScheduleText = expandedSchedule?.querySelector(".schedule");
32
33const updateExpandedScheduleAlignment = () => {
34  if (!expandedSchedule || !expandedScheduleText) return;
35  expandedSchedule.style.setProperty(
36    "--schedule-text-offset",
37    `${expandedScheduleText.offsetLeft}px`,
38  );
39};
40
41if (expandedSchedule) {
42  const expandedScheduleResizeObserver = new ResizeObserver(
43    updateExpandedScheduleAlignment,
44  );
45  expandedScheduleResizeObserver.observe(expandedSchedule);
46  updateExpandedScheduleAlignment();
47}
48
49scheduleToggle?.addEventListener("click", () => {
50  const isExpanded = expandedSchedule.classList.toggle("is-open");
51  scheduleToggle.setAttribute("aria-expanded", String(isExpanded));
52  scheduleToggle.setAttribute(
53    "aria-label",
54    isExpanded ? "收合後續比賽場次" : "展開後續比賽場次",
55  );
56});
57
58function updateSchedule() {
59  const now = Date.now();
60  const programIndex = schedulePrograms.findIndex((item) => item.endsAt > now);
61  const program = programIndex >= 0 ? schedulePrograms[programIndex] : null;
62
63  const getTaiwanDate = (timestamp) => {
64    const parts = Object.fromEntries(
65      taiwanDateFormatter
66        .formatToParts(new Date(timestamp))
67        .map(({ type, value }) => [type, value]),
68    );
69    return `${parts.year}-${parts.month}-${parts.day}`;
70  };
71  const today = getTaiwanDate(now);
72  const tomorrow = getTaiwanDate(now + 86400000);
73  const todayPrograms = schedulePrograms.filter(
74    (item) => item.date === today && item.endsAt > now && item !== program,
75  );
76  const upcomingPrograms = todayPrograms.length
77    ? todayPrograms
78    : schedulePrograms.filter(
79        (item) => item.date === tomorrow && item !== program,
80      );
81  const scheduleKey = `${programIndex}:${upcomingPrograms.map((item) => item.startsAt).join(",")}`;
82  if (scheduleKey === displayedScheduleKey) return;
83  displayedScheduleKey = scheduleKey;
84
85  let requiredText = "暫無即將播出的節目";
86  if (program) {
87    const [year, month, day] = program.date.split("-").map(Number);
88    const weekday =
89      scheduleWeekdays[new Date(Date.UTC(year, month - 1, day)).getUTCDay()];
90    requiredText = `${month}/${day}(${weekday}) ${program.startTime} ${program.title}`;
91  }
92  const optionalTexts =
93    program && Array.isArray(program.texts) ? program.texts : [];
94  scheduleBlocks.forEach((block) => {
95    block.querySelector(".required").textContent = requiredText;
96    const optional = block.querySelector(".optional");
97    optional.textContent = optionalTexts.join(" ");
98    optional.classList.toggle("hidden", optionalTexts.length === 0);
99  });
100
101  const upcomingElement =
102    expandedSchedule?.querySelector(".schedule-upcoming");
103  if (upcomingElement) {
104    upcomingElement.replaceChildren();
105    if (upcomingPrograms.length) {
106      upcomingPrograms.forEach((upcomingProgram) => {
107        const [year, month, day] = upcomingProgram.date.split("-").map(Number);
108        const weekday =
109          scheduleWeekdays[
110            new Date(Date.UTC(year, month - 1, day)).getUTCDay()
111          ];
112        const details = Array.isArray(upcomingProgram.texts)
113          ? upcomingProgram.texts.join(" ")
114          : "";
115        const row = document.createElement("div");
116        const required = document.createElement("span");
117        required.className = "required";
118        required.textContent = `${month}/${day}(${weekday}) ${upcomingProgram.startTime} ${upcomingProgram.title}`;
119        row.append(required);
120        const optional = document.createElement("span");
121        optional.className = details ? "optional" : "optional hidden";
122        optional.textContent = details;
123        row.append(optional);
124        upcomingElement.append(row);
125      });
126    } else {
127      const row = document.createElement("div");
128      row.className = "schedule-empty";
129      row.textContent = "請期待精彩的比賽轉播";
130      upcomingElement.append(row);
131    }
132    upcomingElement.classList.remove("hidden");
133    scheduleToggle?.classList.remove("hidden");
134  }
135  updateMobileScheduleHeight();
136}
137
138async function loadSchedule() {
139  scheduleRequest?.abort();
140  const controller = new AbortController();
141  scheduleRequest = controller;
142  try {
143    const dataBase = (window.SITE_DATA_BASE || "").replace(/\/$/, "");
144    const response = await fetch(dataBase ? `${dataBase}/schedule.json` : `json/schedule.json?refresh=${Date.now()}`, {
145      cache: "no-store",
146      signal: AbortSignal.any([controller.signal, AbortSignal.timeout(15000)]),
147    });
148    if (!response.ok)
149      throw new Error(`Schedule request failed: ${response.status}`);
150    const payload = await response.json();
151    if (scheduleRequest !== controller) return;
152    if (!Array.isArray(payload.programs))
153      throw new Error("Schedule programs is not an array");
154    schedulePrograms = payload.programs
155      .filter(
156        (program) =>
157          program &&
158          program.date &&
159          program.startTime &&
160          program.endTime &&
161          program.title,
162      )
163      .map((program) => ({
164        ...program,
165        startsAt: Date.parse(`${program.date}T${program.startTime}:00+08:00`),
166        endsAt: Date.parse(`${program.date}T${program.endTime}:00+08:00`),
167      }))
168      .filter(
169        (program) =>
170          Number.isFinite(program.startsAt) &&
171          Number.isFinite(program.endsAt) &&
172          program.endsAt > program.startsAt,
173      )
174      .sort((first, second) => first.startsAt - second.startsAt);
175    // 即使日期與時段不變,後台修改標題或對戰文字也必須重繪。
176    displayedScheduleKey = undefined;
177    document.querySelectorAll(".schedule-container, #schedule .note").forEach((element) => { element.style.display = ""; });
178    updateSchedule();
179  } catch (error) {
180    if (controller.signal.aborted || scheduleRequest !== controller) return;
181    schedulePrograms = [];
182    displayedScheduleKey = undefined;
183    document
184      .querySelectorAll(".schedule-container, #schedule .note")
185      .forEach((element) => {
186        element.style.display = "none";
187      });
188    updateMobileScheduleHeight();
189    console.error(error);
190  }
191}
192
193loadSchedule();
194setInterval(updateSchedule, 1000);
195document.addEventListener("visibilitychange", () => {
196  if (!document.hidden) { updateSchedule(); if (window.SITE_DATA_BASE) loadSchedule(); }
197});
198if (window.SITE_DATA_BASE) setInterval(() => { if (!document.hidden) loadSchedule(); }, 30000);

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.