import { findAncestorByClass, debounce } from './utils/helpers'; const widgetSearchComments = document.querySelectorAll('.ep-widget-search-comments'); let selectedResultIndex; widgetSearchComments.forEach((element) => { const input = document.createElement('input'); input.setAttribute('autocomplete', 'off'); input.setAttribute('type', 'search'); input.setAttribute('class', 'ep-widget-search-comments-input'); const resultList = document.createElement('ul'); resultList.setAttribute('class', 'ep-widget-search-comments-results'); element.appendChild(input); element.appendChild(resultList); }); // these are the keycodes we listen for in handleUpDown, // and in handleKeyup const keyCodes = [ 38, // up 40, // down 13, // enter ]; /** * Hide the result list * * @param {HTMLInputElement} inputElement The input element used in the widget */ function hideResultsBox(inputElement) { selectedResultIndex = undefined; const widget = findAncestorByClass(inputElement, 'ep-widget-search-comments'); const resultList = widget.querySelector('.ep-widget-search-comments-results'); while (resultList.firstChild) { resultList.removeChild(resultList.firstChild); } } /** * Update the result list * * @param {object} comments Comments to be showed * @param {HTMLInputElement} inputElement The input element used in the widget */ const updateResultsBox = (comments, inputElement) => { let items = ''; let itemHTML = ''; Object.keys(comments).forEach((id, index) => { if (comments[id]?.content && comments[id]?.link) { itemHTML = `
  • ${comments[id].content}
  • `; if (typeof window.epCommentWidgetItemHTMLFilter !== 'undefined') { itemHTML = window.epCommentWidgetItemHTMLFilter( itemHTML, comments[id], index, inputElement.value, ); } items += itemHTML; } }); const widget = findAncestorByClass(inputElement, 'ep-widget-search-comments'); const resultList = widget.querySelector('.ep-widget-search-comments-results'); if (typeof window.epCommentWidgetItemsHTMLFilter !== 'undefined') { items = window.epCommentWidgetItemsHTMLFilter(items, inputElement.value); } resultList.innerHTML = items; }; /** * Update the result list to inform the user that no results were found * * @param {HTMLInputElement} inputElement The input element used in the widget */ const showNotFoundInResultsBox = (inputElement) => { const widget = findAncestorByClass(inputElement, 'ep-widget-search-comments'); const resultList = widget.querySelector('.ep-widget-search-comments-results'); let itemHTML = `
  • ${window.epc.noResultsFoundText}
  • `; if (typeof window.epCommentWidgetItemNotFoundHTMLFilter !== 'undefined') { itemHTML = window.epCommentWidgetItemNotFoundHTMLFilter( itemHTML, window.epc.noResultsFoundText, inputElement.value, ); } resultList.innerHTML = itemHTML; }; function hasMinimumLength(inputElement) { const minimumLength = window.epc.minimumLengthToSearch || 2; return inputElement?.value?.trim().length >= minimumLength; } /** * Add class to the widget element while results are being loaded * * @param {boolean} isLoading Whether results are loading * @param {Node} inputElement Search input field */ function setIsLoading(isLoading, inputElement) { const widget = findAncestorByClass(inputElement, 'ep-widget-search-comments'); if (isLoading) { widget.classList.add('ep-widget-search-comments-is-loading'); } else { widget.classList.remove('ep-widget-search-comments-is-loading'); } } /** * Fetch comments * * @param {HTMLInputElement} inputElement The input element used in the widget * @returns {(false|Promise)} Try to fetch comments */ function fetchResults(inputElement) { if (hasMinimumLength(inputElement)) { const widget = findAncestorByClass(inputElement, 'ep-widget-search-comments'); const postTypeElement = widget.querySelector('#ep-widget-search-comments-post-type'); const postTypeQueryParameter = postTypeElement?.value ? `&post_type=${postTypeElement.value.trim()}` : ''; setIsLoading(true, inputElement); return fetch( `${window.epc.restApiEndpoint}?s=${inputElement.value.trim()}${postTypeQueryParameter}`, ) .then((response) => { if (!response.ok) { throw response; } return response.json(); }) .then((comments) => { if (Object.keys(comments).length === 0) { if (inputElement.value.trim()) { showNotFoundInResultsBox(inputElement); } else { hideResultsBox(inputElement); } } else { updateResultsBox(comments, inputElement); } }) .catch(() => { hideResultsBox(inputElement); }) .finally(() => { setIsLoading(false, inputElement); }); } return false; } /** * Handle up, down and enter key * * @param {Event} event keyup event */ const handleUpDownEnter = (event) => { if (!keyCodes.includes(event.keyCode)) { return; } const widget = findAncestorByClass(event.target, 'ep-widget-search-comments'); const resultList = widget.querySelector('.ep-widget-search-comments-results'); const sizeResult = resultList.querySelectorAll('.ep-widget-search-comments-result-item').length; const results = resultList.children; const previousSelectedResultIndex = selectedResultIndex; switch (event.keyCode) { case 38: // Up selectedResultIndex = selectedResultIndex - 1 < 0 || typeof selectedResultIndex === 'undefined' ? sizeResult - 1 : selectedResultIndex - 1; break; case 40: // Down if ( typeof selectedResultIndex === 'undefined' || selectedResultIndex + 1 > sizeResult - 1 ) { selectedResultIndex = 0; } else { selectedResultIndex += 1; } break; case 13: // Enter if (results[selectedResultIndex]?.classList.contains('selected') || sizeResult === 1) { const indexItem = selectedResultIndex || 0; if (results[indexItem]) { const linkToComment = results[indexItem] .querySelector('a') ?.getAttribute('href'); window.location.href = linkToComment; } } break; default: break; } if (typeof previousSelectedResultIndex === 'number') { results[previousSelectedResultIndex].classList.remove('selected'); results[previousSelectedResultIndex].setAttribute('aria-selected', 'false'); } results[selectedResultIndex]?.classList.add('selected'); results[selectedResultIndex]?.setAttribute('aria-selected', 'true'); }; const debounceFetchResults = debounce(fetchResults, 500); /** * Callback for keyup in Widget Search Comment container. * * Calls a debounced function to get the search results via * api rest request. * * @param {event} event - keyup event */ const handleKeyup = (event) => { event.preventDefault(); const { target, key, keyCode } = event; if (key === 'Escape' || key === 'Esc' || keyCode === 27) { hideResultsBox(target); target.setAttribute('aria-expanded', false); return; } if (keyCodes.includes(keyCode) && target.value !== '') { handleUpDownEnter(event); return; } if (hasMinimumLength(target)) { debounceFetchResults(target); } else { hideResultsBox(target); } }; widgetSearchComments.forEach((element) => { const input = element.querySelector('.ep-widget-search-comments-input'); input.addEventListener('keyup', handleKeyup); input.addEventListener('keydown', (event) => { if (event.keyCode === 38) { event.preventDefault(); } }); input.addEventListener('blur', function () { setTimeout(() => hideResultsBox(input), 200); }); });