(function($) { "use strict"; var DEFAULT_DATA_URL = "/personal/faq/faq-search-data.json"; var MAX_RESULTS = 20; /* 入力中に出す候補(サジェスト)の設定 */ var SUGGEST_MAX = 6; var SUGGEST_DELAY = 200; var SUGGEST_LIST_ID = "faqSearchSuggestList"; var SUGGEST_ITEM_ID = "faqSearchSuggestItem"; function toHalfWidthAscii(value) { return value .replace(/[!-~]/g, function(character) { return String.fromCharCode(character.charCodeAt(0) - 0xFEE0); }) .replace(/ /g, " "); } function normalizeText(value) { var text = value === null || typeof value === "undefined" ? "" : String(value); if (text.normalize) { try { text = text.normalize("NFKC"); } catch (error) { text = toHalfWidthAscii(text); } } else { text = toHalfWidthAscii(text); } return $.trim(text.toLowerCase().replace(/\s+/g, " ")); } function createSearchRecord(item) { var record = { item: item, title: normalizeText(item.title), keywords: normalizeText(item.keywords), category: normalizeText(item.category), tags: normalizeText(item.tags), answer: normalizeText(item.answer) }; record.all = [record.title, record.keywords, record.category, record.tags, record.answer].join(" "); return record; } function scoreRecord(record, query, tokens) { var score = 0; var i; var token; for (i = 0; i < tokens.length; i += 1) { token = tokens[i]; if (record.all.indexOf(token) === -1) { return -1; } if (record.title === token) { score += 120; } else if (record.title.indexOf(token) === 0) { score += 90; } else if (record.title.indexOf(token) !== -1) { score += 70; } if (record.keywords.indexOf(token) !== -1) { score += 60; } if (record.category.indexOf(token) !== -1) { score += 35; } if (record.tags.indexOf(token) !== -1) { score += 25; } if (record.answer.indexOf(token) !== -1) { score += 10; } } if (record.title === query) { score += 200; } else if (record.title.indexOf(query) !== -1) { score += 80; } return score; } function getSortOrder(item) { var value = $.trim(item.sortOrder || ""); return value !== "" && !isNaN(Number(value)) ? Number(value) : null; } function compareMatches(a, b) { var aOrder; var bOrder; if (a.score !== b.score) { return b.score - a.score; } aOrder = getSortOrder(a.record.item); bOrder = getSortOrder(b.record.item); if (aOrder !== null && bOrder !== null && aOrder !== bOrder) { return aOrder - bOrder; } if ((aOrder === null) !== (bOrder === null)) { return aOrder === null ? 1 : -1; } return Number(b.record.item.published || 0) - Number(a.record.item.published || 0); } /* 検索結果一覧とサジェストで同じ絞り込みを使う */ function findMatches(records, rawQuery) { var query = normalizeText(rawQuery); var matches = []; var tokens; if (!query) { return matches; } tokens = query.split(" "); $.each(records || [], function(index, record) { var score = scoreRecord(record, query, tokens); if (score >= 0) { matches.push({ record: record, score: score }); } }); matches.sort(compareMatches); return matches; } function getLocationQuery() { var match = window.location.search.match(/[?&]q=([^&]*)/); if (!match) { return ""; } try { return decodeURIComponent(match[1].replace(/\+/g, " ")); } catch (error) { return match[1].replace(/\+/g, " "); } } function updateLocationQuery(query, replace) { var url; if (!window.history || !window.history.pushState) { return; } url = window.location.pathname + (query ? "?q=" + encodeURIComponent(query) : ""); window.history[replace ? "replaceState" : "pushState"]({ faqQuery: query }, "", url); } function initFaqSearch() { var $form = $(".js-faq-search-form").first(); var $input = $form.find(".js-faq-search-input").first(); var $submit = $form.find(".js-faq-search-submit").first(); var $inputWrap = $input.closest(".p-faqTop__searchInputWrap"); var $results = $(".js-faq-search-results").first(); var $status = $results.find(".js-faq-search-status").first(); var $list = $results.find(".js-faq-search-list").first(); var $message = $results.find(".js-faq-search-message").first(); var dataUrl = $.trim($form.attr("data-search-data") || "") || DEFAULT_DATA_URL; var records = null; var loading = false; var dataFailed = false; var readyCallbacks = []; var initialQuery; /* サジェスト側の状態 */ var $suggest = null; var $suggestLive = null; var suggestMatches = []; var suggestIndex = -1; var suggestTimer = null; var composing = false; if (!$form.length || !$results.length) { return; } function setBusy(busy) { $submit.prop("disabled", busy).toggleClass("is-loading", busy); $submit.text(busy ? "読込中" : "検索する"); } function showState(status, message, error) { $results.removeAttr("hidden"); $status.text(status || "").toggleClass("is-error", !!error); $list.empty().attr("hidden", "hidden"); if (message) { $message.text(message).removeAttr("hidden").toggleClass("is-error", !!error); } else { $message.text("").attr("hidden", "hidden").removeClass("is-error"); } } /* 検索データの読み込みを1本にまとめる。サジェストと検索ボタンの両方から 呼ばれるので、読み込み中に来た依頼はキューへ積んで完了時にまとめて流す。 */ function flushReady(success) { var callbacks = readyCallbacks; readyCallbacks = []; $.each(callbacks, function(index, callback) { callback(success); }); } function ensureData(onReady) { if (records !== null) { if (onReady) { onReady(true); } return; } /* 一度失敗したらサジェストからは再試行しない(検索ボタン側でリセットする) */ if (dataFailed) { if (onReady) { onReady(false); } return; } if (onReady) { readyCallbacks.push(onReady); } if (loading) { return; } loading = true; $.ajax({ url: dataUrl, dataType: "json", cache: false }).done(function(data) { var items = $.isArray(data) ? data : []; records = $.map(items, function(item) { return createSearchRecord(item || {}); }); loading = false; flushReady(true); }).fail(function() { loading = false; dataFailed = true; flushReady(false); }); } function appendResult(match) { var item = match.record.item; var $resultItem = $("
") .addClass("p-faqTop__searchSuggestLive") .attr({ role: "status", "aria-live": "polite" }); $inputWrap.append($suggest).append($suggestLive); $input.attr({ role: "combobox", "aria-expanded": "false", "aria-autocomplete": "list", "aria-controls": SUGGEST_LIST_ID }); } function closeSuggest() { if (!$suggest) { return; } $suggest.attr("hidden", "hidden").empty(); $input.attr("aria-expanded", "false").removeAttr("aria-activedescendant"); $suggestLive.text(""); suggestMatches = []; suggestIndex = -1; } /* 選んだ候補がリストからはみ出していたらスクロールして見せる */ function scrollSuggestIntoView($option) { var listTop = $suggest.scrollTop(); var listHeight = $suggest.innerHeight(); var optionTop = $option.position().top + listTop; var optionHeight = $option.outerHeight(); if (optionTop < listTop) { $suggest.scrollTop(optionTop); } else if (optionTop + optionHeight > listTop + listHeight) { $suggest.scrollTop(optionTop + optionHeight - listHeight); } } function setSuggestActive(index) { var $options = $suggest.children(); var $active; $options.removeClass("is-active").attr("aria-selected", "false"); suggestIndex = index; if (index < 0) { $input.removeAttr("aria-activedescendant"); return; } $active = $options.eq(index).addClass("is-active").attr("aria-selected", "true"); $input.attr("aria-activedescendant", $active.attr("id")); scrollSuggestIntoView($active); } function createSuggestOption(match, index) { var item = match.record.item; var $option = $("