PageSourceSearch

https://jns-e.com/scripts/event-list.js

js jns-e.com collected 2026-10-02 17:13:46 UTC 18,281 bytes, 516 lines download raw bytes

1(function() {
2  'use strict';
3
4  // 祝日判定用の簡易実装(主要な祝日のみ)
5  const isHoliday = function(date) {
6    const year = date.getFullYear();
7    const month = date.getMonth() + 1;
8    const day = date.getDate();
9    const dayOfWeek = date.getDay();
10
11    // 固定祝日
12    const fixedHolidays = [
13      [1, 1],   // 元日
14      [2, 11],  // 建国記念の日
15      [4, 29],  // 昭和の日
16      [5, 3],   // 憲法記念日
17      [5, 4],   // みどりの日
18      [5, 5],   // こどもの日
19      [8, 11],  // 山の日
20      [11, 3],  // 文化の日
21      [11, 23], // 勤労感謝の日
22      [12, 23], // 天皇誕生日
23    ];
24
25    // 固定祝日のチェック
26    for (let i = 0; i < fixedHolidays.length; i++) {
27      if (fixedHolidays[i][0] === month && fixedHolidays[i][1] === day) {
28        return true;
29      }
30    }
31
32    // 春分の日・秋分の日は簡易的に計算(完全ではない)
33    // 実際のプロジェクトでは、より正確な計算やAPIを使用することを推奨
34
35    return false;
36  };
37
38  // 日付フォーマット関数
39  const getDayOfWeek = function(dateStr) {
40    const dateOnly = dateStr.replace(/-/g, '');
41    if (dateOnly.length !== 8) {
42      return { day: '', isHoliday: false, className: '' };
43    }
44
45    const year = parseInt(dateOnly.substring(0, 4), 10);
46    const month = parseInt(dateOnly.substring(4, 6), 10) - 1;
47    const day = parseInt(dateOnly.substring(6, 8), 10);
48    const date = new Date(year, month, day);
49    const dayOfWeekIndex = date.getDay();
50    const dayOfWeekChars = ['日', '月', '火', '水', '木', '金', '土'];
51    const dayOfWeek = dayOfWeekChars[dayOfWeekIndex];
52    const holiday = isHoliday(date);
53    const dayDisplay = holiday ? dayOfWeek + '・祝' : dayOfWeek;
54
55    let className = 'p-home-event__card__date__day-of-week';
56    // 祝日の場合は元の曜日に関係なく日曜クラスを適用
57    if (holiday) {
58      className += ' p-home-event__card__date__day-of-week--sunday';
59    } else if (dayOfWeekIndex === 0) {
60      // 日曜日
61      className += ' p-home-event__card__date__day-of-week--sunday';
62    } else if (dayOfWeekIndex === 6) {
63      // 土曜日
64      className += ' p-home-event__card__date__day-of-week--saturday';
65    }
66
67    return { day: dayDisplay, isHoliday: holiday, className: className };
68  };
69
70  const formatDateRange = function(startDate, endDate) {
71    const startDateOnly = startDate.replace(/-/g, '');
72    
73    if (startDateOnly.length !== 8) {
74      return { year: '', dates: [] };
75    }
76
77    const year = startDateOnly.substring(0, 4);
78    const startMonth = startDateOnly.substring(4, 6);
79    const startDay = startDateOnly.substring(6, 8);
80    const startDateFormatted = startMonth + '/' + startDay;
81    
82    const startDateWithHyphen = year + '-' + startMonth + '-' + startDay;
83    const startDayInfo = getDayOfWeek(startDateWithHyphen);
84    
85    const dates = [
86      { date: startDateFormatted, day: startDayInfo.day, className: startDayInfo.className }
87    ];
88    
89    if (endDate) {
90      const endDateOnly = endDate.replace(/-/g, '');
91      
92      if (endDateOnly.length === 8) {
93        const endYear = endDateOnly.substring(0, 4);
94        const endMonth = endDateOnly.substring(4, 6);
95        const endDay = endDateOnly.substring(6, 8);
96        const endDateFormatted = endMonth + '/' + endDay;
97        
98        const endDateWithHyphen = endYear + '-' + endMonth + '-' + endDay;
99        const endDayInfo = getDayOfWeek(endDateWithHyphen);
100        
101        dates.push({ date: endDateFormatted, day: endDayInfo.day, className: endDayInfo.className });
102      }
103    }
104    
105    return { year: year, dates: dates };
106  };
107
108  // create-slug.ts と同じロジック(public 配下のため import 不可)
109  function removeDateHyphen(dateStr) {
110    return (dateStr || '').replace(/-/g, '');
111  }
112  function createSlug(item) {
113    return item.slug || (removeDateHyphen(item.ymd) + '-' + item.topics_id);
114  }
115
116  // text-to-html.ts と同じロジック(public 配下のため import 不可)
117  function textToHtml(text) {
118    return text.replace(/<br\s*\/?>/gi, '\n');
119  }
120
121  // DOM生成関数
122  const createEventCard = function(event) {
123    const li = document.createElement('li');
124    li.className = 'p-home-event__card';
125    
126    const link = document.createElement('a');
127    const slug = createSlug(event);
128    link.href = '/calendar/' + slug + '/';
129    link.className = 'p-home-event__card__link';
130    
131    // 日付セクション(date-format.tsxから生成されたHTMLを使用)
132    const dateDiv = document.createElement('div');
133    dateDiv.className = 'p-home-event__card__date';
134    
135    if (event.dateHtml) {
136      // dateHtmlが存在する場合はそれを使用
137      dateDiv.innerHTML = event.dateHtml;
138      
139      // 祝日判定を行ってクラス名を修正
140      const dayOfWeekElements = dateDiv.querySelectorAll('.p-home-event__card__date__day-of-week');
141      dayOfWeekElements.forEach(function(element) {
142        // テキストに「・祝」が含まれている場合は祝日として日曜クラスを適用
143        const isHolidayText = element.textContent.indexOf('・祝') !== -1;
144        if (isHolidayText) {
145          element.className = 'p-home-event__card__date__day-of-week p-home-event__card__date__day-of-week--sunday';
146        }
147      });
148    } else {
149      // フォールバック: 既存のロジックを使用
150      const { year, dates } = formatDateRange(event.date.start_date, event.date.end_date);
151      
152      const yearP = document.createElement('p');
153      yearP.className = 'p-home-event__card__date__year';
154      yearP.textContent = year;
155      dateDiv.appendChild(yearP);
156      
157      const datesDiv = document.createElement('div');
158      datesDiv.className = 'p-home-event__card__date__dates';
159      
160      dates.forEach(function(dateInfo, index) {
161        const dateItem = document.createElement('div');
162        dateItem.className = 'p-home-event__card__date__date-item';
163        
164        const dateNumber = document.createElement('span');
165        dateNumber.className = 'p-home-event__card__date__date-number';
166        dateNumber.textContent = dateInfo.date;
167        dateItem.appendChild(dateNumber);
168        
169        const dayOfWeek = document.createElement('span');
170        dayOfWeek.className = dateInfo.className;
171        dayOfWeek.textContent = dateInfo.day;
172        dateItem.appendChild(dayOfWeek);
173        
174        datesDiv.appendChild(dateItem);
175        
176        if (index < dates.length - 1) {
177          const hyphen = document.createElement('span');
178          hyphen.className = 'p-home-event__card__date__hyphen';
179          datesDiv.appendChild(hyphen);
180        }
181      });
182      
183      dateDiv.appendChild(datesDiv);
184    }
185    
186    link.appendChild(dateDiv);
187    
188    // 画像セクション(ImageFilterと同じ構造)
189    const figure = document.createElement('figure');
190    figure.className = 'c-image-filter';
191    
192    const cloneImg = document.createElement('img');
193    cloneImg.src = event.image.url;
194    cloneImg.alt = event.image.desc;
195    cloneImg.className = 'c-image-filter__clone';
196    figure.appendChild(cloneImg);
197    
198    const mainImg = document.createElement('img');
199    mainImg.src = event.image.url;
200    mainImg.alt = event.image.desc;
201    mainImg.className = 'c-image-filter__main';
202    figure.appendChild(mainImg);
203    
204    link.appendChild(figure);
205    
206    // コンテンツセクション
207    const contentDiv = document.createElement('div');
208    contentDiv.className = 'p-home-event__card__content';
209    
210    const categoryDiv = document.createElement('div');
211    categoryDiv.className = 'p-home-event__card__category';
212    
213    const iconSpan = document.createElement('span');
214    iconSpan.className = 'p-home-event__card__category__icon p-home-event__card__category__icon--' + event.contents_type_slug;
215    categoryDiv.appendChild(iconSpan);
216    
217    const textSpan = document.createElement('span');
218    textSpan.className = 'p-home-event__card__category__text';
219    textSpan.textContent = event.contents_type;
220    categoryDiv.appendChild(textSpan);
221    
222    contentDiv.appendChild(categoryDiv);
223    
224    const divider = document.createElement('div');
225    divider.className = 'p-home-event__card__divider';
226    contentDiv.appendChild(divider);
227    
228    const titleP = document.createElement('p');
229    titleP.className = 'p-home-event__card__title';
230    titleP.textContent = textToHtml(event.subject);
231    contentDiv.appendChild(titleP);
232    
233    link.appendChild(contentDiv);
234    li.appendChild(link);
235    
236    return li;
237  };
238
239  // 日付セクションの高さを揃える関数
240  const alignDateHeights = function(container) {
241    // コンテナ内のすべての日付セクションを取得
242    const dateSections = container.querySelectorAll('.p-home-event__card__date');
243    if (dateSections.length === 0) {
244      return;
245    }
246
247    // 最大の高さを計算
248    let maxHeight = 0;
249    dateSections.forEach(function(section) {
250      // 一時的に高さをリセットして実際の高さを測定
251      const originalHeight = section.style.height;
252      section.style.height = 'auto';
253      const height = section.offsetHeight;
254      section.style.height = originalHeight;
255      if (height > maxHeight) {
256        maxHeight = height;
257      }
258    });
259
260    // すべての日付セクションに最大高さを適用
261    if (maxHeight > 0) {
262      dateSections.forEach(function(section) {
263        section.style.height = maxHeight + 'px';
264      });
265    }
266  };
267
268  // 日本時刻(JST)で「今日」の 0:00 のタイムスタンプを取得
269  const getTodayJSTMs = function() {
270    const jstDateStr = new Date().toLocaleDateString('en-CA', { timeZone: 'Asia/Tokyo' });
271    return new Date(jstDateStr + 'T00:00:00+09:00').getTime();
272  };
273
274  // 日付文字列を日本時刻の 0:00 のタイムスタンプに変換(YYYY-MM-DD または ISO 形式)
275  const getDateJSTMs = function(dateStr) {
276    var dateOnly = (dateStr || '').substring(0, 10);
277    if (dateOnly.length !== 10) return 0;
278    return new Date(dateOnly + 'T00:00:00+09:00').getTime();
279  };
280
281  // イベントの基準日(end_date があれば end_date、なければ start_date)を日本時刻のタイムスタンプで返す
282  const getEventReferenceDateJSTMs = function(event) {
283    var dateStr = event.date.end_date !== undefined && event.date.end_date !== ''
284      ? event.date.end_date
285      : event.date.start_date;
286    return getDateJSTMs(dateStr);
287  };
288
289  // 未来(今日以降)のイベントのみ抽出(日本時刻でフィルタ。基準日は end_date → start_date)
290  const getFilteredEventList = function() {
291    if (!window.eventListData || !Array.isArray(window.eventListData)) {
292      return [];
293    }
294    const todayJSTMs = getTodayJSTMs();
295    return window.eventListData
296      .filter(function(event) {
297        const refJSTMs = getEventReferenceDateJSTMs(event);
298        return refJSTMs >= todayJSTMs;
299      })
300      .sort(function(a, b) {
301        return getDateJSTMs(a.date.start_date) - getDateJSTMs(b.date.start_date);
302      });
303  };
304
305  // 初期化関数
306  const initEventList = function() {
307    const container = document.getElementById('event-list-container');
308    if (!container || !window.eventListData) {
309      return;
310    }
311
312    var displayList = getFilteredEventList().slice(0, 4);
313
314    // 既存のコンテンツをクリア
315    container.innerHTML = '';
316
317    // イベントカードを生成(今日以降のイベントのみ)
318    displayList.forEach(function(event) {
319      const card = createEventCard(event);
320      container.appendChild(card);
321    });
322
323    // 日付セクションの高さを揃える(PC時のみ)
324    const isSP = window.innerWidth < 960;
325    if (!isSP) {
326      // 少し遅延させてレンダリング後に実行
327      setTimeout(function() {
328        alignDateHeights(container);
329      }, 100);
330    }
331
332    // SP時にSplideスライダーを適用
333    const checkAndInitSplide = function() {
334      const isSP = window.innerWidth < 960;
335      if (isSP && typeof Splide !== 'undefined') {
336        // 既にSplideが初期化されている場合はスキップ
337        if (container.querySelector('.splide')) {
338          return;
339        }
340
341        // 生成されたカードを取得
342        const cards = container.querySelectorAll('.p-home-event__card');
343        if (cards.length === 0) {
344          return;
345        }
346
347        // Splideが読み込まれている場合
348        const splideContainer = document.createElement('div');
349        splideContainer.className = 'splide';
350        splideContainer.id = 'event-splide';
351        
352        const splideTrack = document.createElement('div');
353        splideTrack.className = 'splide__track';
354        
355        const splideList = document.createElement('ul');
356        splideList.className = 'splide__list';
357        
358        // 既存のカードをSplideの構造に移動
359        cards.forEach(function(card) {
360          const slide = document.createElement('li');
361          slide.className = 'splide__slide';
362          slide.appendChild(card.cloneNode(true));
363          splideList.appendChild(slide);
364        });
365        
366        splideTrack.appendChild(splideList);
367        splideContainer.appendChild(splideTrack);
368        
369        // 元のコンテナの内容をSplideコンテナに置き換え
370        container.innerHTML = '';
371        container.appendChild(splideContainer);
372        
373        // プログレスバーと矢印ボタンの要素を取得
374        const progressBarElement = document.getElementById('event-progress-bar');
375        const prevArrow = document.getElementById('event-arrow-prev');
376        const nextArrow = document.getElementById('event-arrow-next');
377        
378        // Splideを初期化
379        const splide = new Splide('#event-splide', {
380          type: 'slide',
381          perPage: 1,
382          perMove: 1,
383          gap: '2.5rem',
384          pagination: '#event-pagination',
385          arrows: false,
386          fixedWidth: '27rem',
387          focus: 'center',
388        });
389        
390        // 矢印ボタンのクリックイベント
391        if (prevArrow) {
392          prevArrow.addEventListener('click', function() {
393            splide.go('<');
394          });
395        }
396        
397        if (nextArrow) {
398          nextArrow.addEventListener('click', function() {
399            splide.go('>');
400          });
401        }
402        
403        // 矢印ボタンの無効化状態を更新する関数
404        const updateArrows = function() {
405          if (prevArrow) {
406            prevArrow.disabled = splide.index === 0;
407          }
408          if (nextArrow) {
409            nextArrow.disabled = splide.index === splide.length - 1;
410          }
411        };
412        
413        splide.on('moved', function(newIndex) {
414          // プログレスバーを更新
415          if (progressBarElement) {
416            const total = splide.length;
417            const progress = ((newIndex + 1) / total) * 100;
418            progressBarElement.style.width = progress + '%';
419          }
420          // 矢印ボタンの状態を更新
421          updateArrows();
422        });
423        
424        splide.mount();
425        
426        // 初期プログレスバーの位置を設定
427        if (progressBarElement) {
428          const total = splide.length;
429          const initialProgress = (1 / total) * 100;
430          progressBarElement.style.width = initialProgress + '%';
431        }
432        
433        // 初期の矢印ボタンの状態を設定
434        updateArrows();
435      } else {
436        // PC時: 日付セクションの高さを揃える
437        setTimeout(function() {
438          alignDateHeights(container);
439        }, 100);
440      }
441    };
442
443    // Splideが読み込まれるまで待機
444    if (typeof Splide === 'undefined') {
445      // Splideがまだ読み込まれていない場合、読み込みを待つ
446      const checkSplide = setInterval(function() {
447        if (typeof Splide !== 'undefined') {
448          clearInterval(checkSplide);
449          checkAndInitSplide();
450        }
451      }, 100);
452      
453      // タイムアウト(5秒後)
454      setTimeout(function() {
455        clearInterval(checkSplide);
456      }, 5000);
457    } else {
458      checkAndInitSplide();
459    }
460  };
461
462  // DOMContentLoadedまたは既に読み込み済みの場合
463  if (document.readyState === 'loading') {
464    document.addEventListener('DOMContentLoaded', initEventList);
465  } else {
466    initEventList();
467  }
468
469  // リサイズ時の処理
470  let resizeTimer;
471  window.addEventListener('resize', function() {
472    clearTimeout(resizeTimer);
473    resizeTimer = setTimeout(function() {
474      const container = document.getElementById('event-list-container');
475      if (container && window.eventListData) {
476        const isSP = window.innerWidth < 960;
477        const existingSplide = container.querySelector('.splide');
478        
479        if (isSP) {
480          // SP時: Splideが既に初期化されている場合は破棄して再初期化
481          if (existingSplide) {
482            const splideInstance = existingSplide.splide;
483            if (splideInstance) {
484              splideInstance.destroy();
485            }
486          }
487          // 再初期化
488          initEventList();
489        } else {
490          // PC時: Splideが初期化されている場合は破棄して元の構造に戻す
491          if (existingSplide) {
492            const splideInstance = existingSplide.splide;
493            if (splideInstance) {
494              splideInstance.destroy();
495            }
496            // 元のカード構造に再生成(PC時は4件に制限)
497            container.innerHTML = '';
498            getFilteredEventList().slice(0, 4).forEach(function(event) {
499              const card = createEventCard(event);
500              container.appendChild(card);
501            });
502            // 日付セクションの高さを揃える
503            setTimeout(function() {
504              alignDateHeights(container);
505            }, 100);
506          } else {
507            // Splideが初期化されていない場合も高さを揃える
508            setTimeout(function() {
509              alignDateHeights(container);
510            }, 100);
511          }
512        }
513      }
514    }, 250);
515  });
516})();

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.