/**
* HPKIセカンド電子証明書サービス 統合FAQ
* 医療機関・保険薬局向け / ベンダー向け をタブで切り替える共通アプリケーション
*/
(function () {
'use strict';
// タブ設定テーブル(データ・表示文言・テーマはここで一元管理)
var TAB_CONFIG = {
institution: {
data: (typeof FAQ_DATA_INSTITUTION !== 'undefined') ? FAQ_DATA_INSTITUTION : [],
tabLabel: '医療機関・保険薬局向け',
title: 'クライアント証明書に関するFAQ',
subtitle: '医療機関・保険薬局向け よくある質問と回答',
docTitle: 'クライアント証明書に関するFAQ - HPKIセカンド電子証明書サービス',
placeholder: 'キーワードで検索(例: 振込先、口座名義人、再発行、パスワード通知書)'
},
vendor: {
data: (typeof FAQ_DATA_VENDOR !== 'undefined') ? FAQ_DATA_VENDOR : [],
tabLabel: 'ベンダー向け',
title: 'HPKIセカンド電子証明書サービス 技術的質問 FAQ',
subtitle: 'ベンダー向け よくある質問と回答',
docTitle: 'FAQ - HPKIセカンド電子証明書サービス',
placeholder: 'キーワードで検索(例: FIDO、マイナンバーカード、クライアント証明書)'
}
};
var TAB_ORDER = ['institution', 'vendor'];
var DEFAULT_TAB = 'institution';
var currentTab = DEFAULT_TAB;
var currentKeyword = '';
// 現在タブのカテゴリ表示順とアイコン番号(データの大分類出現順から動的導出)
var categoryOrder = [];
var categoryIcons = {};
// リンクアイコン(🔗)
var LINK_ICON = '🔗';
/**
* 現在タブのデータを取得
*/
function activeData() {
return TAB_CONFIG[currentTab].data;
}
/**
* クエリパラメタ取得(ES5互換)
*/
function getQueryParam(name) {
var m = new RegExp('[?&]' + name + '=([^]*)').exec(location.search);
return m ? decodeURIComponent(m[1].replace(/\+/g, ' ')) : null;
}
/**
* 現在タブのデータからカテゴリ表示順・アイコン番号を再計算
*/
function recomputeCategories() {
categoryOrder = [];
categoryIcons = {};
activeData().forEach(function (item) {
if (categoryOrder.indexOf(item.majorCategory) === -1) {
categoryOrder.push(item.majorCategory);
categoryIcons[item.majorCategory] = String(categoryOrder.length);
}
});
}
/**
* FAQデータをmajorCategory → minorCategoryでグループ化
*/
function groupFAQ(data) {
var grouped = {};
data.forEach(function (item) {
var key = item.majorCategory;
if (!grouped[key]) {
grouped[key] = {
code: item.majorCategory,
name: item.majorCategoryName,
minorCategories: {}
};
}
var minor = item.minorCategory || '';
if (!grouped[key].minorCategories[minor]) {
grouped[key].minorCategories[minor] = [];
}
grouped[key].minorCategories[minor].push(item);
});
return grouped;
}
/**
* テキスト内のキーワードをハイライト
*/
function highlightText(text, keyword) {
if (!keyword) return text;
// HTMLタグ内および ... ブロック内(URL表示)はハイライトしない
var parts = text.split(/(]*>[\s\S]*?<\/a>|<[^>]+>)/);
return parts.map(function (part) {
if (part.startsWith('<')) return part;
var escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return part.replace(new RegExp(escaped, 'gi'), '$&');
}).join('');
}
/**
* FAQ全体をレンダリング
*/
function renderFAQ(data, keyword) {
var contentEl = document.getElementById('faq-content');
var grouped = groupFAQ(data);
var html = '';
var totalVisible = 0;
categoryOrder.forEach(function (catCode) {
var cat = grouped[catCode];
if (!cat) return;
var catHtml = '';
var catHasItems = false;
Object.keys(cat.minorCategories).forEach(function (minorName) {
var items = cat.minorCategories[minorName];
var itemsHtml = '';
items.forEach(function (item) {
var q = keyword ? highlightText(escapeHtml(item.question), keyword) : escapeHtml(item.question);
var a = keyword ? highlightText(item.answer, keyword) : item.answer;
itemsHtml += '' +
'' + q + '' +
'' +
'▶
' +
'' +
' ';
totalVisible++;
});
if (itemsHtml) {
catHasItems = true;
// 小分類名が空の場合は小分類見出しを出力しない
if (minorName) {
var minorTitle = keyword ? highlightText(escapeHtml(minorName), keyword) : escapeHtml(minorName);
catHtml += '' +
'' +
itemsHtml + '
';
} else {
catHtml += '' + itemsHtml + '
';
}
}
});
if (catHasItems) {
html += '';
}
});
if (!html) {
html = '' +
'
🔍
' +
'
該当するFAQが見つかりません
' +
'
別のキーワードでお試しください
' +
'
';
}
contentEl.innerHTML = html;
// 検索件数表示
var countEl = document.getElementById('search-count');
if (keyword) {
countEl.textContent = totalVisible + ' 件の結果';
} else {
countEl.textContent = '全 ' + activeData().length + ' 件';
}
}
/**
* HTMLエスケープ(改行はスペースに変換)
*/
function escapeHtml(text) {
var div = document.createElement('div');
div.textContent = text;
return div.innerHTML.replace(/\n/g, ' ');
}
/**
* キーワードでFAQをフィルタリング
*/
function filterFAQ(keyword) {
currentKeyword = keyword;
if (!keyword) {
renderFAQ(activeData(), '');
return;
}
var lower = keyword.toLowerCase();
var filtered = activeData().filter(function (item) {
var answerText = item.answer.replace(/<[^>]+>/g, '').toLowerCase();
return item.question.toLowerCase().indexOf(lower) !== -1 ||
answerText.indexOf(lower) !== -1;
});
renderFAQ(filtered, keyword);
}
/**
* サイドバーナビゲーション生成
*/
function renderNav() {
var navEl = document.getElementById('category-nav');
var grouped = groupFAQ(activeData());
var html = 'カテゴリ
';
categoryOrder.forEach(function (catCode) {
var cat = grouped[catCode];
if (!cat) return;
var count = 0;
Object.keys(cat.minorCategories).forEach(function (k) {
count += cat.minorCategories[k].length;
});
html += '- ' +
'' +
cat.name + ' (' + count + ')' +
'
';
});
html += '
';
navEl.innerHTML = html;
// クリックイベント(スムーススクロール)
navEl.querySelectorAll('.nav-link').forEach(function (link) {
link.addEventListener('click', function (e) {
e.preventDefault();
var targetId = this.getAttribute('href').substring(1);
var targetEl = document.getElementById(targetId);
if (targetEl) {
targetEl.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
// 検索をクリア
var searchInput = document.getElementById('search-input');
if (searchInput.value) {
searchInput.value = '';
filterFAQ('');
// スクロールし直す(再レンダリング後)
setTimeout(function () {
var el = document.getElementById(targetId);
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, 50);
}
});
});
}
/**
* テキストをクリップボードへコピー(非対応環境はフォールバック)
*/
function copyToClipboard(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
return navigator.clipboard.writeText(text);
}
return new Promise(function (resolve, reject) {
try {
var ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed';
ta.style.opacity = '0';
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
resolve();
} catch (err) {
reject(err);
}
});
}
/**
* コピーボタンに「コピー済み」フィードバックを表示
*/
function showCopied(btn) {
if (btn._resetTimer) clearTimeout(btn._resetTimer);
btn.classList.add('copied');
btn.innerHTML = '✓'; // ✓
btn.setAttribute('aria-label', 'リンクをコピーしました');
btn._resetTimer = setTimeout(function () {
btn.classList.remove('copied');
btn.innerHTML = LINK_ICON;
btn.setAttribute('aria-label', 'このQ&Aへのリンクをコピー');
}, 1500);
}
/**
* URLハッシュ(#faq-{id})に対応するQ&Aを開いてスクロール・強調
* ※ タブは呼び出し側で確定済みである前提
*/
function openFAQFromHash() {
var m = /^#faq-(\d+)$/.exec(location.hash);
if (!m) return;
// 検索中は解除して全件表示(対象が絞り込みで非表示の場合に備える)
var searchInput = document.getElementById('search-input');
if (searchInput && searchInput.value) {
searchInput.value = '';
filterFAQ('');
}
var el = document.getElementById('faq-' + m[1]);
if (!el) return;
el.open = true;
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
el.classList.remove('faq-flash');
// リフローを挟んでアニメーションを再起動
void el.offsetWidth;
el.classList.add('faq-flash');
}
/**
* タブを切り替える
* @param {string} tab 'institution' | 'vendor'
* @param {object} [opts] { updateUrl: boolean }
*/
function switchTab(tab, opts) {
if (!TAB_CONFIG[tab]) tab = DEFAULT_TAB;
currentTab = tab;
var conf = TAB_CONFIG[tab];
// テーマ色分け
document.body.setAttribute('data-theme', tab);
// タブボタンの状態更新
var tabButtons = document.querySelectorAll('.faq-tab');
tabButtons.forEach(function (btn) {
var selected = btn.getAttribute('data-tab') === tab;
btn.setAttribute('aria-selected', selected ? 'true' : 'false');
btn.classList.toggle('active', selected);
btn.tabIndex = selected ? 0 : -1;
});
// ヘッダー・タイトル・プレースホルダ更新
var titleEl = document.getElementById('page-title');
var subtitleEl = document.getElementById('page-subtitle');
if (titleEl) titleEl.textContent = conf.title;
if (subtitleEl) subtitleEl.textContent = conf.subtitle;
document.title = conf.docTitle;
var searchInput = document.getElementById('search-input');
if (searchInput) {
searchInput.placeholder = conf.placeholder;
searchInput.value = '';
}
currentKeyword = '';
// データ再構築・描画
recomputeCategories();
renderNav();
renderFAQ(activeData(), '');
// URLへタブを反映(ハッシュはクリア)
if (opts && opts.updateUrl) {
var newUrl = location.pathname + '?tab=' + tab;
history.replaceState(null, '', newUrl);
}
}
/**
* 初期化
*/
document.addEventListener('DOMContentLoaded', function () {
// 初期タブ:?tab= パラメタ → 不正/無指定は既定
var paramTab = getQueryParam('tab');
var initialTab = TAB_CONFIG[paramTab] ? paramTab : DEFAULT_TAB;
switchTab(initialTab, { updateUrl: false });
// タブクリック
var tablist = document.querySelector('.faq-tabs');
if (tablist) {
tablist.addEventListener('click', function (e) {
var btn = e.target.closest('.faq-tab');
if (!btn) return;
var tab = btn.getAttribute('data-tab');
if (tab !== currentTab) {
switchTab(tab, { updateUrl: true });
}
});
// キーボード操作(左右矢印でタブ移動)
tablist.addEventListener('keydown', function (e) {
if (e.key !== 'ArrowRight' && e.key !== 'ArrowLeft') return;
var idx = TAB_ORDER.indexOf(currentTab);
if (idx === -1) return;
var next = e.key === 'ArrowRight'
? (idx + 1) % TAB_ORDER.length
: (idx - 1 + TAB_ORDER.length) % TAB_ORDER.length;
e.preventDefault();
switchTab(TAB_ORDER[next], { updateUrl: true });
var btn = document.querySelector('.faq-tab[data-tab="' + TAB_ORDER[next] + '"]');
if (btn) btn.focus();
});
}
var contentEl = document.getElementById('faq-content');
// コピーボタン(イベント委譲。クリックでアコーディオンは開閉しない)
contentEl.addEventListener('click', function (e) {
var btn = e.target.closest('.copy-link-btn');
if (!btn) return;
e.preventDefault();
e.stopPropagation();
// タブを含むURLを生成(タブ間でidが重複するため)
var url = location.origin + location.pathname +
'?tab=' + currentTab + '#faq-' + btn.getAttribute('data-id');
copyToClipboard(url).then(function () {
showCopied(btn);
}).catch(function () {
// コピー失敗時はURLを選択可能な状態で通知
window.prompt('以下のリンクをコピーしてください', url);
});
});
// 直リンクで開いた場合・ハッシュ変更時に該当Q&Aを開く(タブ確定後)
openFAQFromHash();
window.addEventListener('hashchange', openFAQFromHash);
// 検索イベント
var searchInput = document.getElementById('search-input');
var debounceTimer;
searchInput.addEventListener('input', function () {
clearTimeout(debounceTimer);
var value = this.value.trim();
debounceTimer = setTimeout(function () {
filterFAQ(value);
}, 200);
});
// Escキーで検索クリア
searchInput.addEventListener('keydown', function (e) {
if (e.key === 'Escape') {
this.value = '';
filterFAQ('');
this.blur();
}
});
});
})();