PluginProbe
Smart Image Resize for WooCommerce / trunk
Smart Image Resize for WooCommerce vtrunk
1.17.1 1.17.0 1.16.0 trunk 1.1.10 1.1.11 1.1.12 1.10.0 1.10.1 1.10.2 1.12.0 1.12.1 1.13.0 1.13.1 1.15.0 1.15.1 1.2.0 1.2.1 1.2.2 1.2.3 1.3 1.3.1 1.3.2 1.3.3 1.3.4 All 52 releases
smart-image-resize / js / scripts.js

scripts.js in Smart Image Resize for WooCommerce trunk, at js/scripts.js

1,188 lines 38.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 // Utils
2
3 var WP_SIR_UTIL = {
4 setCookie: function (cname, cvalue, exdays) {
5 var d = new Date();
6 d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
7 var expires = "expires=" + d.toUTCString();
8 document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
9 }
10 };
11
12 (function ($) {
13 'use strict';
14
15 // ELEMENTS
16 var $colorPicker = $('#wpSirColorPicker');
17 var $compressImageSlider = $('.wpSirSlider');
18
19 // ------------------------------------------------------------------------------------------
20 // INITILIAZE COLOR PICKER
21 // ------------------------------------------------------------------------------------------
22
23 $colorPicker.wpColorPicker();
24
25 // ------------------------------------------------------------------------------------------
26 // INITILIAZE COMPRESSION SLIDER.
27 // ------------------------------------------------------------------------------------------
28
29 $compressImageSlider.each(function () {
30 var handle = $(this).find('.wpSirSliderHandler');
31 var inputElement = $('.' + $(this).data('input'));
32 $(this).slider({
33 create: function () {
34 $(this).slider('value', inputElement.val());
35 handle.text($(this).slider('value') + '%');
36 },
37 slide: function (event, ui) {
38 handle.text(ui.value + '%');
39 inputElement.val(ui.value);
40 },
41 change: function (event, ui) {
42 handle.text(ui.value + '%');
43 },
44 });
45 });
46
47 // we'll wait until the box is rendered, so we can move it to the top.
48 var wpsirLoadIntervalId = setInterval(() => {
49 if ($('.wpsirProcessMediaLibraryImageWraper').length) {
50 clearInterval(wpsirLoadIntervalId);
51
52 $('.wpsirProcessMediaLibraryImageWraper')
53 .insertBefore($('#wp-media-grid > .media-frame'));
54
55 handleProcessMediaLibraryChange($('#processMediaLibraryImage'));
56
57 $(document).on('change', '#processMediaLibraryImage', function () {
58 handleProcessMediaLibraryChange($(this));
59 });
60
61 }
62 }, 100);
63
64
65 /**
66 * Allow user to decide whether to process image being uploaded.
67 * We'll place a checkbox input where we cannot determine image attachment parent
68 * under "Media > Library" and "Media > Add" new pages.
69 */
70
71
72 function handleProcessMediaLibraryChange($input) {
73 var isProcessable = $input.is(':checked');
74
75 WP_SIR_UTIL.setCookie(wp_sir_object.process_ml_upload_cookie, isProcessable.toString(), 365);
76 // Normal HTML uploader.
77 if ($('#html-upload-ui').length) {
78 var $htmlProcessableInput = $('input[name="_processable_image"]');
79
80 if ($htmlProcessableInput.length === 0) {
81 $('#html-upload-ui').append(
82 '<input type="hidden" name="_processable_image" >'
83 );
84 $htmlProcessableInput = $($htmlProcessableInput.selector);
85 }
86 $htmlProcessableInput.val(isProcessable);
87 }
88
89 // Drag-and-drop uploader box.
90 if (
91 typeof wpUploaderInit === 'object' &&
92 wpUploaderInit.hasOwnProperty('multipart_params')
93 ) {
94 wpUploaderInit.multipart_params._processable_image = isProcessable;
95 }
96
97 // Media library modal.
98 if (
99 wp.media &&
100 wp.media.frame &&
101 wp.media.frame.uploader &&
102 wp.media.frame.uploader.uploader
103 ) {
104 wp.media.frame.uploader.uploader.param('_processable_image', isProcessable);
105 }
106 }
107
108
109
110 // Reset "Image sizes" to default ones.
111 $(document).on('click', '#wpsirResetDefaultSizes', function () {
112 var preselectedSizes = $('#wp-sir-sizes-selector').data('defaults').split(',');
113 $('.wpSirSelectSize').each(function () {
114 if (preselectedSizes.indexOf($(this).val()) >= 0) {
115 $(this).prop('checked', true).change();
116 } else {
117 $(this).prop('checked', false).change();
118 }
119 });
120 });
121
122
123 // Add filter to Media Library (grid view)
124
125 if (typeof sir_vars != 'undefined') {
126 var SIR_MediaLibraryTaxonomyFilter = wp.media.view.AttachmentFilters.extend({
127 id: 'media-attachment-sir-filter',
128 createFilters() {
129 this.filters = {
130 all: {
131 text: sir_vars.filter_strings.all,
132 props: { _filter: 'all' },
133 priority: 10,
134 },
135 processed: {
136 text: sir_vars.filter_strings.processed,
137 props: { _filter: 'processed' },
138 priority: 20,
139 },
140 unprocessed: {
141 text: sir_vars.filter_strings.unprocessed,
142 props: { _filter: 'unprocessed' },
143 priority: 30,
144 },
145 };
146 },
147 });
148
149 var SIR_AttachmentsBrowser = wp.media.view.AttachmentsBrowser;
150 wp.media.view.AttachmentsBrowser = wp.media.view.AttachmentsBrowser.extend({
151 createToolbar() {
152 // Make sure to load the original toolbar
153 SIR_AttachmentsBrowser.prototype.createToolbar.call(this);
154 this.toolbar.set(
155 'SIR_MediaLibraryTaxonomyFilter',
156 new SIR_MediaLibraryTaxonomyFilter({
157 controller: this.controller,
158 model: this.collection.props,
159 priority: -75,
160 }).render()
161 );
162 },
163 });
164
165 }
166
167 // Handle the "Clear" button display.
168 $('#wp-sir-clear-bg-color').on('click', function (e) {
169 $colorPicker.find('.wp-picker-clear').click();
170 $colorPicker.val('').trigger('change');
171 $colorPicker.find('.wp-color-result').css({ 'background-color': '' });
172 e.preventDefault();
173 e.stopPropagation();
174 $(this).hide();
175 });
176
177 if (!$colorPicker.val()) {
178 $('#wp-sir-clear-bg-color').hide();
179 }
180
181 $('.wp-picker-container').on('click', function () {
182 if ($(this).hasClass('wp-picker-active')) {
183 $('#wp-sir-clear-bg-color').hide();
184 } else if ($colorPicker.val()) {
185 $('#wp-sir-clear-bg-color').show();
186 }
187 });
188
189
190 $(document).on('click', function (e) {
191 if ($colorPicker.val()) {
192 $('#wp-sir-clear-bg-color').show();
193 }
194 });
195
196
197 $(document).on('change', '.wpSirSelectSize', function () {
198
199 $(this).closest('tr').find('input[type="number"]').prop('disabled', !$(this).is(':checked'));
200 $(this).closest('tr').find('.wp-sir-fit-mode').prop('disabled', !$(this).is(':checked'));
201 if ($(this).closest('tr').find('.wp-sir-fit-mode').is(':checked')) {
202 $(this).closest('tr').find('input[type="number"]').prop('disabled', true);
203 }
204 var isAllSizesSelected = $('.wpSirSelectSize:checked').length === $('.wpSirSelectSize').length;
205 $('#wp-sir-toggle-all-sizes').prop('checked', isAllSizesSelected);
206
207 // Check if current selection matches defaults
208 const defaultSizes = $('#wp-sir-sizes-selector').data('defaults').split(',');
209 const currentSizes = $('.wpSirSelectSize:checked').map(function() {
210 return $(this).val();
211 }).get();
212
213 const hasChanges = defaultSizes.length !== currentSizes.length ||
214 defaultSizes.some(size => !currentSizes.includes(size)) ||
215 currentSizes.some(size => !defaultSizes.includes(size));
216
217 // Show/hide reset button based on changes
218 $('#wpsirResetDefaultSizes').toggle(hasChanges);
219 });
220
221 $('.wpSirSelectSize').each(function () {
222
223 $(this).closest('tr').find('input[type=number]').prop('disabled', !$(this).is(':checked')).change();
224 $(this).closest('tr').find('.wp-sir-fit-mode').prop('disabled', !$(this).is(':checked')).change();
225 $(this).closest('tr').find('.wp-sir-fit-mode').each(function () {
226 if ($(this).is(':checked')) {
227 $(this).closest('tr').find('input[type=number]').prop('disabled', true);
228 }
229 });
230 });
231
232 $('#wp-sir-toggle-all-sizes').on('change', function () {
233 var $toggle = $(this);
234 $('.wpSirSelectSize').each(function(){
235 if(! $(this).is(':disabled')){
236 $(this).prop('checked', $toggle.is(':checked'));
237
238 }
239 });
240 $('.wpSirSelectSize').change();
241 });
242
243 $('.wp-sir-fit-mode').on('change', function () {
244 if ($(this).is(':checked') ) {
245 $(this).closest('tr').find('.wp-sir-custom-dimensions').find('input').prop('disabled', true);
246 } else {
247 $(this).closest('tr').find('.wp-sir-custom-dimensions').find('input').prop('disabled', false);
248 }
249 });
250
251
252 $(document).on('click', '#wp-sir-open-media-uploader', function (e) {
253 var frame;
254
255 if (frame) {
256 frame.open();
257 return;
258 }
259
260 frame = wp.media({
261 title: 'Select or Upload Watermark image',
262 multiple: false,
263 });
264
265 frame.on('select', function () {
266 var attachment = frame.state().get('selection').first().toJSON();
267 var $watermarkImageInput = $('input[name="wp_sir_settings[watermark_image]"]');
268 $watermarkImageInput.val(attachment.id);
269 $watermarkImageInput.data('size', { w: attachment.width, h: attachment.height });
270
271 var $watermarkPreview = $('.wp-sir-watermark-preview-container');
272
273 if ($watermarkPreview.find('img').length) {
274 $watermarkPreview.find('img').attr('src', attachment.url);
275 } else {
276 $watermarkPreview.append('<img src="' + attachment.url + '"/>');
277 $watermarkPreview.find('img').css('position', 'absolute');
278 }
279
280 var previewSize = { w: $watermarkPreview.width(), h: $watermarkPreview.height() };
281 var h,w;
282 var size = +$('.wp-sir-watermark-size').val();
283
284 if (attachment.width >= attachment.height) {
285 w = previewSize.w * size /100;
286 if (w >= previewSize.w) {
287 w = previewSize.w;
288 }
289 h = attachment.height * w / attachment.width;
290 } else {
291 h = previewSize.h * size / 100;
292 if (h >= previewSize.h) {
293 h = previewSize.h
294 }
295 w = attachment.width * h / attachment.height;
296 }
297
298 if(w >= previewSize.w) {
299 w = previewSize.w;
300 h = attachment.height * w / attachment.width;
301 }
302
303 if(h >= previewSize.h) {
304 h = previewSize.h;
305 w = attachment.width * h / attachment.height;
306 }
307
308 $watermarkPreview.find('img').css({ width: w + 'px', height: h + 'px' });
309 $watermarkPreview.find('img').css({ opacity: +$('.wp-sir-watermark-opacity').val()/100 });
310 $('#wp-sir-watermark-position').trigger('change');
311 });
312
313 frame.open();
314
315 });
316
317
318 // Handle watermark slider change.
319 $('.wp-sir-watermark-size').on('input', function() {
320 var $slider = $(this);
321 var $previewImageContainer = $('.wp-sir-watermark-preview-container');
322 var $watermark = $previewImageContainer.find('img');
323 var value = $slider.val();
324
325 // Update the size input value
326
327 if (!$watermark.length) {
328 return;
329 }
330
331 var previewSize = {
332 w: $previewImageContainer.width(),
333 h: $previewImageContainer.height()
334 };
335 var watermarkSize = {
336 w: $watermark.width(),
337 h: $watermark.height()
338 };
339
340 var watermarkNewWidth, watermarkNewHeight;
341
342 if (watermarkSize.w >= watermarkSize.h) {
343 watermarkNewWidth = previewSize.w * value / 100;
344 if (watermarkNewWidth >= previewSize.w) {
345 watermarkNewWidth = previewSize.w;
346 }
347 watermarkNewHeight = watermarkSize.h * watermarkNewWidth / watermarkSize.w;
348 } else {
349 watermarkNewHeight = previewSize.h * value / 100;
350 if (watermarkNewHeight >= previewSize.h) {
351 watermarkNewHeight = previewSize.h;
352 }
353 watermarkNewWidth = watermarkSize.w * watermarkNewHeight / watermarkSize.h;
354 }
355
356 if (watermarkNewWidth >= previewSize.w) {
357 watermarkNewWidth = previewSize.w;
358 watermarkNewHeight = watermarkSize.h * watermarkNewWidth / watermarkSize.w;
359 }
360
361 if (watermarkNewHeight >= previewSize.h) {
362 watermarkNewHeight = previewSize.h;
363 watermarkNewWidth = watermarkSize.w * watermarkNewHeight / watermarkSize.h;
364 }
365
366 $watermark.css({
367 width: watermarkNewWidth + 'px',
368 height: watermarkNewHeight + 'px'
369 });
370
371 });
372
373 $(document).on('change', '#wp-sir-watermark-position', function () {
374 setWatermarkPosition($(this));
375 });
376
377
378 $('.wp-sir-watermark-opacity').on('input', function() {
379 var $slider = $(this);
380 var $opacityInput = $('.' + $slider.data('input'));
381 var value = $slider.val();
382 var $watermark = $('.wp-sir-watermark-preview-container').find('img');
383
384 // Update the opacity input value and display
385 $opacityInput.val(value);
386 $('.wp-sir-watermark-opacity-slider-handler').text(value + '%');
387
388 // Update watermark opacity if it exists
389 if ($watermark.length) {
390 $watermark.css({ opacity: value / 100 });
391 }
392 });
393
394 // Initialize opacity on page load
395 $(document).ready(function() {
396 $('.wp-sir-watermark-opacity').each(function() {
397 $(this).trigger('input');
398 });
399 });
400
401 function setWatermarkPosition($element) {
402 var $img = $('.wp-sir-watermark-preview-container').find('img');
403 var $offset_y = $('#wp-sir-watermark-offset-y');
404 var $offset_x = $('#wp-sir-watermark-offset-x');
405 var offset_x = parseInt($offset_x.val()) || 0;
406 var offset_y = parseInt($offset_y.val()) || 0;
407
408 // Reset any previous positioning
409 $img.css({
410 'top': '',
411 'left': '',
412 'right': '',
413 'bottom': '',
414 'transform': ''
415 });
416
417 // Enable both inputs by default
418 $offset_x.prop('disabled', false);
419 $offset_y.prop('disabled', false);
420
421 switch ($element.val()) {
422 case 'top-left':
423 $img.css({
424 'top': offset_y + 'px',
425 'left': offset_x + 'px'
426 });
427 break;
428 case 'top-right':
429 $img.css({
430 'top': offset_y + 'px',
431 'right': offset_x + 'px'
432 });
433 break;
434 case 'bottom-left':
435 $img.css({
436 'bottom': offset_y + 'px',
437 'left': offset_x + 'px'
438 });
439 break;
440 case 'bottom-right':
441 $img.css({
442 'bottom': offset_y + 'px',
443 'right': offset_x + 'px'
444 });
445 break;
446 case 'center':
447 $img.css({
448 'top': '50%',
449 'left': '50%',
450 'transform': 'translate(-50%, -50%)'
451 });
452 // Disable both offsets for center position
453 $offset_x.prop('disabled', true);
454 $offset_y.prop('disabled', true);
455 break;
456 case 'left':
457 $img.css({
458 'top': '50%',
459 'left': offset_x + 'px',
460 'transform': 'translateY(-50%)'
461 });
462 // Disable Y offset for left position
463 $offset_y.prop('disabled', true);
464 break;
465 case 'right':
466 $img.css({
467 'top': '50%',
468 'right': offset_x + 'px',
469 'transform': 'translateY(-50%)'
470 });
471 // Disable Y offset for right position
472 $offset_y.prop('disabled', true);
473 break;
474 case 'top':
475 $img.css({
476 'top': offset_y + 'px',
477 'left': '50%',
478 'transform': 'translateX(-50%)'
479 });
480 // Disable X offset for top position
481 $offset_x.prop('disabled', true);
482 break;
483 case 'bottom':
484 $img.css({
485 'bottom': offset_y + 'px',
486 'left': '50%',
487 'transform': 'translateX(-50%)'
488 });
489 // Disable X offset for bottom position
490 $offset_x.prop('disabled', true);
491 break;
492 }
493
494 // Add visual feedback for disabled inputs
495 $('.wp-sir-offset-input').each(function() {
496 $(this).closest('.wp-sir-offset-field').toggleClass('disabled', $(this).prop('disabled'));
497 });
498 }
499
500 setWatermarkPosition($('#wp-sir-watermark-position'));
501
502 // Update offset handlers
503 $(document).on('change keyup paste', '#wp-sir-watermark-offset-x, #wp-sir-watermark-offset-y', function() {
504 setWatermarkPosition($('#wp-sir-watermark-position'));
505 });
506
507 $(document).on('change', '#wp-sir-enable-watermark', function () {
508 if ($(this).is(':checked')) {
509 $('.wp-sir-watermark-settings').css('display', 'flex');
510 } else {
511 $('.wp-sir-watermark-settings').css('display', 'none');
512
513 }
514 }).change();
515
516
517 jQuery(document).ready(function($) {
518
519
520 // Update tolerance value display
521 $('.wp-sir-range-input').on('input', function() {
522 $('#' + $(this).data('value-display')).text($(this).val() + '%');
523 });
524
525 // Trim toggle — show/hide advanced sub-fields
526 $('#wp-sir-enable-trim').on('change', function() {
527 $('.wp-sir-trim-advanced-settings').toggle($(this).prop('checked'));
528 });
529
530 // Tolerance slider — live feedback message
531 $('#wp-sir-trim-tolerance').on('input', function() {
532 var value = parseInt( $(this).val(), 10 );
533 var $feedback = $('.wp-sir-tolerance-feedback');
534 if ( value > 50 ) {
535 $feedback.html('<span class="wp-sir-feedback--danger">Warning: High tolerance may trim parts of your image that you want to keep.</span>');
536 } else if ( value > 20 ) {
537 $feedback.html('<span class="wp-sir-feedback--warning">Caution: Moderate-high tolerance — test on sample images first.</span>');
538 } else if ( value > 10 ) {
539 $feedback.text('Medium tolerance — will trim similar shades of white.');
540 } else {
541 $feedback.text('Default: 3%. Increase to trim more aggressively.');
542 }
543 }).trigger('input');
544
545 $('.wp-sir-watermark-size').trigger('input');
546
547 // Initialize tooltips
548 if($.fn.tipTip)
549 {
550 $('.wp-sir-help-tip').tipTip({
551 'attribute': 'title',
552 'fadeIn': 50,
553 'fadeOut': 50,
554 'delay': 200
555 });
556 }
557
558 // Handle sizes section toggle
559 $('.wp-sir-toggle-sizes').on('click', function() {
560 const $button = $(this);
561 const $wrapper = $('#wp-sir-sizes-options');
562 const isExpanded = $button.attr('aria-expanded') === 'true';
563
564 $wrapper.slideToggle(200);
565 $button.attr('aria-expanded', !isExpanded);
566
567 // Update button text
568 const $text = $button.find('.wp-sir-toggle-text');
569 $text.text(isExpanded ? 'Customize image sizes' : 'Hide image sizes');
570 });
571
572 // Update sizes summary when selections change
573 $('#wp-sir-sizes-selector input[type="checkbox"]').on('change', function() {
574 const totalSizes = $('#wp-sir-sizes-selector .wpSirSelectSize').length;
575 const selectedSizes = $('#wp-sir-sizes-selector .wpSirSelectSize:checked').length;
576
577 $('.wp-sir-sizes-summary').text(
578 `${selectedSizes} of ${totalSizes} size${totalSizes !== 1 ? 's' : ''} selected`
579 );
580 });
581 });
582
583 function createCurveControl() {
584 const positions = [
585 ['top-left', 'top', 'top-right'],
586 ['left', 'center', 'right'],
587 ['bottom-left', 'bottom', 'bottom-right']
588 ];
589
590 let html = '<div class="wp-sir-curve-control">';
591 positions.forEach((row) => {
592 html += '<div class="wp-sir-curve-row">';
593 row.forEach((pos) => {
594 const label = pos.replace(/-/g, ' ');
595 html += `<button type="button" class="wp-sir-curve-point" data-position="${pos}" title="${label}"><span class="screen-reader-text">${label}</span></button>`;
596 });
597 html += '</div>';
598 });
599 html += '</div>';
600
601 $('#wp-sir-watermark-position').after(html);
602
603 // Handle clicks
604 $(document).on('click', '.wp-sir-curve-point', function() {
605 $('.wp-sir-curve-point').removeClass('active');
606 $(this).addClass('active');
607 $('#wp-sir-watermark-position').val($(this).data('position')).trigger('change');
608 });
609
610 // Sync grid with dropdown
611 function updateCurveFromDropdown() {
612 const current = $('#wp-sir-watermark-position').val();
613 $('.wp-sir-curve-point').removeClass('active');
614 $(`.wp-sir-curve-point[data-position="${current}"]`).addClass('active');
615 }
616
617 updateCurveFromDropdown();
618 $('#wp-sir-watermark-position').on('change', updateCurveFromDropdown);
619 }
620
621 // Initialize curve control when document is ready
622 $(document).ready(function() {
623 createCurveControl();
624 });
625
626 $('.wp-sir-tabs div').on('click', function(e) {
627 e.preventDefault();
628 $('.wp-sir-tabs div').removeClass('active');
629 $(this).addClass('active');
630 if($(this).data('tab') === 'general'){
631 $('.sir-settings-general >table>tbody>tr:not(.wp-sir-is-advanced)').removeClass('hidden');
632 $('.sir-settings-general >table>tbody>tr.wp-sir-is-advanced').addClass('hidden');
633 }else{
634 $('.sir-settings-general >table>tbody>tr:not(.wp-sir-is-advanced)').addClass('hidden');
635 $('.sir-settings-general >table>tbody>tr.wp-sir-is-advanced').removeClass('hidden');
636 }
637 });
638
639 // ── Redesigned settings page interactions ────────────────────────────────────
640 (function ($) {
641 'use strict';
642
643 $(document).ready(function () {
644
645 // Advanced settings toggle
646 $('#wp-sir-toggle-advanced').on('click', function () {
647 var $btn = $(this);
648 var $panel = $('#wp-sir-advanced-fields');
649 var expanded = $btn.attr('aria-expanded') === 'true';
650
651 $panel.slideToggle(200);
652 $btn.attr('aria-expanded', !expanded);
653 });
654
655 // Image Uniformity enable/disable — dim the card body when off.
656 function toggleUniformityState() {
657 var $card = $('#wp-sir-core-settings');
658 if ($('#wp-sir-enable').is(':checked')) {
659 $card.removeClass('wp-sir-card--disabled');
660 } else {
661 $card.addClass('wp-sir-card--disabled');
662 }
663 }
664 toggleUniformityState();
665 $('#wp-sir-enable').on('change', toggleUniformityState);
666
667 // Add-on card expand / collapse via header click
668 // The toggle checkbox itself is handled separately to avoid double-firing.
669 $(document).on('click', '.wp-sir-addon-card__header', function (e) {
670 // Don't collapse when clicking the toggle checkbox or the PRO pill link.
671 if ($(e.target).is('input[type="checkbox"], a, .wp-sir-toggle-label, .wp-sir-toggle-label *')) {
672 return;
673 }
674
675 var $header = $(this);
676 var targetId = $header.attr('aria-controls');
677 var $body = $('#' + targetId);
678 var expanded = $header.attr('aria-expanded') === 'true';
679
680 $body.slideToggle(200);
681 $header.attr('aria-expanded', !expanded);
682 });
683
684 // Keyboard accessibility for add-on card headers
685 $(document).on('keydown', '.wp-sir-addon-card__header', function (e) {
686 if (e.which === 13 || e.which === 32) {
687 e.preventDefault();
688 $(this).trigger('click');
689 }
690 });
691
692 // When an add-on toggle is switched ON, also expand the body.
693 $(document).on('change', '.wp-sir-addon-toggle', function () {
694 var $checkbox = $(this);
695 var targetId = $checkbox.data('target');
696 var $body = $('#' + targetId);
697 var $header = $body.closest('.wp-sir-addon-card').find('.wp-sir-addon-card__header');
698
699 if ($checkbox.is(':checked')) {
700 $body.slideDown(200);
701 $header.attr('aria-expanded', 'true');
702 } else {
703 $body.slideUp(200);
704 $header.attr('aria-expanded', 'false');
705 }
706 });
707
708 });
709 })(jQuery);
710
711 // Handle Regenerate Thumbnails plugin installation — removed (built-in bulk processor replaces RT)
712
713 // ─── Built-in Bulk Image Processor ───────────────────────────────────────────
714 (function ($) {
715 'use strict';
716
717 // Only run when the bulk-regenerate tab is present.
718 if (typeof wp_sir_object === 'undefined' || !wp_sir_object.bulk) {
719 return;
720 }
721
722 var bulk = wp_sir_object.bulk;
723 var ajaxUrl = wp_sir_object.ajax_url;
724 var nonce = wp_sir_object.nonce;
725
726 // All errors accumulated across the entire session (survives ticks).
727 var _allErrors = [];
728 var _aborted = false;
729 var _started = false; // True once user initiates a process — prevents stale fetchStatus data.
730 var _done = false; // True once done state is shown — prevents late callbacks overriding it.
731
732 // DOM refs — resolved once the page is ready.
733 var $wrap, $stateIdle, $stateActive, $stateDone;
734 var $btnStart, $btnPause, $btnResume, $btnRestart, $btnAbort;
735 var $statusLabel, $doneCount, $totalCount;
736 var $progressBar, $progressBarWrap, $percentLabel;
737 var $doneSummary;
738 var $errorLog, $errorLogToggle, $errorLogTitle, $errorLogBody, $errorTableBody;
739 var $idleMessage, $idleHint;
740
741 function init() {
742 $wrap = $('#wp-sir-bulk-wrap');
743 if (!$wrap.length) return;
744
745 $stateIdle = $('#wp-sir-state-idle');
746 $stateActive = $('#wp-sir-state-active');
747 $stateDone = $('#wp-sir-state-done');
748
749 $btnStart = $('#wp-sir-bulk-start');
750 $btnPause = $('#wp-sir-bulk-pause');
751 $btnResume = $('#wp-sir-bulk-resume');
752 $btnRestart = $('#wp-sir-bulk-restart');
753 $btnAbort = $('#wp-sir-bulk-abort');
754
755 $statusLabel = $('#wp-sir-status-label');
756 $doneCount = $('#wp-sir-done-count');
757 $totalCount = $('#wp-sir-total-count');
758 $progressBar = $('#wp-sir-progress-bar');
759 $progressBarWrap = $('#wp-sir-progress-bar-wrap');
760 $percentLabel = $('#wp-sir-percent-label');
761 $doneSummary = $('#wp-sir-done-summary');
762
763 $errorLog = $('#wp-sir-error-log');
764 $errorLogToggle = $('#wp-sir-error-log-toggle');
765 $errorLogTitle = $('#wp-sir-error-log-title');
766 $errorLogBody = $('#wp-sir-error-log-body');
767 $errorTableBody = $('#wp-sir-error-table-body');
768 $idleMessage = $('#wp-sir-idle-message');
769 $idleHint = $('#wp-sir-idle-hint');
770
771 // Collapsible error log.
772 $errorLogToggle.on('click keydown', function (e) {
773 if (e.type === 'keydown' && e.which !== 13 && e.which !== 32) return;
774 var expanded = $errorLogToggle.attr('aria-expanded') === 'true';
775 $errorLogToggle.attr('aria-expanded', !expanded);
776 $errorLogBody.slideToggle(150);
777 $errorLogToggle.find('.wp-sir-error-log-chevron')
778 .toggleClass('dashicons-arrow-down-alt2', expanded)
779 .toggleClass('dashicons-arrow-up-alt2', !expanded);
780 });
781
782 $btnStart.on('click', function () { startProcess(false); });
783 $btnPause.on('click', pauseProcess);
784 $btnResume.on('click', function () { resumeProcess(); });
785 $btnRestart.on('click', function () {
786 startProcess(true);
787 });
788 $btnAbort.on('click', function () {
789 if (!window.confirm('Abort this process? Progress will be cleared and you can start fresh.')) return;
790 _aborted = true;
791 var $icon = $btnAbort.find('.dashicons');
792 var $label = $btnAbort.contents().filter(function() { return this.nodeType === 3; }).last();
793 $btnAbort.prop('disabled', true);
794 $label[0].textContent = ' Aborting\u2026';
795 ajax(bulk.action_reset, {}, function (res) {
796 $label[0].textContent = ' Abort';
797 $btnAbort.prop('disabled', false);
798 _allErrors = [];
799 $errorTableBody.empty();
800 $errorLog.hide();
801 if (res && res.success) {
802 applyState({ status: 'idle' });
803 fetchCount();
804 }
805 });
806 });
807
808 fetchStatus();
809
810 // Refresh count link — manual recount.
811 $('#wp-sir-refresh-count').on('click', function (e) {
812 e.preventDefault();
813 var $link = $(this);
814 $link.addClass('wp-sir-refresh-count--spinning');
815 ajax(bulk.action_count, {}, function (res) {
816 $link.removeClass('wp-sir-refresh-count--spinning');
817 if (res && res.success) {
818 applyCount(parseInt(res.data.count, 10) || 0);
819 }
820 });
821 });
822
823 // If no cached count was available server-side, fetch it now.
824 if ($btnStart.prop('disabled') && $idleMessage.text().indexOf('Checking') !== -1) {
825 prefetchCount();
826 }
827 }
828
829 // ── API ────────────────────────────────────────────────────────────────────
830
831 var _prefetchedCount = null;
832
833 function ajax(action, extraData, callback) {
834 $.post(ajaxUrl, $.extend({ action: action, nonce: nonce }, extraData || {}), callback, 'json');
835 }
836
837 function fetchStatus() {
838 ajax(bulk.action_status, {}, function (res) {
839 if (res && res.success) {
840 // Only restore errors from a previous session if we haven't already
841 // started a new process (prevents race condition on fast clicks).
842 if (!_started && res.data.new_errors && res.data.new_errors.length && res.data.status !== 'running') {
843 appendErrors(res.data.new_errors);
844 }
845
846 // On a fresh page load there is no active tick loop.
847 // If the server says "running" it means the previous session was
848 // interrupted (tab closed, reload, crash). Treat it as paused so
849 // the user sees Resume, not Pause with nothing actually processing.
850 if (res.data.status === 'running') {
851 res.data.status = 'paused';
852 // Tell the server to record the paused state too, fire-and-forget.
853 ajax(bulk.action_pause, {});
854 }
855
856 applyState(res.data);
857
858 // If idle, apply the prefetched count (or wait for it).
859 if (!res.data.status || res.data.status === 'idle') {
860 applyCount(_prefetchedCount);
861 }
862 }
863 });
864 }
865
866 // Fire the count request in parallel with fetchStatus for faster load.
867 function prefetchCount() {
868 ajax(bulk.action_count, {}, function (res) {
869 if (res && res.success) {
870 _prefetchedCount = parseInt(res.data.count, 10) || 0;
871 } else {
872 _prefetchedCount = -1; // error state
873 }
874 // If idle state is already shown, apply immediately.
875 if ($stateIdle.is(':visible')) {
876 applyCount(_prefetchedCount);
877 }
878 });
879 }
880
881 function applyCount(count) {
882 if (count === null) return; // still loading
883 if (count === -1) {
884 $idleMessage.text('Ready to process your images.');
885 $idleHint.show();
886 $btnStart.prop('disabled', false);
887 return;
888 }
889 if (count > 0) {
890 $idleMessage.text(count + ' image' + (count !== 1 ? 's' : '') + ' ready to be processed.');
891 $btnStart.prop('disabled', false);
892 } else {
893 $idleMessage.text('All your images are already up to date.');
894 $btnStart.prop('disabled', true);
895 }
896 $idleHint.show();
897 }
898
899 function fetchCount() {
900 // Used after abort — fires a fresh count request.
901 ajax(bulk.action_count, {}, function (res) {
902 if (res && res.success) {
903 applyCount(parseInt(res.data.count, 10) || 0);
904 } else {
905 applyCount(-1);
906 }
907 });
908 }
909
910 function startProcess(restart) {
911 _aborted = false;
912 _started = true;
913 _done = false;
914 // Always clear the client-side error log before starting or restarting —
915 // the server truncates the errors table on both paths too.
916 _allErrors = [];
917 $errorTableBody.empty();
918 $errorLog.hide();
919 // Instant feedback — disable button and show loading state.
920 $btnStart.prop('disabled', true).addClass('wp-sir-btn-loading');
921 ajax(bulk.action_start, { restart: restart ? 1 : 0 }, function (res) {
922 $btnStart.prop('disabled', false).removeClass('wp-sir-btn-loading');
923 if (res && res.success) {
924 applyState(res.data);
925 if (res.data.status === 'running') tick();
926 }
927 });
928 }
929
930 function resumeProcess() {
931 _aborted = false;
932 // Instant feedback — hide resume, show pause immediately.
933 $btnResume.hide();
934 $btnAbort.hide();
935 $btnPause.css('display', 'inline-flex');
936 $statusLabel.text('Processing\u2026');
937 ajax(bulk.action_start, { restart: 0 }, function (res) {
938 if (res && res.success) {
939 applyState(res.data);
940 if (res.data.status === 'running') tick();
941 }
942 });
943 }
944
945 function pauseProcess() {
946 _aborted = true;
947 // Instant feedback — disable button and show loading state.
948 $btnPause.prop('disabled', true).addClass('wp-sir-btn-loading');
949 ajax(bulk.action_pause, {}, function (res) {
950 $btnPause.prop('disabled', false).removeClass('wp-sir-btn-loading');
951 if (res && res.success) applyState(res.data);
952 });
953 }
954
955 function tick() {
956 if (_aborted) return;
957
958 ajax(bulk.action_process, {}, function (res) {
959 if (_aborted) return; // pause was clicked while this request was in-flight
960
961 if (!res || !res.success) {
962 _aborted = true;
963 applyState({ status: 'paused', total: parseInt($totalCount.text(), 10), done: parseInt($doneCount.text(), 10), error_count: _allErrors.length });
964 return;
965 }
966
967 // Append any new errors from this batch immediately.
968 if (res.data.new_errors && res.data.new_errors.length) {
969 appendErrors(res.data.new_errors);
970 }
971
972 applyState(res.data);
973
974 if (res.data.status === 'running') {
975 setTimeout(tick, 300);
976 }
977 });
978 }
979
980 // ── Error log helpers ──────────────────────────────────────────────────────
981
982 function appendErrors(errors) {
983 if (!errors || !errors.length) return;
984
985 errors.forEach(function (err) {
986 _allErrors.push(err);
987 var $row = $('<tr>')
988 .append($('<td class="wp-sir-error-file">').text(err.file))
989 .append($('<td class="wp-sir-error-reason">').text(err.reason));
990 $errorTableBody.append($row);
991 });
992
993 // Update header count and show the log.
994 var count = _allErrors.length;
995 $errorLogTitle.text(count + ' image' + (count !== 1 ? 's' : '') + ' skipped');
996 $errorLog.show();
997 }
998
999 // ── UI state ───────────────────────────────────────────────────────────────
1000
1001 function applyState(data) {
1002 var status = data.status || 'idle';
1003 var total = parseInt(data.total, 10) || 0;
1004 var done = parseInt(data.done, 10) || 0;
1005 var pct = total > 0 ? Math.round((done / total) * 100) : 0;
1006
1007 // Once done, don't allow late callbacks to revert to another state.
1008 if (_done && status !== 'done' && status !== 'idle') {
1009 return;
1010 }
1011
1012 // Hide loading placeholder once real state is known.
1013 $('#wp-sir-state-loading').remove();
1014
1015 $stateIdle.hide();
1016 $stateActive.hide();
1017 $stateDone.hide();
1018
1019 if (status === 'idle') {
1020 _done = false;
1021 $stateIdle.show();
1022 return;
1023 }
1024
1025 if (status === 'done') {
1026 _done = true;
1027 $stateDone.show();
1028 var skipped = _allErrors.length;
1029 var succeeded = done - skipped;
1030 var summary = succeeded + ' image' + (succeeded !== 1 ? 's' : '') + ' processed successfully.';
1031 if (skipped > 0) {
1032 summary += ' ' + skipped + ' image' + (skipped !== 1 ? 's' : '') + ' were skipped — see the log below.';
1033 }
1034 $doneSummary.text(summary);
1035 return;
1036 }
1037
1038 // running or paused
1039 $stateActive.show();
1040 $doneCount.text(done);
1041 $totalCount.text(total);
1042 $percentLabel.text(pct + '%');
1043 $progressBar.css('width', pct + '%');
1044 $progressBarWrap.attr('aria-valuenow', pct);
1045
1046 if (status === 'running') {
1047 $statusLabel.text('Processing\u2026');
1048 $btnPause.css('display', 'inline-flex');
1049 $btnResume.hide();
1050 $btnAbort.hide();
1051 $progressBar.removeClass('wp-sir-bulk-progress-bar--paused');
1052 } else {
1053 $statusLabel.text('Paused');
1054 $btnPause.hide();
1055 $btnResume.css('display', 'inline-flex');
1056 $btnAbort.css('display', 'inline-flex');
1057 $progressBar.addClass('wp-sir-bulk-progress-bar--paused');
1058 }
1059 }
1060
1061 $(document).ready(init);
1062
1063 })(jQuery);
1064
1065 })(jQuery);
1066
1067
1068
1069
1070
1071
1072 // ─── Reset images (settings page) ────────────────────────────────────────────
1073 (function ($) {
1074 'use strict';
1075
1076 $(document).ready(function () {
1077
1078 if (!$('#wp-sir-reset-section').length) return;
1079
1080 var ajaxUrl = wp_sir_object.ajax_url;
1081 var nonce = wp_sir_object.restore_nonce;
1082 var BATCH = 5;
1083
1084 var $btnStart = $('#wp-sir-reset-start');
1085 var $countLabel = $('#wp-sir-reset-count');
1086 var $stateIdle = $('#wp-sir-reset-state-idle');
1087 var $stateRunning = $('#wp-sir-reset-state-running');
1088 var $stateDone = $('#wp-sir-reset-state-done');
1089 var $progressBar = $('#wp-sir-reset-progress-bar');
1090 var $progressWrap = $('#wp-sir-reset-progress-wrap');
1091 var $pct = $('#wp-sir-reset-percent');
1092 var $doneCount = $('#wp-sir-reset-done');
1093 var $totalCount = $('#wp-sir-reset-total');
1094 var $summary = $('#wp-sir-reset-summary');
1095 var $errorsWrap = $('#wp-sir-reset-errors');
1096 var $errorList = $('#wp-sir-reset-error-list');
1097
1098 var _ids = [];
1099 var _total = 0;
1100 var _done = 0;
1101 var _errors = [];
1102 var _stopped = false;
1103
1104 // Fetch count of processed images on page load.
1105 $.post(ajaxUrl, { action: 'wp_sir_bulk_restore_status', nonce: nonce }, function (res) {
1106 if (res && res.success && res.data.total > 0) {
1107 _ids = res.data.ids;
1108 _total = res.data.total;
1109 $countLabel.text('(' + _total + ' image' + (_total !== 1 ? 's' : '') + ' processed)');
1110 $btnStart.prop('disabled', false);
1111 } else {
1112 $countLabel.text('(' + wp_sir_object.i18n.no_processed_images + ')');
1113 }
1114 }, 'json');
1115
1116 $btnStart.on('click', function () {
1117 if (!_ids.length) return;
1118 if (!window.confirm(wp_sir_object.i18n.restore_confirm)) return;
1119
1120 _done = 0;
1121 _errors = [];
1122 _stopped = false;
1123 $errorList.empty();
1124 $errorsWrap.hide();
1125 $stateIdle.hide();
1126 $stateRunning.show();
1127 $stateDone.hide();
1128 $totalCount.text(_total);
1129 processBatch();
1130 });
1131
1132 $('#wp-sir-reset-stop').on('click', function () {
1133 _stopped = true;
1134 $(this).prop('disabled', true).text('Stopping…');
1135 });
1136
1137 function processBatch() {
1138 if (_stopped) { finish(); return; }
1139 var batch = _ids.slice(_done, _done + BATCH);
1140 if (!batch.length) { finish(); return; }
1141
1142 $.post(ajaxUrl, {
1143 action: 'wp_sir_bulk_restore_batch',
1144 nonce: nonce,
1145 ids: JSON.stringify(batch)
1146 }, function (res) {
1147 if (!res || !res.success) { finish(); return; }
1148
1149 _done += batch.length;
1150
1151 if (res.data.errors && res.data.errors.length) {
1152 res.data.errors.forEach(function (e) {
1153 _errors.push(e);
1154 $errorList.append('<li>ID ' + e.id + ': ' + $('<span>').text(e.reason).html() + '</li>');
1155 });
1156 $errorsWrap.show();
1157 }
1158
1159 var percent = Math.round((_done / _total) * 100);
1160 $progressBar.css('width', percent + '%');
1161 $progressWrap.attr('aria-valuenow', percent);
1162 $pct.text(percent + '%');
1163 $doneCount.text(_done);
1164
1165 if (_done >= _total) { finish(); } else if (!_stopped) { setTimeout(processBatch, 300); } else { finish(); }
1166 }, 'json');
1167 }
1168
1169 function finish() {
1170 $stateRunning.hide();
1171 $stateDone.show();
1172 $('#wp-sir-reset-stop').prop('disabled', false).text('Stop');
1173
1174 var succeeded = _done - _errors.length;
1175 var s = succeeded + ' image' + (succeeded !== 1 ? 's' : '') + ' restored to original state.';
1176 if (_stopped && _done < _total) { s += ' Stopped — ' + (_total - _done) + ' remaining.'; }
1177 if (_errors.length) { s += ' ' + _errors.length + ' failed — see above.'; }
1178 $summary.text(s);
1179
1180 _ids = [];
1181 $btnStart.prop('disabled', true);
1182 $countLabel.text('(' + wp_sir_object.i18n.no_processed_images + ')');
1183 }
1184
1185 });
1186
1187 })(jQuery);
1188