PluginProbe
ElasticPress / 4.3.0
ElasticPress v4.3.0
5.3.5 5.3.4 3.6.5 3.6.6 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.4.1 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.7.0 4.7.1 4.7.2 5.0.0 5.0.1 5.0.2 All 108 releases
elasticpress / assets / js / comments.js

comments.js in ElasticPress 4.3.0, at assets/js/comments.js

289 lines 7.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { findAncestorByClass, debounce } from './utils/helpers';
2
3 const widgetSearchComments = document.querySelectorAll('.ep-widget-search-comments');
4
5 let selectedResultIndex;
6
7 widgetSearchComments.forEach((element) => {
8 const input = document.createElement('input');
9 input.setAttribute('autocomplete', 'off');
10 input.setAttribute('type', 'search');
11 input.setAttribute('class', 'ep-widget-search-comments-input');
12
13 const resultList = document.createElement('ul');
14 resultList.setAttribute('class', 'ep-widget-search-comments-results');
15
16 element.appendChild(input);
17 element.appendChild(resultList);
18 });
19
20 // these are the keycodes we listen for in handleUpDown,
21 // and in handleKeyup
22 const keyCodes = [
23 38, // up
24 40, // down
25 13, // enter
26 ];
27
28 /**
29 * Hide the result list
30 *
31 * @param {HTMLInputElement} inputElement The input element used in the widget
32 */
33 function hideResultsBox(inputElement) {
34 selectedResultIndex = undefined;
35
36 const widget = findAncestorByClass(inputElement, 'ep-widget-search-comments');
37 const resultList = widget.querySelector('.ep-widget-search-comments-results');
38
39 while (resultList.firstChild) {
40 resultList.removeChild(resultList.firstChild);
41 }
42 }
43
44 /**
45 * Update the result list
46 *
47 * @param {object} comments Comments to be showed
48 * @param {HTMLInputElement} inputElement The input element used in the widget
49 */
50 const updateResultsBox = (comments, inputElement) => {
51 let items = '';
52 let itemHTML = '';
53
54 Object.keys(comments).forEach((id, index) => {
55 if (comments[id]?.content && comments[id]?.link) {
56 itemHTML = `
57 <li class="ep-widget-search-comments-result-item">
58 <a href="${comments[id].link}">
59 ${comments[id].content}
60 </a>
61 </li>
62 `;
63
64 if (typeof window.epCommentWidgetItemHTMLFilter !== 'undefined') {
65 itemHTML = window.epCommentWidgetItemHTMLFilter(
66 itemHTML,
67 comments[id],
68 index,
69 inputElement.value,
70 );
71 }
72
73 items += itemHTML;
74 }
75 });
76
77 const widget = findAncestorByClass(inputElement, 'ep-widget-search-comments');
78 const resultList = widget.querySelector('.ep-widget-search-comments-results');
79
80 if (typeof window.epCommentWidgetItemsHTMLFilter !== 'undefined') {
81 items = window.epCommentWidgetItemsHTMLFilter(items, inputElement.value);
82 }
83
84 resultList.innerHTML = items;
85 };
86
87 /**
88 * Update the result list to inform the user that no results were found
89 *
90 * @param {HTMLInputElement} inputElement The input element used in the widget
91 */
92 const showNotFoundInResultsBox = (inputElement) => {
93 const widget = findAncestorByClass(inputElement, 'ep-widget-search-comments');
94 const resultList = widget.querySelector('.ep-widget-search-comments-results');
95
96 let itemHTML = `<li class="ep-widget-search-comments-result-item-not-found">${window.epc.noResultsFoundText}</li>`;
97
98 if (typeof window.epCommentWidgetItemNotFoundHTMLFilter !== 'undefined') {
99 itemHTML = window.epCommentWidgetItemNotFoundHTMLFilter(
100 itemHTML,
101 window.epc.noResultsFoundText,
102 inputElement.value,
103 );
104 }
105
106 resultList.innerHTML = itemHTML;
107 };
108
109 function hasMinimumLength(inputElement) {
110 const minimumLength = window.epc.minimumLengthToSearch || 2;
111 return inputElement?.value?.trim().length >= minimumLength;
112 }
113
114 /**
115 * Add class to the widget element while results are being loaded
116 *
117 * @param {boolean} isLoading Whether results are loading
118 * @param {Node} inputElement Search input field
119 */
120 function setIsLoading(isLoading, inputElement) {
121 const widget = findAncestorByClass(inputElement, 'ep-widget-search-comments');
122
123 if (isLoading) {
124 widget.classList.add('ep-widget-search-comments-is-loading');
125 } else {
126 widget.classList.remove('ep-widget-search-comments-is-loading');
127 }
128 }
129
130 /**
131 * Fetch comments
132 *
133 * @param {HTMLInputElement} inputElement The input element used in the widget
134 * @returns {(false|Promise)} Try to fetch comments
135 */
136 function fetchResults(inputElement) {
137 if (hasMinimumLength(inputElement)) {
138 const widget = findAncestorByClass(inputElement, 'ep-widget-search-comments');
139 const postTypeElement = widget.querySelector('#ep-widget-search-comments-post-type');
140 const postTypeQueryParameter = postTypeElement?.value
141 ? `&post_type=${postTypeElement.value.trim()}`
142 : '';
143
144 setIsLoading(true, inputElement);
145 return fetch(
146 `${window.epc.restApiEndpoint}?s=${inputElement.value.trim()}${postTypeQueryParameter}`,
147 )
148 .then((response) => {
149 if (!response.ok) {
150 throw response;
151 }
152
153 return response.json();
154 })
155 .then((comments) => {
156 if (Object.keys(comments).length === 0) {
157 if (inputElement.value.trim()) {
158 showNotFoundInResultsBox(inputElement);
159 } else {
160 hideResultsBox(inputElement);
161 }
162 } else {
163 updateResultsBox(comments, inputElement);
164 }
165 })
166 .catch(() => {
167 hideResultsBox(inputElement);
168 })
169 .finally(() => {
170 setIsLoading(false, inputElement);
171 });
172 }
173 return false;
174 }
175
176 /**
177 * Handle up, down and enter key
178 *
179 * @param {Event} event keyup event
180 */
181 const handleUpDownEnter = (event) => {
182 if (!keyCodes.includes(event.keyCode)) {
183 return;
184 }
185
186 const widget = findAncestorByClass(event.target, 'ep-widget-search-comments');
187 const resultList = widget.querySelector('.ep-widget-search-comments-results');
188 const sizeResult = resultList.querySelectorAll('.ep-widget-search-comments-result-item').length;
189 const results = resultList.children;
190
191 const previousSelectedResultIndex = selectedResultIndex;
192
193 switch (event.keyCode) {
194 case 38: // Up
195 selectedResultIndex =
196 selectedResultIndex - 1 < 0 || typeof selectedResultIndex === 'undefined'
197 ? sizeResult - 1
198 : selectedResultIndex - 1;
199
200 break;
201
202 case 40: // Down
203 if (
204 typeof selectedResultIndex === 'undefined' ||
205 selectedResultIndex + 1 > sizeResult - 1
206 ) {
207 selectedResultIndex = 0;
208 } else {
209 selectedResultIndex += 1;
210 }
211
212 break;
213
214 case 13: // Enter
215 if (results[selectedResultIndex]?.classList.contains('selected') || sizeResult === 1) {
216 const indexItem = selectedResultIndex || 0;
217
218 if (results[indexItem]) {
219 const linkToComment = results[indexItem]
220 .querySelector('a')
221 ?.getAttribute('href');
222
223 window.location.href = linkToComment;
224 }
225 }
226
227 break;
228
229 default:
230 break;
231 }
232
233 if (typeof previousSelectedResultIndex === 'number') {
234 results[previousSelectedResultIndex].classList.remove('selected');
235 results[previousSelectedResultIndex].setAttribute('aria-selected', 'false');
236 }
237
238 results[selectedResultIndex]?.classList.add('selected');
239 results[selectedResultIndex]?.setAttribute('aria-selected', 'true');
240 };
241
242 const debounceFetchResults = debounce(fetchResults, 500);
243
244 /**
245 * Callback for keyup in Widget Search Comment container.
246 *
247 * Calls a debounced function to get the search results via
248 * api rest request.
249 *
250 * @param {event} event - keyup event
251 */
252 const handleKeyup = (event) => {
253 event.preventDefault();
254 const { target, key, keyCode } = event;
255
256 if (key === 'Escape' || key === 'Esc' || keyCode === 27) {
257 hideResultsBox(target);
258 target.setAttribute('aria-expanded', false);
259
260 return;
261 }
262
263 if (keyCodes.includes(keyCode) && target.value !== '') {
264 handleUpDownEnter(event);
265
266 return;
267 }
268
269 if (hasMinimumLength(target)) {
270 debounceFetchResults(target);
271 } else {
272 hideResultsBox(target);
273 }
274 };
275
276 widgetSearchComments.forEach((element) => {
277 const input = element.querySelector('.ep-widget-search-comments-input');
278
279 input.addEventListener('keyup', handleKeyup);
280 input.addEventListener('keydown', (event) => {
281 if (event.keyCode === 38) {
282 event.preventDefault();
283 }
284 });
285 input.addEventListener('blur', function () {
286 setTimeout(() => hideResultsBox(input), 200);
287 });
288 });
289