PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.14.0
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.14.0
2.14.0 2.13.0 2.13.1 2.12.0 2.11.1 2.11.0 2.10.0 2.9.0 2.7.4 2.7.5 2.7.6 2.7.7 2.8.0 2.8.1 2.9.1 trunk 1.0 1.0-beta1 1.0-beta2 1.0-beta3 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 All 81 releases
ablocks / vendor / storeengine / wordpress-sdk / static / deactivation-modal.js

deactivation-modal.js in aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder 2.14.0, at vendor/storeengine/wordpress-sdk/static/deactivation-modal.js

365 lines 12.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function($, window) {
2 'use strict';
3
4 if (window.StoreEngineSDKDeactivationModal) {
5 $(window.StoreEngineSDKDeactivationModal.init);
6 return;
7 }
8
9 function preventDefault(event) {
10 if (event) {
11 event.preventDefault();
12 }
13 }
14
15 /**
16 * Put a button into a disabled+spinner state. Returns the original
17 * label so the caller can restore it if the action is reversible.
18 */
19 function setButtonLoading(buttonElem, config) {
20 if (buttonElem.data('se-sdk-original-label') == null) {
21 buttonElem.data('se-sdk-original-label', buttonElem.text());
22 }
23 var label = config.processingLabel || 'Processing...';
24 // `loading` keeps the button's primary colour; `disabled` blocks
25 // further clicks. The CSS targets the combination so the spinner
26 // stays visible against the original background rather than
27 // dropping to the muted disabled style.
28 buttonElem.addClass('disabled loading').prop('disabled', true).html(
29 '<span class="se-sdk-spinner" aria-hidden="true"></span>' +
30 '<span class="se-sdk-spinner-label">' + label + '</span>'
31 );
32 }
33
34 function restoreButton(buttonElem) {
35 var label = buttonElem.data('se-sdk-original-label');
36 if (label != null) {
37 buttonElem.removeClass('disabled loading').prop('disabled', false).text(label);
38 }
39 }
40
41 function ajaxSubmit(data, buttonElem, callback, config) {
42 if (buttonElem.hasClass('disabled') || buttonElem.is(':disabled')) {
43 return;
44 }
45
46 return $.ajax({
47 url: window.ajaxurl,
48 type: 'POST',
49 data: $.extend({}, {
50 action: config.uninstallAction,
51 _wpnonce: config.nonce
52 }, data),
53 beforeSend: function() {
54 setButtonLoading(buttonElem, config);
55 },
56 complete: function(event, xhr, options) {
57 restoreButton(buttonElem);
58 if (typeof callback === 'string') {
59 window.location.href = callback;
60 } else if (typeof callback === 'function') {
61 callback({ event: event, xhr: xhr, options: options });
62 }
63 }
64 });
65 }
66
67 /**
68 * Fire-and-forget feedback delivery via sendBeacon. Used when the user
69 * provides a real reason — we don't need to wait for the server's
70 * acknowledgement before navigating to the WP deactivate URL, because
71 * the browser delivers beacon payloads even after page unload.
72 *
73 * Returns true if the beacon was queued; false if not supported (caller
74 * should fall back to ajaxSubmit).
75 */
76 function sendFeedbackBeacon(data, config) {
77 if (!window.navigator || typeof window.navigator.sendBeacon !== 'function') {
78 return false;
79 }
80
81 var payload = $.extend({}, {
82 action: config.uninstallAction,
83 _wpnonce: config.nonce
84 }, data);
85
86 try {
87 var body = new URLSearchParams();
88 Object.keys(payload).forEach(function (key) {
89 body.append(key, payload[key] == null ? '' : payload[key]);
90 });
91 // admin-ajax expects application/x-www-form-urlencoded; Blob lets
92 // us set the MIME type explicitly because sendBeacon defaults to
93 // text/plain for raw strings, which WP rejects.
94 return window.navigator.sendBeacon(
95 window.ajaxurl,
96 new Blob([body.toString()], { type: 'application/x-www-form-urlencoded' })
97 );
98 } catch (err) {
99 return false;
100 }
101 }
102
103 function initModal(modal) {
104 if (!modal.length || modal.data('seSdkModalInit')) {
105 return;
106 }
107
108 modal.data('seSdkModalInit', true);
109
110 var config = {
111 slug: modal.data('slug'),
112 plugin: modal.data('plugin'),
113 uninstallAction: modal.data('uninstall-action'),
114 supportAction: modal.data('support-action'),
115 nonce: modal.data('nonce'),
116 supportUrl: modal.data('support-url'),
117 processingLabel: modal.data('processing-label') || 'Processing...'
118 };
119
120 var deactivateLink = '';
121 var reason = modal.find('.reason');
122 var support = modal.find('.support');
123 var supportResponse = support.find('.response');
124 var mui = modal.find('.mui input, .mui textarea, .mui select');
125 var responseButtons = modal.find('.reason .se-sdk-deactivation-modal--footer .send-reason');
126 var validMessage = [];
127
128 function resetTicketForm(clearValues, clearAll) {
129 modal.find('p.helper-text.mui-error').remove();
130 modal.find('.mui-error').removeClass('mui-error');
131 if (!clearValues) {
132 return;
133 }
134 if (clearAll) {
135 mui.val('');
136 return;
137 }
138 modal.find('#' + config.slug + '-se-sdk-support--message,#' + config.slug + '-se-sdk-support--subject').val('');
139 }
140
141 function closeModal(event) {
142 preventDefault(event);
143 $('body').removeClass('se-sdk-deactivation-modal-open');
144 modal.removeClass('modal-active');
145 supportResponse.hide().find('.wrapper').html('');
146 support.hide();
147 reason.show(0);
148 modal.find('.button').removeClass('disabled').prop('disabled', false).each(function() {
149 var self = $(this);
150 var label = self.attr('data-label');
151 if (label) {
152 self.text(label);
153 }
154 });
155 modal.find('input[type="radio"]').prop('checked', false).removeClass('selected-reason');
156 // Collapse the "What data do we collect?" disclosure so it starts
157 // hidden again on the next open.
158 modal.find('.se-sdk-consent-details').prop('open', false);
159 modal.find('.reason-input').remove();
160 }
161
162 function checkMessageValidity(event) {
163 var target = event && event.target ? event.target : this;
164 var self = $(this);
165 var currentMui = self.closest('.mui');
166 var label = currentMui.find('label');
167 var control = currentMui.find('.se-sdk-form-control');
168
169 if (target.checkValidity()) {
170 label.removeClass('mui-error');
171 control.removeClass('mui-error');
172 currentMui.find('p.helper-text').hide().remove();
173 validMessage.push(true);
174 return;
175 }
176
177 validMessage.push(false);
178 }
179
180 mui.not('select').not('[type="checkbox"]').not('[type="radio"]').on('focus', function() {
181 var self = $(this);
182 var currentMui = self.closest('.mui');
183 currentMui.find('.se-sdk-form-control').addClass('focused');
184 currentMui.find('label').addClass('focused shrink');
185 }).on('blur', function() {
186 var self = $(this);
187 var currentMui = self.closest('.mui');
188 var label = currentMui.find('label');
189 currentMui.find('.se-sdk-form-control').removeClass('focused');
190 label.removeClass('focused');
191 if (self.val() === '') {
192 label.removeClass('shrink');
193 }
194 });
195
196 mui.on('blur', checkMessageValidity).on('invalid', function(event) {
197 preventDefault(event);
198 var self = $(this);
199 var currentMui = self.closest('.mui');
200 var label = currentMui.find('label');
201 var control = currentMui.find('.se-sdk-form-control');
202 currentMui.find('p.helper-text').remove();
203 label.addClass('mui-error');
204 control.addClass('mui-error');
205 control.after('<p class="helper-text mui-error">' + event.target.validationMessage + '</p>');
206 });
207
208 // Bind to the plugin's "Deactivate" link. Prefer data-plugin (the
209 // plugin file basename) because WordPress always sets it on the row and
210 // it's unique — data-slug is derived from the wp.org slug / plugin name
211 // and won't match when the SDK's configured slug differs (e.g. a "-pro"
212 // product slug on a differently-named plugin folder), which would leave
213 // the modal never opening. data-slug is kept as a fallback.
214 var deactivateRow = config.plugin
215 ? $('tr[data-plugin="' + config.plugin + '"]')
216 : $();
217 if (!deactivateRow.length) {
218 deactivateRow = $('tr[data-slug="' + config.slug + '"]');
219 }
220 deactivateRow.find('.deactivate a').off('click.seSdkModal').on('click.seSdkModal', function(event) {
221 preventDefault(event);
222 $('body').addClass('se-sdk-deactivation-modal-open');
223 modal.addClass('modal-active');
224 deactivateLink = $(this).attr('href');
225 modal.find('a.dont-bother-me').attr('href', deactivateLink).css('float', 'left');
226 });
227
228 modal.on('click', '.not-interested', function(event) {
229 preventDefault(event);
230 $(this).closest('.response').slideUp();
231 responseButtons.removeClass('disabled').prop('disabled', false);
232 }).on('click', '.open-ticket-form', function(event) {
233 preventDefault(event);
234 support.show(0);
235 reason.hide(0);
236 supportResponse.find('.wrapper').html('');
237 supportResponse.hide(0);
238 resetTicketForm(true);
239 }).on('click', '.close-ticket', function(event) {
240 preventDefault(event);
241 support.hide(0);
242 reason.show(0);
243 }).on('click', '.modal-close, .se-sdk-deactivation-modal--close', closeModal).on('click', '.reason-type', function() {
244 var self = $(this);
245 var parent = self.closest('.reason-item');
246 var inputType = parent.data('type');
247 modal.find('.reason-input').slideUp(function() {
248 $(this).remove();
249 });
250 self.closest('.reasons').find('.selected-reason').removeClass('selected-reason');
251 self.addClass('selected-reason');
252
253 // For "found-better" / "other" expose an optional follow-up
254 // input. It's not required — Submit & Deactivate stays enabled
255 // whether or not the user fills it in.
256 if (inputType !== '') {
257 var reasonMessage = $(inputType === 'text' ? '<input type="text" size="40" />' : '<textarea rows="5" cols="45"></textarea>');
258 reasonMessage.attr('placeholder', parent.data('placeholder'));
259 reasonMessage.slideUp(0);
260 $('<div class="reason-input"></div>').append(reasonMessage).appendTo(parent);
261 reasonMessage.slideDown('fast').focus();
262 }
263 }).on('click', '.dont-bother-me', function(event) {
264 preventDefault(event);
265 // User explicitly chose not to share — nothing useful to record.
266 // Show a brief spinner so the click feels acknowledged, then
267 // deactivate immediately (no server round-trip).
268 setButtonLoading($(this), config);
269 window.location.href = deactivateLink;
270 }).on('click', '.send-reason', function(event) {
271 preventDefault(event);
272 if ($(this).hasClass('disabled') || $(this).is(':disabled')) {
273 return;
274 }
275 var radio = modal.find('input.reason-type:checked');
276 var input = radio.length
277 ? radio.closest('.reason-item').find('textarea, input[type="text"]')
278 : $();
279
280 // The modal discloses exactly what is sent (reason plus the listed
281 // site data), and "Skip & Deactivate" is the cancel path that sends
282 // nothing — so submitting here is informed consent.
283 var payload = {
284 reason_id: radio.length ? radio.val() : 'no-comment',
285 reason_info: input.length ? $.trim(input.val()) : ''
286 };
287
288 // Show spinner immediately so the click feels acknowledged,
289 // independent of whether we use beacon or fall back to AJAX.
290 setButtonLoading($(this), config);
291
292 // Try sendBeacon first — browser delivers it even after we
293 // navigate away, so the user doesn't wait for the server.
294 if (sendFeedbackBeacon(payload, config)) {
295 window.location.href = deactivateLink;
296 return;
297 }
298
299 // Fallback: old-school AJAX with loader. Only hit on browsers
300 // without sendBeacon support (≤ IE / very old Safari).
301 ajaxSubmit(payload, $(this), deactivateLink, config);
302 }).on('click', '.send-ticket', function(event) {
303 preventDefault(event);
304 validMessage = [];
305 mui.each(checkMessageValidity);
306 if (!validMessage.every(Boolean)) {
307 return;
308 }
309
310 var buttonElem = $(this);
311 var buttonText = buttonElem.text();
312 var data = {
313 action: config.supportAction
314 };
315
316 mui.each(function() {
317 data[$(this).attr('name')] = $(this).val();
318 });
319
320 ajaxSubmit(data, buttonElem, function(jqXhr) {
321 buttonElem.removeClass('disabled').prop('disabled', false).text(buttonText);
322 if (jqXhr.xhr === 'error') {
323 supportResponse.find('.wrapper').html('<p class="mui-error">Something went wrong. Please refresh or try again.</p>');
324 supportResponse.show();
325 return;
326 }
327
328 var response = jqXhr.event.responseJSON;
329 if (response && Object.prototype.hasOwnProperty.call(response, 'data')) {
330 var message = response.success ? '<p>' + response.data + '</p>' : '<p class="mui-error">' + response.data + '</p>';
331 supportResponse.find('.wrapper').html(message);
332 supportResponse.show();
333 if (response.success) {
334 modal.find('#' + config.slug + '-se-sdk-support--message,#' + config.slug + '-se-sdk-support--subject').val('');
335 }
336 return;
337 }
338
339 if (!config.supportUrl) {
340 return;
341 }
342
343 window.setTimeout(function() {
344 window.open(config.supportUrl, '_blank');
345 if (buttonElem.hasClass('disabled')) {
346 buttonElem.removeClass('disabled').prop('disabled', false);
347 }
348 }, 5000);
349 }, config);
350 });
351 }
352
353 function init() {
354 $('.se-sdk-deactivation-modal').each(function() {
355 initModal($(this));
356 });
357 }
358
359 window.StoreEngineSDKDeactivationModal = {
360 init: init
361 };
362
363 $(init);
364 })(jQuery, window);
365