PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
1.8.13.1 1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 All 212 releases
charitable / assets / js / admin / charitable-admin-addon-directory.js

charitable-admin-addon-directory.js in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.13, at assets/js/admin/charitable-admin-addon-directory.js

572 lines 16.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* global charitable_admin, jconfirm, wpCookies, Choices, List */
2
3 ;( function( $ ) {
4
5 'use strict';
6
7 // Global settings access.
8 var s;
9
10 // Admin object.
11 var CharitableDirAdmin = {
12
13 // Settings.
14 settings: {
15 iconActivate: '', // '<i class="fa fa-toggle-on fa-flip-horizontal" aria-hidden="true"></i>',
16 iconDeactivate: '', // '<i class="fa fa-toggle-on" aria-hidden="true"></i>',
17 iconInstall: '', // '<i class="fa fa-cloud-download" aria-hidden="true"></i>',
18 iconSpinner: 'Standby...', // '<i class="fa fa-spinner fa-spin" aria-hidden="true"></i>',
19 mediaFrame: false,
20 },
21
22 /**
23 * Start the engine.
24 *
25 * @since 1.3.9
26 */
27 init: function() {
28
29 // Settings shortcut.
30 s = this.settings;
31
32 // Document ready.
33 $( CharitableDirAdmin.ready );
34
35 // Addons List.
36 $( document ).on( 'CharitableDirReady', CharitableDirAdmin.initAddons );
37 },
38
39 /**
40 * Document ready.
41 *
42 * @since 1.3.9
43 */
44 ready: function() {
45
46 // Action available for each binding.
47 $( document ).trigger( 'CharitableDirReady' );
48
49 // if there is a value already in the input text box search, then simulate a click to filter the addon list.
50 if ( $( '#charitable-admin-addons-search' ).val() ) {
51 $( '#charitable-admin-addons-search' ).trigger( 'keyup' );
52 }
53 },
54
55 //--------------------------------------------------------------------//
56 // Addons List.
57 //--------------------------------------------------------------------//
58
59 /**
60 * Element bindings for Addons List page.
61 *
62 * @since 1.3.9
63 */
64 initAddons: function() {
65
66 // Only run on the addons page.
67 if ( ! $( '#charitable-admin-addons' ).length ) {
68 return;
69 }
70
71 // Addon page collapsible sections.
72 function setup_addon_sections() {
73 function setCookie(name, value, days) {
74 var expires = "";
75 if (days) {
76 var date = new Date();
77 date.setTime(date.getTime() + (days*24*60*60*1000));
78 expires = "; expires=" + date.toUTCString();
79 }
80 document.cookie = name + "=" + (value || "") + expires + "; path=/";
81 }
82
83 function getCookie(name) {
84 var nameEQ = name + "=";
85 var ca = document.cookie.split(';');
86 for(var i=0;i < ca.length;i++) {
87 var c = ca[i];
88 while (c.charAt(0)==' ') c = c.substring(1,c.length);
89 if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
90 }
91 return null;
92 }
93
94 var $collapsible_headers = $('.charitable-addons-section-header:not(.charitable-addons-section-header-featured)');
95 var open_sections = getCookie('charitable_open_addon_sections');
96 open_sections = open_sections ? JSON.parse(open_sections) : [];
97
98 // Make sure 'featured' is not in the cookie for some legacy reason.
99 var featured_index = open_sections.indexOf('featured');
100 if (featured_index > -1) {
101 open_sections.splice(featured_index, 1);
102 setCookie('charitable_open_addon_sections', JSON.stringify(open_sections), 7);
103 }
104
105 $collapsible_headers.each(function() {
106 var section_id = $(this).data('section');
107 if (open_sections.includes(section_id)) {
108 $(this).addClass('open');
109 $('#charitable-addons-' + section_id).show();
110 }
111 });
112
113 $collapsible_headers.on('click', function() {
114 var section_id = $(this).data('section');
115 var content = $('#charitable-addons-' + section_id);
116 var open_sections = getCookie('charitable_open_addon_sections');
117 open_sections = open_sections ? JSON.parse(open_sections) : [];
118
119 $(this).toggleClass('open');
120 content.slideToggle();
121
122 if ($(this).hasClass('open')) {
123 if (!open_sections.includes(section_id)) {
124 open_sections.push(section_id);
125 }
126 } else {
127 var index = open_sections.indexOf(section_id);
128 if (index > -1) {
129 open_sections.splice(index, 1);
130 }
131 }
132 setCookie('charitable_open_addon_sections', JSON.stringify(open_sections), 7);
133 });
134 }
135
136 setup_addon_sections();
137
138 // Addons searching.
139 if ( $( '#charitable-addons' ).length ) {
140 $( '#charitable-admin-addons-search' ).on(
141 'keyup search input',
142 function() {
143 CharitableDirAdmin.updateAddonSearchResult( this );
144 }
145 );
146
147 // Store search term in session storage for persistence
148 $( '#charitable-admin-addons-search' ).on(
149 'input',
150 function() {
151 var searchTerm = $( this ).val();
152 if ( searchTerm ) {
153 sessionStorage.setItem( 'charitable_addons_search', searchTerm );
154 } else {
155 sessionStorage.removeItem( 'charitable_addons_search' );
156 }
157 }
158 );
159
160 // Restore search term on page load
161 var savedSearch = sessionStorage.getItem( 'charitable_addons_search' );
162 if ( savedSearch ) {
163 $( '#charitable-admin-addons-search' ).val( savedSearch ).trigger( 'keyup' );
164 }
165 }
166
167 // Toggle an addon state.
168 $( document ).on( 'click', '#charitable-admin-addons .charitable-addons-list-item button', function( event ) {
169
170 event.preventDefault();
171
172 if ( $( this ).hasClass( 'disabledd6' ) ) {
173 return false;
174 }
175
176 CharitableDirAdmin.addonToggle( $( this ) );
177 } );
178 },
179
180 /**
181 * Handle addons search field operations.
182 *
183 * @since 1.7.4
184 *
185 * @param {object} searchField The search field html element.
186 * @param {object} addonSearch Addons list (uses List.js).
187 */
188 updateAddonSearchResult: function( searchField ) {
189
190 var searchTerm = $( searchField ).val();
191
192 /*
193 * Replace dot and comma with space
194 * it is workaround for a bug in listjs library.
195 *
196 * Note: remove when the issue below is fixed:
197 * @see https://github.com/javve/list.js/issues/699
198 */
199 searchTerm = searchTerm.replace( /[.,]/g, ' ' );
200
201 // Custom search implementation for the current HTML structure
202 CharitableDirAdmin.performCustomSearch( searchTerm );
203
204 // Add highlighting to search results
205 CharitableDirAdmin.highlightSearchTerms( searchTerm );
206 },
207
208 /**
209 * Perform custom search across all addon items.
210 *
211 * @since 1.7.4
212 *
213 * @param {string} searchTerm The search term to filter by.
214 */
215 performCustomSearch: function( searchTerm ) {
216
217 var $allAddonItems = $( '#charitable-addons .charitable-addons-list-item' );
218 var searchWords = searchTerm.toLowerCase().split( ' ' ).filter( function( word ) {
219 return word.length > 0;
220 } );
221
222 if ( searchWords.length === 0 ) {
223 // Show all items when search is empty
224 $allAddonItems.show();
225 $( '.charitable-addons-section' ).show();
226 return;
227 }
228
229 var hasVisibleItems = false;
230
231 $allAddonItems.each( function() {
232 var $item = $( this );
233 var $title = $item.find( '.addon-link' );
234 var $description = $item.find( '.addon-description' );
235
236 var titleText = $title.text().toLowerCase();
237 var descriptionText = $description.text().toLowerCase();
238
239 var matches = false;
240
241 // Check if any search word matches title or description
242 searchWords.forEach( function( word ) {
243 if ( titleText.indexOf( word ) !== -1 || descriptionText.indexOf( word ) !== -1 ) {
244 matches = true;
245 }
246 } );
247
248 if ( matches ) {
249 $item.show();
250 hasVisibleItems = true;
251 } else {
252 $item.hide();
253 }
254 } );
255
256 // Show/hide sections based on whether they have visible items
257 $( '.charitable-addons-section' ).each( function() {
258 var $section = $( this );
259 var $visibleItems = $section.find( '.charitable-addons-list-item:visible' );
260
261 if ( $visibleItems.length > 0 ) {
262 $section.show();
263 // Ensure the section content is visible
264 $section.find( '.charitable-addons-section-content' ).show();
265 $section.find( '.charitable-addons-section-header' ).addClass( 'open' );
266 } else {
267 $section.hide();
268 }
269 } );
270 },
271
272 /**
273 * Highlight search terms in the results.
274 *
275 * @since 1.7.4
276 *
277 * @param {string} searchTerm The search term to highlight.
278 */
279 highlightSearchTerms: function( searchTerm ) {
280
281 if ( ! searchTerm ) {
282 // Remove all highlighting when search is cleared
283 $( '.addon-link, .addon-description' ).each( function() {
284 var $element = $( this );
285 $element.html( $element.text() );
286 } );
287 return;
288 }
289
290 // Split search term into words for highlighting
291 var searchWords = searchTerm.toLowerCase().split( ' ' ).filter( function( word ) {
292 return word.length > 0;
293 } );
294
295 if ( searchWords.length === 0 ) {
296 return;
297 }
298
299 // Only highlight visible items
300 $( '.charitable-addons-list-item:visible .addon-link, .charitable-addons-list-item:visible .addon-description' ).each( function() {
301 var $element = $( this );
302 var originalText = $element.text();
303 var highlightedText = originalText;
304
305 // Highlight each search word
306 searchWords.forEach( function( word ) {
307 if ( word.length > 0 ) {
308 var regex = new RegExp( '(' + word.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ) + ')', 'gi' );
309 highlightedText = highlightedText.replace( regex, '<mark>$1</mark>' );
310 }
311 } );
312
313 // Update the element with highlighted text
314 if ( highlightedText !== originalText ) {
315 $element.html( highlightedText );
316 }
317 } );
318 },
319
320 /**
321 * Change plugin/addon state.
322 *
323 * @since 1.6.3
324 *
325 * @param {string} plugin Plugin slug or URL for download.
326 * @param {string} state State status activate|deactivate|install.
327 * @param {string} pluginType Plugin type addon or plugin.
328 * @param {Function} callback Callback for get result from AJAX.
329 */
330 setAddonState: function( plugin, state, pluginType, callback ) {
331
332 var actions = {
333 'activate': 'charitable_activate_addon',
334 'install': 'charitable_install_addon',
335 'deactivate': 'charitable_deactivate_addon',
336 },
337 action = actions[ state ];
338
339 if ( ! action ) {
340 return;
341 }
342
343 var data = {
344 action: action,
345 nonce: charitable_admin.nonce,
346 plugin: plugin,
347 type: pluginType,
348 };
349
350 $.post( charitable_admin.ajax_url, data, function( res ) {
351
352 callback( res );
353 } ).fail( function( xhr ) {
354
355 console.log( xhr.responseText );
356 } );
357 },
358
359 /**
360 * Toggle addon state.
361 *
362 * @since 1.3.9
363 */
364 addonToggle: function( $btn ) {
365
366 // Check if button is disabled or has invalid plugin data
367 if ( $btn.prop( 'disabled' ) || $btn.attr( 'data-plugin' ) === 'invalid' ) {
368 return;
369 }
370
371 var $addon = $btn.closest( '.addon-item' ),
372 plugin = $btn.attr( 'data-plugin' ),
373 pluginType = $btn.attr( 'data-type' ),
374 state,
375 cssClass,
376 stateText,
377 buttonText,
378 errorText,
379 successText;
380
381
382
383 if ( $btn.hasClass( 'status-go-to-url' ) ) {
384
385 // Open url in new tab.
386 window.open( $btn.attr( 'data-plugin' ), '_blank' );
387 return;
388 }
389
390 $btn.prop( 'disabled', true ).addClass( 'loading' );
391 $btn.html( s.iconSpinner );
392
393 if ( $btn.hasClass( 'status-active' ) ) {
394
395 // Deactivate.
396 state = 'deactivate';
397 cssClass = 'status-installed';
398 if ( pluginType === 'plugin' || pluginType === 'addon' ) {
399 cssClass += ' button button-secondary';
400 }
401 stateText = charitable_admin.addon_inactive;
402 buttonText = charitable_admin.addon_activate;
403 errorText = charitable_admin.addon_deactivate;
404 if ( pluginType === 'addon' ) {
405 buttonText = s.iconActivate + buttonText;
406 errorText = s.iconDeactivate + errorText;
407 }
408
409 } else if ( $btn.hasClass( 'status-installed' ) ) {
410
411 // Activate.
412 state = 'activate';
413 cssClass = 'status-active';
414 if ( pluginType === 'plugin' || pluginType === 'addon' ) {
415 cssClass += ' button';
416 }
417 stateText = charitable_admin.addon_active;
418 buttonText = charitable_admin.addon_deactivate;
419 if ( pluginType === 'addon' ) {
420 buttonText = s.iconDeactivate + buttonText;
421 errorText = s.iconActivate + charitable_admin.addon_activate;
422 } else if ( pluginType === 'plugin' ) {
423 buttonText = charitable_admin.addon_activated;
424 errorText = charitable_admin.addon_activate;
425 }
426
427 } else if ( $btn.hasClass( 'status-missing' ) ) {
428
429 // Install & Activate.
430 state = 'install';
431 cssClass = 'status-active';
432 if ( pluginType === 'plugin' || pluginType === 'addon' ) {
433 cssClass += ' button disabled';
434 }
435 stateText = charitable_admin.addon_active;
436 buttonText = charitable_admin.addon_activated;
437 errorText = s.iconInstall;
438 if ( pluginType === 'addon' ) {
439 buttonText = s.iconActivate + charitable_admin.addon_deactivate;
440 errorText += charitable_admin.addon_install;
441 }
442
443 } else {
444 return;
445 }
446
447
448
449 // eslint-disable-next-line complexity
450 CharitableDirAdmin.setAddonState( plugin, state, pluginType, function( res ) {
451
452 if ( res.success ) {
453
454 // Server may include a `redirect` URL when the just-completed
455 // action should navigate the admin somewhere new (e.g. installing
456 // Charitable Pro auto-deactivates Lite, so we send the user to the
457 // Charitable dashboard rather than letting them land on the
458 // no-longer-existing Lite Addons screen).
459 if ( res.data && 'object' === typeof res.data && res.data.redirect ) {
460 window.location.href = res.data.redirect;
461 return;
462 }
463
464 if ( 'install' === state ) {
465 $btn.attr( 'data-plugin', res.data.basename );
466 successText = res.data.msg;
467 if ( ! res.data.is_activated ) {
468 stateText = charitable_admin.addon_inactive;
469 buttonText = 'addon' === pluginType ? charitable_admin.addon_activate : s.iconActivate + charitable_admin.addon_activate;
470 cssClass = 'addon' === pluginType ? 'status-installed button button-secondary' : 'status-installed';
471 } else {
472 // Addon was installed and activated, so it should be status-active without disabled
473 cssClass = 'status-active button';
474 stateText = charitable_admin.addon_active;
475 buttonText = charitable_admin.addon_deactivate;
476 }
477 } else {
478 successText = res.data;
479 }
480
481 $addon.find( '.actions' ).append( '<div class="msg success">' + successText + '</div>' );
482 $addon.find( 'span.status-label' )
483 .removeClass( 'status-active status-installed status-missing' )
484 .addClass( cssClass )
485 .removeClass( 'button button-primary button-secondary disabled' )
486 .text( stateText );
487 $btn
488 .removeClass( 'status-active status-installed status-missing' )
489 .removeClass( 'button button-primary button-secondary disabled' )
490 .addClass( cssClass ).html( buttonText );
491 } else {
492 if ( 'object' === typeof res.data ) {
493 if ( pluginType === 'addon' ) {
494 $addon.find( '.actions' ).append( '<div class="msg error"><p>' + charitable_admin.addon_error + '</p></div>' );
495 } else {
496 $addon.find( '.actions' ).append( '<div class="msg error"><p>' + charitable_admin.plugin_error + '</p></div>' );
497 }
498 } else {
499
500 if ( 'string' === typeof res ) {
501 var err_data = JSON.parse( res );
502
503 if ( 'string' === typeof err_data.error ) {
504 $addon.find( '.actions' ).append( '<div class="msg error"><p>' + err_data.error + '</p></div>' );
505 } else {
506 $addon.find( '.actions' ).append( '<div class="msg error"><p>There has been an error.</p></div>' );
507 }
508 }
509 }
510 if ( 'install' === state && 'addon' === pluginType ) {
511 $btn.addClass( 'status-go-to-url' ).removeClass( 'status-missing' );
512 }
513 $btn.html( errorText );
514 }
515
516 $btn.prop( 'disabled', false ).removeClass( 'loading' );
517
518 if ( ! $addon.find( '.actions' ).find( '.msg.error' ).length ) {
519 setTimeout( function() {
520
521 $( '.addon-item .msg' ).remove();
522 }, 3000 );
523 }
524 } );
525 },
526
527 /**
528 * Get query string in a URL.
529 *
530 * @since 1.3.9
531 */
532 getQueryString: function( name ) {
533
534 var match = new RegExp( '[?&]' + name + '=([^&]*)' ).exec( window.location.search );
535 return match && decodeURIComponent( match[1].replace( /\+/g, ' ' ) );
536 },
537
538 /**
539 * Debug output helper.
540 *
541 * @since 1.4.4
542 * @param msg
543 */
544 debug: function( msg ) {
545
546 if ( CharitableDirAdmin.isDebug() ) {
547 if ( typeof msg === 'object' || msg.constructor === Array ) {
548 console.log( 'Charitable Debug:' );
549 console.log( msg );
550 } else {
551 console.log( 'Charitable Debug: ' + msg );
552 }
553 }
554 },
555
556 /**
557 * Is debug mode.
558 *
559 * @since 1.4.4
560 */
561 isDebug: function() {
562
563 return ( window.location.hash && '#charitabledebug' === window.location.hash );
564 },
565 };
566
567 CharitableDirAdmin.init();
568
569 window.CharitableDirAdmin = CharitableDirAdmin;
570
571 } )( jQuery );
572