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
← All changes | assets/src/js/admin/addons.js +393 -27 4.4.9 → 4.4.10 View file →
@@ -18,15 +18,23 @@
18 18 }
19 19
20 20 static selectors = {
21 21 elAddonsPage: '.lp-addons-page',
22 + elToolbar: '.lp-addons-toolbar',
22 23 elLPAddons: '#lp-addons',
23 24 elAddonItem: '.lp-addon-item',
24 25 elBtnAction: '.btn-addon-action',
25 26 elNavTab: '.nav-tab',
26 27 elNavTabActive: '.nav-tab.nav-tab-active',
28 + elFilter: '.lp-addons-filter',
29 + elFilterToggle: '.lp-addons-filter__toggle',
30 + elFilterMenu: '.lp-addons-filter__menu',
27 31 elCategory: '.lp-addons-category',
28 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',
29 37 elSearchInput: '#lp-search-addons__input',
30 38 elItemPurchase: '.lp-addon-item__purchase',
31 39 elPurchaseInstall: '.purchase-install',
32 40 elPurchaseCode: '.enter-purchase-code',
@@ -42,9 +50,57 @@
42 50 if ( ! this.elAddonsPage ) {
43 51 return;
44 52 }
45 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 +
46 100 this.filterAddons();
101 + this.updateSearchClearBtnState();
102 + this.updateFilterToggleState();
47 103 this.events();
48 104 } );
49 105 }
50 106
@@ -70,12 +126,27 @@
70 126 class: this,
71 127 callBack: this.handleCategoryClick.name,
72 128 },
73 129 {
74 - selector: AdminAddons.selectors.elPurchaseCode,
130 + selector: AdminAddons.selectors.elFilterToggle,
75 131 class: this,
76 - callBack: this.handlePurchaseCodeClick.name,
132 + callBack: this.handleFilterToggleClick.name,
77 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 + },
78 149 ] );
79 150 lpUtils.eventHandlers( 'input', [
80 151 {
81 152 selector: AdminAddons.selectors.elSearchInput,
@@ -87,8 +158,35 @@
87 158 class: this,
88 159 callBack: this.handlePurchaseCodeInput.name,
89 160 },
90 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 + } );
91 189 }
92 190
93 191 /**
94 192 * Get addon data embedded on the item element.
@@ -121,10 +219,16 @@
121 219 purchase_code: data.purchase_code || '',
122 220 id_url: 'addon-action',
123 221 };
124 222
223 + const releaseHandling = () => {
224 + lpUtils.lpSetLoadingEl( el, 0 );
225 + };
226 +
125 227 window.lpAJAXG.fetchAJAX( params, {
126 228 success: ( response ) => {
229 + releaseHandling();
230 +
127 231 const { status, message, data: resData } = response;
128 232
129 233 lpToastify.show( message, status );
130 234
@@ -146,12 +250,11 @@
146 250 }
147 251 }
148 252
149 253 //this.filterAddons();
150 - lpUtils.lpSetLoadingEl( el, 0 );
151 254 },
152 255 error: ( error ) => {
153 - lpUtils.lpSetLoadingEl( el, 0 );
256 + releaseHandling();
154 257 lpToastify.show( `error js: ${ error }`, 'error' );
155 258 },
156 259 } );
157 260 }
@@ -271,30 +374,8 @@
271 374 } );
272 375 }
273 376
274 377 /**
275 - * Handle click events.
276 - *
277 - * @param {Object} args Click event arguments.
278 - * @return void
279 - */
280 - handlePurchaseCodeClick( args ) {
281 - const { target: el } = args;
282 -
283 - if ( ! el.value.startsWith( '***' ) ) {
284 - return;
285 - }
286 -
287 - el.value = '';
288 - const elItemPurchase = el.closest(
289 - AdminAddons.selectors.elItemPurchase
290 - );
291 - if ( elItemPurchase ) {
292 - elItemPurchase.querySelector( 'input[name=purchase-code]' ).value = '';
293 - }
294 - }
295 -
296 - /**
297 378 * Handle addon action button click.
298 379 *
299 380 * @param {Object} args Click event arguments.
300 381 * @return void
@@ -334,8 +415,15 @@
334 415 );
335 416 const elPurchaseSubmit = elPurchaseInstall.querySelector(
336 417 '.lp-addon-purchase__submit'
337 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;
338 426
339 427 if ( action === 'update-purchase-code' ) {
340 428 const elLicense = elAddonItem.querySelector( selectors.elLicense );
341 429 elPurchaseCode.value = elLicense
@@ -340,8 +428,9 @@
340 428 const elLicense = elAddonItem.querySelector( selectors.elLicense );
341 429 elPurchaseCode.value = elLicense
342 430 ? elLicense.dataset.purchaseCodeMasked || ''
343 431 : '';
432 + hasPurchaseCode = '' !== elPurchaseCode.value;
344 433 elPurchaseSubmit.dataset.action = 'update-purchase';
345 434 } else {
346 435 elPurchaseCode.value = '';
347 436 elPurchaseSubmit.dataset.action = 'install';
@@ -346,8 +435,12 @@
346 435 elPurchaseCode.value = '';
347 436 elPurchaseSubmit.dataset.action = 'install';
348 437 }
349 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' );
350 443 elPurchaseCode.classList.remove( 'is-error' );
351 444 elPurchaseCode.removeAttribute( 'aria-invalid' );
352 445 elItemPurchase.querySelector( 'input[name=purchase-code]' ).value =
353 446 '';
@@ -352,11 +445,64 @@
352 445 elItemPurchase.querySelector( 'input[name=purchase-code]' ).value =
353 446 '';
354 447 elPurchaseInstall.style.display = 'flex';
355 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' );
356 477 elPurchaseCode.focus();
357 478 lpUtils.lpSetLoadingEl( el, 0 );
358 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;
359 505 } else if ( action === 'cancel' ) {
360 506 elItemPurchase.style.display = 'none';
361 507 elItemPurchase.querySelector(
362 508 selectors.elPurchaseInstall
@@ -399,14 +545,31 @@
399 545 elTab.setAttribute( 'aria-pressed', 'false' );
400 546 } );
401 547 el.classList.add( 'nav-tab-active' );
402 548 el.setAttribute( 'aria-pressed', 'true' );
549 + if ( el.scrollIntoView ) {
550 + el.scrollIntoView( { inline: 'center', block: 'nearest', behavior: 'smooth' } );
551 + }
403 552
404 553 const tabName = el.dataset.tab;
405 554 const elSearch = this.elAddonsPage.querySelector(
406 555 selectors.elSearchInput
407 556 );
408 - elSearch.value = '';
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 + }
409 572 this.elAddonsPage
410 573 .querySelectorAll( selectors.elCategory )
411 574 .forEach( ( elCategory ) => {
412 575 const isActive = 'all' === elCategory.dataset.category;
@@ -422,12 +585,90 @@
422 585 {},
423 586 '',
424 587 `${ window.location.pathname }?${ this.urlParams.toString() }`
425 588 );
589 + this.updateFilterToggleState();
426 590 this.filterAddons();
427 591 }
428 592
429 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 + /**
430 671 * Handle category click.
431 672 *
432 673 * @param {Object} args Click event arguments.
433 674 * @return void
@@ -448,8 +689,9 @@
448 689 'aria-pressed',
449 690 isActive ? 'true' : 'false'
450 691 );
451 692 } );
693 + this.updateFilterToggleState();
452 694 this.filterAddons();
453 695 }
454 696
455 697 /**
@@ -457,9 +699,133 @@
457 699 *
458 700 * @return void
459 701 */
460 702 handleSearchInput() {
703 + this.updateSearchClearBtnState();
461 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();
462 828 }
463 829
464 830 /**
465 831 * Handle purchase code input.