PageSourceSearch

https://www.kirinholdings.com/assets/scripts/top/RenderTopics.js

js kirinholdings.com collected 2026-10-02 02:21:38 UTC 13,278 bytes, 450 lines download raw bytes

1import { common } from '../core/core.js';
2import { setShowMoreOnSp } from './ShowMoreOnSp.js';
3
4const { lang: langType } = common;
5const blankSvg = `<svg class="m-link__icon" viewBox="0 0 100 100" role="img"><title>${langType === 'ja' ? '別ウィンドウで開く' : 'Open in a new window'}</title><use href="#icon-windowRed"></use></svg>`;
6const pdfSvg = `<svg class="m-link__icon" viewBox="0 0 100 100" role="img"><title>${langType === 'ja' ? 'PDFファイルを開く' : 'open the PDF file'}</title><use href="#icon-filePdf"></use></svg>`;
7
8/**
9 * JSON情報を取得
10 *
11 * @param {string} path
12 * @param {RequestInit} [option] fetchの第2引数で指定するオプション。個別に指定を追加したい場合
13 */
14const fetchJson = async (path, option = {}) => {
15  const timeoutController = new AbortController();
16  const timeoutSeconds = 3000;
17  const timeout = window.setTimeout(() => {
18    timeoutController.abort();
19  }, timeoutSeconds);
20
21  try {
22    const res = await fetch(path, {
23      method: 'GET',
24      signal: timeoutController.signal,
25      cache: 'no-cache',
26      ...option,
27    });
28
29    if (!res.ok) {
30      throw new Error(
31        langType === 'ja'
32          ? 'データを取得できませんでした'
33          : 'Unable to load news'
34      );
35    }
36
37    /** @type {TopNewsReleaseInfo} */
38    const resJson = await res.json();
39
40    return resJson;
41  } finally {
42    window.clearTimeout(timeout);
43  }
44};
45
46/**
47 * ニュースリリースエリアの出力を実施
48 *
49 * @export
50 * @class RenderNewsRelease
51 */
52export class RenderNewsRelease {
53  /**
54   * @param {HTMLElement} renderRoot
55   * @memberof RenderNewsRelease
56   */
57  constructor(renderRoot) {
58    /** @type {HTMLElement} */
59    this.renderRoot = renderRoot;
60    /** @type {HTMLTemplateElement | null} */
61    this.renderTemplate = this.renderRoot.querySelector(
62      '.js-render-news__template'
63    );
64    /** @type {HTMLTemplateElement | null} */
65    this.renderTemplateError = this.renderRoot.querySelector(
66      '.js-render-news__error'
67    );
68    /** @type {string} */
69    this.jsonPath =
70      langType === 'ja'
71        ? '/jp/newsroom/release/inc/top_newsrelease.json'
72        : '/en/newsroom/release/inc/top_newsrelease.json';
73    /** @type {TopNewsReleaseInfo} */
74    this.jsonData = {};
75    /** @type {Object<string, string[]>} */
76    this.category = {
77      '01': ['m-badge--ir', 'IR', 'IR'],
78      '02': ['m-badge--csv', 'CSV', 'CSV'],
79      '03': ['m-badge--product', '商品・サービス', 'Products and Services'],
80      '04': ['m-badge--other', 'キャンペーン', 'Marketing Campaigns'],
81      '05': ['m-badge--technology', '研究・技術', 'Research and Technology'],
82      '06': ['m-badge--other', '調査', '調査'],
83      '07': ['m-badge--hr', '人事', 'Human Resources'],
84      '08': ['m-badge--other', 'その他', 'Other'],
85    };
86  }
87
88  async init() {
89    if (!this.renderTemplate || !this.renderTemplateError) {
90      return;
91    }
92
93    try {
94      this.jsonData = await fetchJson(this.jsonPath);
95    } catch (e) {
96      this.renderError(e.message);
97    }
98
99    if (this.jsonData.length === 0) {
100      this.renderError(
101        langType === 'ja' ? '現在ニュースはありません' : 'No news to display'
102      );
103
104      return;
105    }
106
107    this.render(this.jsonData);
108  }
109
110  /**
111   * 年表記文字列を取得
112   *
113   * @static
114   * @param {string} baseFormat 日付情報文字列(yyyy-MM-dd HH:mm:ss形式)
115   * @param {string} lang 言語タイプ
116   * @return {{dateText: string, datetime: string}} 出力フォーマットに対応した情報
117   */
118  static getFormattedDateString(baseFormat, lang = 'ja') {
119    const date = new Date(baseFormat);
120    const YEAR = String(date.getFullYear());
121    const MONTH = String(date.getMonth() + 1);
122    const DAY = String(date.getDate());
123    const MONTH_EN = [
124      'January',
125      'February',
126      'March',
127      'April',
128      'May',
129      'June',
130      'July',
131      'August',
132      'September',
133      'October',
134      'November',
135      'December',
136    ][date.getMonth()];
137    const textFormat =
138      lang === 'ja'
139        ? `${YEAR}年${MONTH}月${DAY}日`
140        : `${MONTH_EN} ${DAY}, ${YEAR}`;
141
142    return {
143      dateText: textFormat,
144      datetime: `${YEAR}-${MONTH.padStart(2, '0')}-${DAY.padStart(2, '0')}`,
145    };
146  }
147
148  /**
149   * ニュースリリース情報の出力
150   *
151   * @param {TopNewsReleaseInfo} renderData
152   */
153  render(renderData) {
154    /** @type {HTMLElement} */
155    const clone = this.renderTemplate.content.cloneNode(true);
156    /** @type {HTMLElement | null} */
157    const list = clone.querySelector('.js-render-news__list');
158    /** @type {HTMLElement | null} */
159    const listItem = clone.querySelector('.js-render-news__listItem');
160
161    if (!list && !listItem) {
162      this.renderError(
163        langType === 'ja'
164          ? 'ニュースを表示できませんでした'
165          : 'Unable to display news.'
166      );
167
168      return;
169    }
170
171    if (renderData.kh_sitetop.length === 0) {
172      this.renderError(
173        langType === 'ja' ? '現在ニュースはありません' : 'No news to display'
174      );
175
176      return;
177    }
178
179    for (const data of renderData.kh_sitetop) {
180      /** @type {HTMLElement} */
181      const listItemClone = listItem.cloneNode(true);
182      /** @type {HTMLAnchorElement | null} */
183      const link = listItemClone.querySelector('.js-render-news__link');
184      /** @type {HTMLElement | null} */
185      const linkText = listItemClone.querySelector('.js-render-news__linkText');
186      /** @type {HTMLTimeElement | null} */
187      const releaseDate = listItemClone.querySelector('.js-render-news__date');
188      /** @type {HTMLElement | null} */
189      const listLabel = listItemClone.querySelector(
190        '.js-render-news__listLabel'
191      );
192      const listLabelItem = listItemClone.querySelector(
193        '.js-render-news__listLabelItem'
194      );
195
196      if (!link || !linkText || !releaseDate || !listLabel || !listLabelItem) {
197        this.renderError();
198
199        break;
200      }
201
202      link.setAttribute('href', data.link);
203      linkText.textContent = data.title;
204
205      if (data.target === '_blank') {
206        link.setAttribute('target', '_blank');
207      }
208
209      if (data.filesize) {
210        linkText.textContent = `${data.title} ${data.filesize}`;
211        linkText.insertAdjacentHTML('afterend', pdfSvg);
212      } else if (data.target === '_blank') {
213        linkText.insertAdjacentHTML('afterend', blankSvg);
214      }
215
216      const { dateText, datetime } = RenderNewsRelease.getFormattedDateString(
217        data.date,
218        langType
219      );
220
221      releaseDate.textContent = dateText;
222      releaseDate.setAttribute('datetime', datetime);
223
224      // カテゴリ情報
225      for (const category of data.categories) {
226        if (!(category in this.category)) {
227          continue;
228        }
229
230        const [categoryColorType, categoryText, categoryTextGlobal] =
231          this.category[category];
232        const listLabelItemClone = listLabelItem.cloneNode(true);
233        /** @type {HTMLElement | null} */
234        const listLabelText = listLabelItemClone.querySelector(
235          '.js-render-news__listLabelText'
236        );
237
238        if (!listLabelText) {
239          continue;
240        }
241
242        listLabelText.textContent =
243          langType === 'ja' ? categoryText : categoryTextGlobal;
244        listLabelText.classList.add(categoryColorType);
245
246        listLabel.append(listLabelItemClone);
247      }
248
249      listLabelItem.remove(); // 復元元のリスト削除
250      list.append(listItemClone);
251    }
252
253    listItem.remove(); // 複製元のリスト削除
254    this.renderTemplate.replaceWith(clone);
255    this.renderRoot.classList.add('is-rendered');
256  }
257
258  /**
259   * エラー情報出力
260   * @param {string} errorText
261   */
262  renderError(errorText) {
263    /** @type {HTMLElement} */
264    const clone = this.renderTemplateError.content.cloneNode(true);
265    const label = clone.querySelector('.js-render-news__errorText');
266
267    if (label) {
268      label.textContent = errorText;
269    }
270
271    this.renderRoot.innerHTML = '';
272    this.renderRoot.append(clone);
273  }
274}
275
276/**
277 * 重要なお知らせエリアの出力を実施
278 *
279 * @export
280 * @class RenderImportantTopics
281 */
282export class RenderImportantTopics {
283  /**
284   * @param {HTMLElement} renderRoot
285   * @memberof RenderNewsRelease
286   */
287  constructor(renderRoot) {
288    /** @type {HTMLElement} */
289    this.renderRoot = renderRoot;
290    /** @type {HTMLTemplateElement | null} */
291    this.renderTemplate = this.renderRoot.querySelector(
292      '.js-render-important__template'
293    );
294    /** @type {HTMLTemplateElement | null} */
295    this.renderTemplateError = this.renderRoot.querySelector(
296      '.js-render-important__error'
297    );
298    /** @type {string} */
299    this.jsonPath =
300      langType === 'ja'
301        ? 'https://info.kirinholdings.com/parts/multi_faq_for_json.php?category=2001&num=50'
302        : 'https://info.kirinholdings.com/parts/multi_faq_for_json.php?category=2002&num=50&_=1748912240765';
303    /** @type {TopImportantTopicsInfo} */
304    this.jsonData = {};
305    this.showMoreOnSpElement =
306      this.renderRoot.querySelector('.js-showMoreOnSp');
307  }
308
309  async init() {
310    try {
311      this.jsonData = await fetchJson(this.jsonPath);
312
313      if (this.jsonData.length === 0) {
314        this.renderError();
315
316        return;
317      }
318
319      this.render(this.jsonData);
320    } catch {
321      this.renderError();
322    }
323  }
324
325  /**
326   * 年表記文字列を取得
327   *
328   * @static
329   * @param {string} baseFormat 日付情報文字列(yyyy-MM-dd HH:mm:ss形式)
330   * @param {string} lang 言語タイプ
331   * @return {{dateText: string, datetime: string}} 出力フォーマットに対応した情報
332   */
333  static getFormattedDateString(baseFormat, lang = 'ja') {
334    let datetimeFormat = '';
335
336    switch (lang) {
337      case 'ja': {
338        const [yearText, monthText, dayText] = baseFormat
339          .replace(/[年月]/gu, '-')
340          .replace('日', '')
341          .split('-');
342
343        datetimeFormat = `${yearText}-${monthText.padStart(2, '0')}-${dayText.padStart(2, '0')}`;
344        break;
345      }
346      case 'en': {
347        // ex) 'May 13, 2025' → ['May', '13', '2025']
348        const [monthEnText, dayEnText, yearEnText] = baseFormat
349          .replace(/,/gu, '')
350          .replace(/\s/gu, ',')
351          .split(',');
352        const monthMap = {
353          January: '01',
354          February: '02',
355          March: '03',
356          April: '04',
357          May: '05',
358          June: '06',
359          July: '07',
360          August: '08',
361          September: '09',
362          October: '10',
363          November: '11',
364          December: '12',
365        };
366
367        datetimeFormat = `${yearEnText}-${monthMap[monthEnText]}-${dayEnText.padStart(2, '0')}`;
368        break;
369      }
370      default:
371        break;
372    }
373
374    return {
375      dateText: baseFormat,
376      datetime: datetimeFormat,
377    };
378  }
379
380  /**
381   * 対象要素のレンダリング
382   *
383   * @param {TopImportantTopicsInfo[]} renderData
384   */
385  render(renderData) {
386    /** @type {HTMLElement} */
387    const clone = this.renderTemplate.content.cloneNode(true);
388    /** @type {HTMLElement | null} */
389    const list = clone.querySelector('.js-render-important__list');
390    /** @type {HTMLElement | null} */
391    const listItem = clone.querySelector('.js-render-important__listItem');
392
393    if (!list && !listItem) {
394      this.renderError();
395
396      return;
397    }
398
399    for (const data of renderData) {
400      /** @type {HTMLElement} */
401      const listItemClone = listItem.cloneNode(true);
402      const releaseDate = listItemClone.querySelector(
403        '.js-render-important__date'
404      );
405      const link = listItemClone.querySelector('.js-render-important__link');
406      const linkText = listItemClone.querySelector(
407        '.js-render-important__linkText'
408      );
409
410      if (!releaseDate && !link && !linkText && !releaseDate) {
411        this.renderError();
412
413        break;
414      }
415
416      linkText.textContent = data.title;
417      link.setAttribute('href', data.link);
418
419      if (data.target === '_blank') {
420        link.setAttribute('target', '_blank');
421
422        if (!data.pdf) {
423          linkText.insertAdjacentHTML('afterend', blankSvg);
424        } else {
425          linkText.insertAdjacentHTML('afterend', pdfSvg);
426        }
427      }
428
429      const { dateText, datetime } =
430        RenderImportantTopics.getFormattedDateString(data.date, langType);
431
432      releaseDate.textContent = dateText;
433      releaseDate.setAttribute('datetime', datetime);
434
435      list.append(listItemClone);
436    }
437
438    listItem.remove();
439    this.renderTemplate.replaceWith(clone);
440    this.renderRoot.classList.add('is-rendered');
441
442    if (this.showMoreOnSpElement) {
443      setShowMoreOnSp(this.showMoreOnSpElement);
444    }
445  }
446
447  renderError() {
448    this.renderRoot.remove();
449  }
450}

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.