PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.10
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.10
4.4.10 4.4.9.1 4.4.9 4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 All 142 releases
learnpress / assets / src / js / admin / addons.js

addons.js in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.4.10, at assets/src/js/admin/addons.js

859 lines 22.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Admin Add-ons page JS handler as a class.
3 *
4 * Add-ons list is rendered server-side via AdminAddonsPage::render_addons()
5 * through TemplateAJAX::load_content_via_ajax(). This class only handles
6 * UI interactions and addon actions via window.lpAJAXG.
7 *
8 * @since 4.4.7
9 * @version 1.0.0
10 */
11 import * as lpToastify from 'lpAssetsJsPath/lpToastify.js';
12 import * as lpUtils from 'lpAssetsJsPath/utils.js';
13
14 class AdminAddons {
15 constructor() {
16 this.elAddonsPage = null;
17 this.urlParams = new URLSearchParams( window.location.search );
18 }
19
20 static selectors = {
21 elAddonsPage: '.lp-addons-page',
22 elToolbar: '.lp-addons-toolbar',
23 elLPAddons: '#lp-addons',
24 elAddonItem: '.lp-addon-item',
25 elBtnAction: '.btn-addon-action',
26 elNavTab: '.nav-tab',
27 elNavTabActive: '.nav-tab.nav-tab-active',
28 elFilter: '.lp-addons-filter',
29 elFilterToggle: '.lp-addons-filter__toggle',
30 elFilterMenu: '.lp-addons-filter__menu',
31 elCategory: '.lp-addons-category',
32 elCategoryActive: '.lp-addons-category.active',
33 elSearch: '.lp-addons-search',
34 elSearchBtn: '.lp-addons-search__btn',
35 elSearchBtnClear: '.lp-addons-search__btn-clear',
36 elSearchClose: '.lp-addons-search__close',
37 elSearchInput: '#lp-search-addons__input',
38 elItemPurchase: '.lp-addon-item__purchase',
39 elPurchaseInstall: '.purchase-install',
40 elPurchaseCode: '.enter-purchase-code',
41 elLicense: '.lp-addon-license',
42 };
43
44 init() {
45 // Content is injected via AJAX into .lp-target, wait for #lp-addons.
46 lpUtils.lpOnElementReady( AdminAddons.selectors.elLPAddons, () => {
47 this.elAddonsPage = document.querySelector(
48 AdminAddons.selectors.elAddonsPage
49 );
50 if ( ! this.elAddonsPage ) {
51 return;
52 }
53
54 const selectors = AdminAddons.selectors;
55 const urlTab = this.urlParams.get( 'tab' );
56 if ( urlTab ) {
57 const elMatchingTab = this.elAddonsPage.querySelector(
58 `${ selectors.elNavTab }[data-tab="${ urlTab }"]`
59 );
60 if ( elMatchingTab ) {
61 this.elAddonsPage
62 .querySelectorAll( selectors.elNavTab )
63 .forEach( ( elTab ) => {
64 elTab.classList.remove( 'nav-tab-active' );
65 elTab.setAttribute( 'aria-pressed', 'false' );
66 } );
67 elMatchingTab.classList.add( 'nav-tab-active' );
68 elMatchingTab.setAttribute( 'aria-pressed', 'true' );
69 }
70 }
71
72 let elActiveTab = this.elAddonsPage.querySelector(
73 selectors.elNavTabActive
74 );
75 if ( ! elActiveTab ) {
76 elActiveTab =
77 this.elAddonsPage.querySelector(
78 `${ selectors.elNavTab }[data-tab="all"]`
79 ) || this.elAddonsPage.querySelector( selectors.elNavTab );
80 if ( elActiveTab ) {
81 elActiveTab.classList.add( 'nav-tab-active' );
82 elActiveTab.setAttribute( 'aria-pressed', 'true' );
83 }
84 }
85
86 let elActiveCategory = this.elAddonsPage.querySelector(
87 selectors.elCategoryActive
88 );
89 if ( ! elActiveCategory ) {
90 elActiveCategory =
91 this.elAddonsPage.querySelector(
92 `${ selectors.elCategory }[data-category="all"]`
93 ) || this.elAddonsPage.querySelector( selectors.elCategory );
94 if ( elActiveCategory ) {
95 elActiveCategory.classList.add( 'active' );
96 elActiveCategory.setAttribute( 'aria-pressed', 'true' );
97 }
98 }
99
100 this.filterAddons();
101 this.updateSearchClearBtnState();
102 this.updateFilterToggleState();
103 this.events();
104 } );
105 }
106
107 /**
108 * Register events.
109 *
110 * @return void
111 */
112 events() {
113 lpUtils.eventHandlers( 'click', [
114 {
115 selector: AdminAddons.selectors.elBtnAction,
116 class: this,
117 callBack: this.handleBtnAction.name,
118 },
119 {
120 selector: AdminAddons.selectors.elNavTab,
121 class: this,
122 callBack: this.handleTabClick.name,
123 },
124 {
125 selector: AdminAddons.selectors.elCategory,
126 class: this,
127 callBack: this.handleCategoryClick.name,
128 },
129 {
130 selector: AdminAddons.selectors.elFilterToggle,
131 class: this,
132 callBack: this.handleFilterToggleClick.name,
133 },
134 {
135 selector: AdminAddons.selectors.elSearchBtn,
136 class: this,
137 callBack: this.handleSearchBtnClick.name,
138 },
139 {
140 selector: AdminAddons.selectors.elSearchBtnClear,
141 class: this,
142 callBack: this.handleSearchClearClick.name,
143 },
144 {
145 selector: AdminAddons.selectors.elSearchClose,
146 class: this,
147 callBack: this.handleSearchCloseClick.name,
148 },
149 ] );
150 lpUtils.eventHandlers( 'input', [
151 {
152 selector: AdminAddons.selectors.elSearchInput,
153 class: this,
154 callBack: this.handleSearchInput.name,
155 },
156 {
157 selector: AdminAddons.selectors.elPurchaseCode,
158 class: this,
159 callBack: this.handlePurchaseCodeInput.name,
160 },
161 ] );
162 lpUtils.eventHandlers( 'keydown', [
163 {
164 selector: AdminAddons.selectors.elSearchInput,
165 class: this,
166 callBack: this.handleSearchKeydown.name,
167 },
168 ] );
169
170 document.addEventListener( 'click', ( e ) => {
171 if ( ! this.elAddonsPage ) {
172 return;
173 }
174 const elToolbar = this.elAddonsPage.querySelector(
175 AdminAddons.selectors.elToolbar
176 );
177 if ( elToolbar && elToolbar.classList.contains( 'is-filter-open' ) ) {
178 if ( ! elToolbar.contains( e.target ) ) {
179 this.toggleFilterMenu( false );
180 }
181 }
182 } );
183
184 document.addEventListener( 'keydown', ( e ) => {
185 if ( 'Escape' === e.key ) {
186 this.toggleFilterMenu( false );
187 }
188 } );
189 }
190
191 /**
192 * Get addon data embedded on the item element.
193 *
194 * @param {Element} elAddonItem Addon item element.
195 * @return {Object|null}
196 */
197 getAddonData( elAddonItem ) {
198 try {
199 return JSON.parse( elAddonItem.dataset.addon );
200 } catch ( e ) {
201 return null;
202 }
203 }
204
205 /**
206 * Send addon action to server via lpAJAXG.
207 *
208 * @param {Object} data { action, addon, purchase_code, el, elAddonItem }.
209 * @return void
210 */
211 addonsAction( data ) {
212 const { action, addon, el, elAddonItem } = data;
213 const selectors = AdminAddons.selectors;
214
215 const params = {
216 action: 'addon_action',
217 action_type: action,
218 addon,
219 purchase_code: data.purchase_code || '',
220 id_url: 'addon-action',
221 };
222
223 const releaseHandling = () => {
224 lpUtils.lpSetLoadingEl( el, 0 );
225 };
226
227 window.lpAJAXG.fetchAJAX( params, {
228 success: ( response ) => {
229 releaseHandling();
230
231 const { status, message, data: resData } = response;
232
233 lpToastify.show( message, status );
234
235 if ( status === 'success' && resData?.html ) {
236 elAddonItem.outerHTML = resData.html;
237 } else if (
238 'install' === action ||
239 'update-purchase' === action
240 ) {
241 const elPurchasePanel = el.closest(
242 selectors.elPurchaseInstall
243 );
244 const elPurchaseCode = elPurchasePanel
245 ? elPurchasePanel.querySelector( selectors.elPurchaseCode )
246 : null;
247 if ( elPurchaseCode ) {
248 elPurchaseCode.classList.add( 'is-error' );
249 elPurchaseCode.setAttribute( 'aria-invalid', 'true' );
250 }
251 }
252
253 //this.filterAddons();
254 },
255 error: ( error ) => {
256 releaseHandling();
257 lpToastify.show( `error js: ${ error }`, 'error' );
258 },
259 } );
260 }
261
262 /**
263 * Filter add-ons by status, category, and search query.
264 *
265 * @return void
266 */
267 filterAddons() {
268 const selectors = AdminAddons.selectors;
269 const elAddonItems = this.elAddonsPage.querySelectorAll(
270 selectors.elAddonItem
271 );
272
273 // Count items for each tab
274 this.updateTabCounts( elAddonItems );
275 const elActiveTab = this.elAddonsPage.querySelector(
276 selectors.elNavTabActive
277 );
278 const elActiveCategory = this.elAddonsPage.querySelector(
279 selectors.elCategoryActive
280 );
281 const elSearch = this.elAddonsPage.querySelector(
282 selectors.elSearchInput
283 );
284 const tabName = elActiveTab ? elActiveTab.dataset.tab : 'all';
285 const category = elActiveCategory
286 ? elActiveCategory.dataset.category
287 : 'all';
288 const keyword = elSearch ? elSearch.value.trim().toLowerCase() : '';
289 const searchTerms = keyword.split( /\s+/ ).filter( Boolean );
290
291 // Count items for category by tab
292 this.updateCategoryCounts( elAddonItems, tabName );
293
294 elAddonItems.forEach( ( elAddonItem ) => {
295 const addonInfo = JSON.parse( elAddonItem.dataset.addon );
296
297 const addonName = elAddonItem
298 .querySelector( 'a' )
299 .textContent.toLowerCase();
300 const textCompare = addonName + addonInfo.description;
301
302 const addonCategories = ( elAddonItem.dataset.category || '' )
303 .split( /\s+/ )
304 .filter( Boolean );
305 const matchesTab =
306 'all' === tabName || elAddonItem.classList.contains( tabName );
307 const matchesCategory =
308 'all' === category || addonCategories.includes( category );
309 const matchesSearch = searchTerms.every( ( term ) => textCompare.includes( term ) );
310 const isVisible = matchesTab && matchesCategory && matchesSearch;
311
312 elAddonItem.classList.toggle( 'hide', ! matchesTab );
313 elAddonItem.classList.toggle(
314 'search-not-found',
315 ! isVisible
316 );
317 } );
318 }
319
320 updateCategoryCounts( elAddonItems, tabName ) {
321 const categoryCounts = { all: 0 };
322
323 elAddonItems.forEach( ( elAddonItem ) => {
324 const matchesTab =
325 'all' === tabName || elAddonItem.classList.contains( tabName );
326
327 if ( ! matchesTab ) {
328 return;
329 }
330
331 categoryCounts.all++;
332 ( elAddonItem.dataset.category || '' )
333 .split( /\s+/ )
334 .filter( Boolean )
335 .forEach( ( addonCategory ) => {
336 categoryCounts[ addonCategory ] =
337 ( categoryCounts[ addonCategory ] || 0 ) + 1;
338 } );
339 } );
340
341 this.elAddonsPage
342 .querySelectorAll( AdminAddons.selectors.elCategory )
343 .forEach( ( elCategory ) => {
344 const count =
345 categoryCounts[ elCategory.dataset.category ] || 0;
346 const elCount = elCategory.querySelector(
347 '.lp-addons-category__count'
348 );
349
350 if ( elCount ) {
351 elCount.textContent = `(${ count })`;
352 }
353
354 elCategory.hidden =
355 'all' !== elCategory.dataset.category && 0 === count;
356 } );
357 }
358
359 updateTabCounts( elAddonItems ) {
360 this.elAddonsPage
361 .querySelectorAll( AdminAddons.selectors.elNavTab )
362 .forEach( ( elNavTab ) => {
363 const tabName = elNavTab.dataset.tab;
364 const count = Array.from( elAddonItems ).filter(
365 ( elAddonItem ) =>
366 'all' === tabName ||
367 elAddonItem.classList.contains( tabName )
368 ).length;
369 const elCount = elNavTab.querySelector( 'span' );
370
371 if ( elCount ) {
372 elCount.textContent = count;
373 }
374 } );
375 }
376
377 /**
378 * Handle addon action button click.
379 *
380 * @param {Object} args Click event arguments.
381 * @return void
382 */
383 handleBtnAction( args ) {
384 const { e, target } = args;
385 const selectors = AdminAddons.selectors;
386 const el = target.closest( selectors.elBtnAction );
387
388 // Ignore button type link
389 if ( 'A' === el.tagName ) {
390 return;
391 }
392
393 e.preventDefault();
394 lpUtils.lpSetLoadingEl( el, 1 );
395
396 let purchaseCode = '';
397 const elAddonItem = el.closest( selectors.elAddonItem );
398 const addon = this.getAddonData( elAddonItem );
399 const action = el.dataset.action;
400 const elItemPurchase = elAddonItem.querySelector(
401 selectors.elItemPurchase
402 );
403
404 if ( ! addon ) {
405 lpUtils.lpSetLoadingEl( el, 0 );
406 return;
407 }
408
409 if ( action === 'purchase' || action === 'update-purchase-code' ) {
410 const elPurchaseInstall = elItemPurchase.querySelector(
411 selectors.elPurchaseInstall
412 );
413 const elPurchaseCode = elPurchaseInstall.querySelector(
414 selectors.elPurchaseCode
415 );
416 const elPurchaseSubmit = elPurchaseInstall.querySelector(
417 '.lp-addon-purchase__submit'
418 );
419 const elPurchaseClear = elPurchaseInstall.querySelector(
420 '.lp-addon-purchase__clear'
421 );
422 const elPurchaseCancelClear = elPurchaseInstall.querySelector(
423 '.lp-addon-purchase__cancel-clear'
424 );
425 let hasPurchaseCode = false;
426
427 if ( action === 'update-purchase-code' ) {
428 const elLicense = elAddonItem.querySelector( selectors.elLicense );
429 elPurchaseCode.value = elLicense
430 ? elLicense.dataset.purchaseCodeMasked || ''
431 : '';
432 hasPurchaseCode = '' !== elPurchaseCode.value;
433 elPurchaseSubmit.dataset.action = 'update-purchase';
434 } else {
435 elPurchaseCode.value = '';
436 elPurchaseSubmit.dataset.action = 'install';
437 }
438
439 elPurchaseCode.disabled = hasPurchaseCode;
440 elPurchaseSubmit.classList.toggle( 'lp-hidden', hasPurchaseCode );
441 elPurchaseClear.classList.toggle( 'lp-hidden', ! hasPurchaseCode );
442 elPurchaseCancelClear.classList.add( 'lp-hidden' );
443 elPurchaseCode.classList.remove( 'is-error' );
444 elPurchaseCode.removeAttribute( 'aria-invalid' );
445 elItemPurchase.querySelector( 'input[name=purchase-code]' ).value =
446 '';
447 elPurchaseInstall.style.display = 'flex';
448 elItemPurchase.style.display = 'block';
449 if ( ! hasPurchaseCode ) {
450 elPurchaseCode.focus();
451 }
452 lpUtils.lpSetLoadingEl( el, 0 );
453 return;
454 } else if ( action === 'clear-license' ) {
455 const elPurchaseCode = elItemPurchase.querySelector(
456 selectors.elPurchaseCode
457 );
458 const elPurchaseSubmit = elItemPurchase.querySelector(
459 '.lp-addon-purchase__submit'
460 );
461 const elPurchaseClear = elItemPurchase.querySelector(
462 '.lp-addon-purchase__clear'
463 );
464 const elPurchaseCancelClear = elItemPurchase.querySelector(
465 '.lp-addon-purchase__cancel-clear'
466 );
467
468 elPurchaseCode.value = '';
469 elPurchaseCode.disabled = false;
470 elPurchaseCode.classList.remove( 'is-error' );
471 elPurchaseCode.removeAttribute( 'aria-invalid' );
472 elItemPurchase.querySelector( 'input[name=purchase-code]' ).value =
473 '';
474 elPurchaseSubmit.classList.remove( 'lp-hidden' );
475 elPurchaseClear.classList.add( 'lp-hidden' );
476 elPurchaseCancelClear.classList.remove( 'lp-hidden' );
477 elPurchaseCode.focus();
478 lpUtils.lpSetLoadingEl( el, 0 );
479 return;
480 } else if ( action === 'cancel-clear-license' ) {
481 const elLicense = elAddonItem.querySelector( selectors.elLicense );
482 const elPurchaseCode = elItemPurchase.querySelector(
483 selectors.elPurchaseCode
484 );
485 const elPurchaseSubmit = elItemPurchase.querySelector(
486 '.lp-addon-purchase__submit'
487 );
488 const elPurchaseClear = elItemPurchase.querySelector(
489 '.lp-addon-purchase__clear'
490 );
491
492 elPurchaseCode.value = elLicense
493 ? elLicense.dataset.purchaseCodeMasked || ''
494 : '';
495 elPurchaseCode.disabled = true;
496 elPurchaseCode.classList.remove( 'is-error' );
497 elPurchaseCode.removeAttribute( 'aria-invalid' );
498 elItemPurchase.querySelector( 'input[name=purchase-code]' ).value =
499 '';
500 elPurchaseSubmit.classList.add( 'lp-hidden' );
501 elPurchaseClear.classList.remove( 'lp-hidden' );
502 el.classList.add( 'lp-hidden' );
503 lpUtils.lpSetLoadingEl( el, 0 );
504 return;
505 } else if ( action === 'cancel' ) {
506 elItemPurchase.style.display = 'none';
507 elItemPurchase.querySelector(
508 selectors.elPurchaseInstall
509 ).style.display = 'none';
510 lpUtils.lpSetLoadingEl( el, 0 );
511 return;
512 }
513
514 // Send request to server.
515 if ( elItemPurchase ) {
516 purchaseCode = elItemPurchase.querySelector(
517 'input[name=purchase-code]'
518 ).value;
519 }
520
521 this.addonsAction( {
522 purchase_code: purchaseCode,
523 action,
524 addon,
525 el,
526 elAddonItem,
527 } );
528 }
529
530 /**
531 * Handle tab click.
532 *
533 * @param {Object} args Click event arguments.
534 * @return void
535 */
536 handleTabClick( args ) {
537 const { e, target } = args;
538 const selectors = AdminAddons.selectors;
539 const el = target.closest( selectors.elNavTab );
540 e.preventDefault();
541
542 const elTabs = this.elAddonsPage.querySelectorAll( selectors.elNavTab );
543 elTabs.forEach( ( elTab ) => {
544 elTab.classList.remove( 'nav-tab-active' );
545 elTab.setAttribute( 'aria-pressed', 'false' );
546 } );
547 el.classList.add( 'nav-tab-active' );
548 el.setAttribute( 'aria-pressed', 'true' );
549 if ( el.scrollIntoView ) {
550 el.scrollIntoView( { inline: 'center', block: 'nearest', behavior: 'smooth' } );
551 }
552
553 const tabName = el.dataset.tab;
554 const elSearch = this.elAddonsPage.querySelector(
555 selectors.elSearchInput
556 );
557 if ( elSearch ) {
558 elSearch.value = '';
559 }
560 const elSearchContainer = this.elAddonsPage.querySelector(
561 selectors.elSearch
562 );
563 if ( elSearchContainer ) {
564 elSearchContainer.classList.remove( 'is-open' );
565 const btn = elSearchContainer.querySelector(
566 selectors.elSearchBtn
567 );
568 if ( btn ) {
569 btn.setAttribute( 'aria-expanded', 'false' );
570 }
571 }
572 this.elAddonsPage
573 .querySelectorAll( selectors.elCategory )
574 .forEach( ( elCategory ) => {
575 const isActive = 'all' === elCategory.dataset.category;
576 elCategory.classList.toggle( 'active', isActive );
577 elCategory.setAttribute(
578 'aria-pressed',
579 isActive ? 'true' : 'false'
580 );
581 } );
582
583 this.urlParams.set( 'tab', tabName );
584 window.history.pushState(
585 {},
586 '',
587 `${ window.location.pathname }?${ this.urlParams.toString() }`
588 );
589 this.updateFilterToggleState();
590 this.filterAddons();
591 }
592
593 /**
594 * Handle filter toggle button click.
595 *
596 * @param {Object} args Click event arguments.
597 * @return void
598 */
599 handleFilterToggleClick( args ) {
600 const { e } = args;
601 e.preventDefault();
602 e.stopPropagation();
603 this.toggleFilterMenu();
604 }
605
606 /**
607 * Toggle or set filter menu dropdown open state.
608 *
609 * @param {boolean|undefined} open Optional boolean to force state.
610 * @return void
611 */
612 toggleFilterMenu( open ) {
613 const selectors = AdminAddons.selectors;
614 const elToolbar = this.elAddonsPage.querySelector( selectors.elToolbar );
615 const elFilter = this.elAddonsPage.querySelector( selectors.elFilter );
616 if ( ! elToolbar && ! elFilter ) {
617 return;
618 }
619 const toggleBtn = this.elAddonsPage.querySelector(
620 selectors.elFilterToggle
621 );
622 const shouldOpen =
623 undefined !== open
624 ? open
625 : elToolbar
626 ? ! elToolbar.classList.contains( 'is-filter-open' )
627 : ! elFilter.classList.contains( 'is-open' );
628
629 if ( elToolbar ) {
630 elToolbar.classList.toggle( 'is-filter-open', shouldOpen );
631 }
632 if ( elFilter ) {
633 elFilter.classList.toggle( 'is-open', shouldOpen );
634 }
635 if ( toggleBtn ) {
636 toggleBtn.setAttribute(
637 'aria-expanded',
638 shouldOpen ? 'true' : 'false'
639 );
640 }
641 }
642
643 /**
644 * Update filter toggle button has-filter state.
645 *
646 * @return void
647 */
648 updateFilterToggleState() {
649 const selectors = AdminAddons.selectors;
650 const toggleBtn = this.elAddonsPage.querySelector(
651 selectors.elFilterToggle
652 );
653 if ( ! toggleBtn ) {
654 return;
655 }
656 const elActiveTab = this.elAddonsPage.querySelector(
657 selectors.elNavTabActive
658 );
659 const elActiveCategory = this.elAddonsPage.querySelector(
660 selectors.elCategoryActive
661 );
662 const tabName = elActiveTab ? elActiveTab.dataset.tab : 'all';
663 const category = elActiveCategory
664 ? elActiveCategory.dataset.category
665 : 'all';
666 const hasFilter = 'all' !== tabName || 'all' !== category;
667 toggleBtn.classList.toggle( 'has-filter', hasFilter );
668 }
669
670 /**
671 * Handle category click.
672 *
673 * @param {Object} args Click event arguments.
674 * @return void
675 */
676 handleCategoryClick( args ) {
677 const { e, target } = args;
678 const elCategory = target.closest(
679 AdminAddons.selectors.elCategory
680 );
681 e.preventDefault();
682
683 this.elAddonsPage
684 .querySelectorAll( AdminAddons.selectors.elCategory )
685 .forEach( ( item ) => {
686 const isActive = item === elCategory;
687 item.classList.toggle( 'active', isActive );
688 item.setAttribute(
689 'aria-pressed',
690 isActive ? 'true' : 'false'
691 );
692 } );
693 this.updateFilterToggleState();
694 this.filterAddons();
695 }
696
697 /**
698 * Handle search input.
699 *
700 * @return void
701 */
702 handleSearchInput() {
703 this.updateSearchClearBtnState();
704 this.filterAddons();
705 }
706
707 /**
708 * Handle search button click to open search overlay.
709 *
710 * @param {Object} args Click event arguments.
711 * @return void
712 */
713 handleSearchBtnClick( args ) {
714 const { e } = args;
715 e.preventDefault();
716 this.toggleFilterMenu( false );
717 const selectors = AdminAddons.selectors;
718 const elSearch = this.elAddonsPage.querySelector(
719 selectors.elSearch
720 );
721 const elSearchInput = this.elAddonsPage.querySelector(
722 selectors.elSearchInput
723 );
724 if ( elSearch ) {
725 elSearch.classList.add( 'is-open' );
726 const btn = elSearch.querySelector( selectors.elSearchBtn );
727 if ( btn ) {
728 btn.setAttribute( 'aria-expanded', 'true' );
729 }
730 }
731 if ( elSearchInput ) {
732 this.updateSearchClearBtnState();
733 elSearchInput.focus();
734 }
735 }
736
737 /**
738 * Handle search clear button click.
739 *
740 * @param {Object} args Click event arguments.
741 * @return void
742 */
743 handleSearchClearClick( args ) {
744 const { e } = args;
745 e.preventDefault();
746 const selectors = AdminAddons.selectors;
747 const elSearchInput = this.elAddonsPage.querySelector(
748 selectors.elSearchInput
749 );
750 if ( elSearchInput ) {
751 elSearchInput.value = '';
752 this.updateSearchClearBtnState();
753 this.filterAddons();
754 elSearchInput.focus();
755 }
756 }
757
758 /**
759 * Update disabled state of clear search button.
760 *
761 * @return void
762 */
763 updateSearchClearBtnState() {
764 const selectors = AdminAddons.selectors;
765 const elSearchInput = this.elAddonsPage.querySelector(
766 selectors.elSearchInput
767 );
768 const elClearBtn = this.elAddonsPage.querySelector(
769 selectors.elSearchBtnClear
770 );
771 if ( elClearBtn && elSearchInput ) {
772 elClearBtn.disabled = ! elSearchInput.value.trim();
773 }
774 }
775
776 /**
777 * Handle search close button click.
778 *
779 * @param {Object} args Click event arguments.
780 * @return void
781 */
782 handleSearchCloseClick( args ) {
783 const { e } = args;
784 e.preventDefault();
785 this.closeSearch();
786 }
787
788 /**
789 * Handle search input keydown.
790 *
791 * @param {Object} args Keydown event arguments.
792 * @return void
793 */
794 handleSearchKeydown( args ) {
795 const { e } = args;
796 if ( 'Escape' === e.key ) {
797 e.preventDefault();
798 this.closeSearch();
799 }
800 }
801
802 /**
803 * Close search overlay and reset query if needed.
804 *
805 * @return void
806 */
807 closeSearch() {
808 const selectors = AdminAddons.selectors;
809 const elSearch = this.elAddonsPage.querySelector(
810 selectors.elSearch
811 );
812 const elSearchInput = this.elAddonsPage.querySelector(
813 selectors.elSearchInput
814 );
815 if ( elSearch ) {
816 elSearch.classList.remove( 'is-open' );
817 const btn = elSearch.querySelector( selectors.elSearchBtn );
818 if ( btn ) {
819 btn.setAttribute( 'aria-expanded', 'false' );
820 btn.focus();
821 }
822 }
823 if ( elSearchInput && elSearchInput.value ) {
824 elSearchInput.value = '';
825 this.filterAddons();
826 }
827 this.updateSearchClearBtnState();
828 }
829
830 /**
831 * Handle purchase code input.
832 *
833 * @param {Object} args Input event arguments.
834 * @return void
835 */
836 handlePurchaseCodeInput( args ) {
837 const { e, target: el } = args;
838 e.preventDefault();
839 el.classList.remove( 'is-error' );
840 el.removeAttribute( 'aria-invalid' );
841 const purchaseCode = el.value;
842 const elItemPurchase = el.closest(
843 AdminAddons.selectors.elItemPurchase
844 );
845
846 if ( elItemPurchase ) {
847 const input = elItemPurchase.querySelector(
848 'input[name=purchase-code]'
849 );
850 input.value = purchaseCode;
851 }
852 }
853 }
854
855 const adminAddons = new AdminAddons();
856 adminAddons.init();
857
858 export default AdminAddons;
859