d83d5b28 by Jeff Balicki

sss

Signed-off-by: Jeff <jeff@gotenzing.com>
1 parent 5e5f513a
Showing 489 changed files with 4987 additions and 0 deletions
1 import '../scss/app.scss';
2
3 /**
4 * Admin modules
5 */
6
7 const WP_Smush = WP_Smush || {};
8 window.WP_Smush = WP_Smush;
9
10 /**
11 * IE polyfill for includes.
12 *
13 * @since 3.1.0
14 * @param {string} search
15 * @param {number} start
16 * @return {boolean} Returns true if searchString appears as a substring of the result of converting this
17 * object to a String, at one or more positions that are
18 * greater than or equal to position; otherwise, returns false.
19 */
20 if ( ! String.prototype.includes ) {
21 String.prototype.includes = function( search, start ) {
22 if ( typeof start !== 'number' ) {
23 start = 0;
24 }
25
26 if ( start + search.length > this.length ) {
27 return false;
28 }
29 return this.indexOf( search, start ) !== -1;
30 };
31 }
32
33 require( './modules/helpers' );
34 require( './modules/admin' );
35 require( './modules/admin-common' );
36 require( './modules/bulk-smush' );
37 require( './modules/background-process' );
38 require( './modules/onboarding' );
39 require( './modules/directory-smush' );
40 require( './smush/cdn' );
41 require( './smush/webp' );
42 require( './smush/lazy-load' );
43 require( './modules/bulk-restore' );
44 require( './smush/settings' );
45
46 /**
47 * Notice scripts.
48 *
49 * Notices are used in the following functions:
50 *
51 * @used-by \Smush\Core\Modules\Smush::smush_updated()
52 * @used-by \Smush\Core\Integrations\S3::3_support_required_notice()
53 * @used-by \Smush\App\Abstract_Page::installation_notice()
54 *
55 * TODO: should this be moved out in a separate file like common.scss?
56 */
57 require( './modules/notice' );
1 import lazySizes from 'lazysizes';
2 import 'lazysizes/plugins/native-loading/ls.native-loading';
3
4 lazySizes.cfg.nativeLoading = {
5 setLoadingAttribute: true,
6 disableListeners: {
7 scroll: true,
8 },
9 };
10
11 lazySizes.init();
1 import lazySizes from 'lazysizes';
2
3 lazySizes.init();
1 import '../../scss/resize-detection.scss';
2
3 /**
4 * Image resize detection (IRD).
5 *
6 * Show all wrongly scaled images with a highlighted border and resize box.
7 *
8 * Made in pure JS.
9 * DO NOT ADD JQUERY SUPPORT!!!
10 *
11 * @since 2.9
12 */
13 ( function() {
14 'use strict';
15
16 const SmushIRS = {
17 bar: document.getElementById( 'smush-image-bar' ),
18 toggle: document.getElementById( 'smush-image-bar-toggle' ),
19 images: {
20 bigger: [],
21 smaller: [],
22 },
23 strings: window.wp_smush_resize_vars,
24
25 /**
26 * Init scripts.
27 */
28 init() {
29 /**
30 * Make sure these are set, before we proceed.
31 */
32 if ( ! this.bar ) {
33 this.bar = document.getElementById( 'smush-image-bar' );
34 }
35 if ( ! this.toggle ) {
36 this.toggle = document.getElementById(
37 'smush-image-bar-toggle'
38 );
39 }
40
41 this.process();
42
43 // Register the event handler after everything is done.
44 this.toggle.addEventListener(
45 'click',
46 this.handleToggleClick.bind( this )
47 );
48 },
49
50 /**
51 * Do image processing.
52 */
53 process() {
54 const icon = this.toggle.querySelector( 'i' );
55
56 icon.classList.add( 'sui-icon-loader' );
57 icon.classList.remove( 'sui-icon-info' );
58
59 this.detectImages();
60
61 if ( ! this.images.bigger.length && ! this.images.smaller.length ) {
62 this.toggle.classList.add( 'smush-toggle-success' );
63 document.getElementById(
64 'smush-image-bar-notice'
65 ).style.display = 'block';
66 document.getElementById(
67 'smush-image-bar-notice-desc'
68 ).style.display = 'none';
69 } else {
70 this.toggle.classList.remove( 'smush-toggle-success' );
71 document.getElementById(
72 'smush-image-bar-notice'
73 ).style.display = 'none';
74 document.getElementById(
75 'smush-image-bar-notice-desc'
76 ).style.display = 'block';
77 this.generateMarkup( 'bigger' );
78 this.generateMarkup( 'smaller' );
79 }
80
81 this.toggleDivs();
82
83 icon.classList.remove( 'sui-icon-loader' );
84 icon.classList.add( 'sui-icon-info' );
85 },
86
87 /**
88 * Various checks to see if the image should be processed.
89 *
90 * @param {Object} image
91 * @return {boolean} Should skip image or not.
92 */
93 shouldSkipImage( image ) {
94 // Skip avatars.
95 if ( image.classList.contains( 'avatar' ) ) {
96 return true;
97 }
98
99 // Skip images from Smush CDN with auto-resize feature.
100 if (
101 'string' === typeof image.getAttribute( 'no-resize-detection' )
102 ) {
103 return true;
104 }
105
106 // Skip 1x1px images.
107 if (
108 image.clientWidth === image.clientHeight &&
109 1 === image.clientWidth
110 ) {
111 return true;
112 }
113
114 // Skip 1x1px placeholders.
115 if (
116 image.naturalWidth === image.naturalHeight &&
117 1 === image.naturalWidth
118 ) {
119 return true;
120 }
121
122 // If width attribute is not set, do not continue.
123 return null === image.clientWidth || null === image.clientHeight;
124 },
125
126 /**
127 * Get tooltip text.
128 *
129 * @param {Object} props
130 * @return {string} Tooltip.
131 */
132 getTooltipText( props ) {
133 let tooltipText = '';
134
135 if ( props.bigger_width || props.bigger_height ) {
136 /** @param {string} strings.large_image */
137 tooltipText = this.strings.large_image;
138 } else if ( props.smaller_width || props.smaller_height ) {
139 /** @param {string} strings.small_image */
140 tooltipText = this.strings.small_image;
141 }
142
143 return tooltipText
144 .replace( 'width', props.real_width )
145 .replace( 'height', props.real_height );
146 },
147
148 /**
149 * Generate markup.
150 *
151 * @param {string} type Accepts: 'bigger' or 'smaller'.
152 */
153 generateMarkup( type ) {
154 this.images[ type ].forEach( ( image, index ) => {
155 const item = document.createElement( 'div' ),
156 tooltip = this.getTooltipText( image.props );
157
158 item.setAttribute(
159 'class',
160 'smush-resize-box smush-tooltip smush-tooltip-constrained'
161 );
162 item.setAttribute( 'data-tooltip', tooltip );
163 item.setAttribute( 'data-image', image.class );
164 item.addEventListener( 'click', ( e ) =>
165 this.highlightImage( e )
166 );
167
168 item.innerHTML = `
169 <div class="smush-image-info">
170 <span>${ index + 1 }</span>
171 <span class="smush-tag">${ image.props.computed_width } x ${ image.props.computed_height }px</span>
172 <i class="smush-front-icons smush-front-icon-arrows-in" aria-hidden="true">&nbsp;</i>
173 <span class="smush-tag smush-tag-success">${ image.props.real_width } × ${ image.props.real_height }px</span>
174 </div>
175 <div class="smush-image-description">${ tooltip }</div>
176 `;
177
178 document
179 .getElementById( 'smush-image-bar-items-' + type )
180 .appendChild( item );
181 } );
182 },
183
184 /**
185 * Show/hide sections based on images.
186 */
187 toggleDivs() {
188 const types = [ 'bigger', 'smaller' ];
189 types.forEach( ( type ) => {
190 const div = document.getElementById(
191 'smush-image-bar-items-' + type
192 );
193 if ( 0 === this.images[ type ].length ) {
194 div.style.display = 'none';
195 } else {
196 div.style.display = 'block';
197 }
198 } );
199 },
200
201 /**
202 * Scroll the selected image into view and highlight it.
203 *
204 * @param {Object} e
205 */
206 highlightImage( e ) {
207 this.removeSelection();
208
209 const el = document.getElementsByClassName(
210 e.currentTarget.dataset.image
211 );
212 if ( 'undefined' !== typeof el[ 0 ] ) {
213 // Display description box.
214 e.currentTarget.classList.toggle( 'show-description' );
215
216 // Scroll and flash image.
217 el[ 0 ].scrollIntoView( {
218 behavior: 'smooth',
219 block: 'center',
220 inline: 'nearest',
221 } );
222 el[ 0 ].style.opacity = '0.5';
223 setTimeout( () => {
224 el[ 0 ].style.opacity = '1';
225 }, 1000 );
226 }
227 },
228
229 /**
230 * Handle click on the toggle item.
231 */
232 handleToggleClick() {
233 this.bar.classList.toggle( 'closed' );
234 this.toggle.classList.toggle( 'closed' );
235 this.removeSelection();
236 },
237
238 /**
239 * Remove selected items.
240 */
241 removeSelection() {
242 const items = document.getElementsByClassName( 'show-description' );
243 if ( items.length > 0 ) {
244 Array.from( items ).forEach( ( div ) =>
245 div.classList.remove( 'show-description' )
246 );
247 }
248 },
249
250 /**
251 * Function to highlight all scaled images.
252 *
253 * Add yellow border and then show one small box to
254 * resize the images as per the required size, on fly.
255 */
256 detectImages() {
257 const images = document.getElementsByTagName( 'img' );
258 for ( const image of images ) {
259 if ( this.shouldSkipImage( image ) ) {
260 continue;
261 }
262
263 // Get defined width and height.
264 const props = {
265 real_width: image.clientWidth,
266 real_height: image.clientHeight,
267 computed_width: image.naturalWidth,
268 computed_height: image.naturalHeight,
269 bigger_width: image.clientWidth * 1.5 < image.naturalWidth,
270 bigger_height:
271 image.clientHeight * 1.5 < image.naturalHeight,
272 smaller_width: image.clientWidth > image.naturalWidth,
273 smaller_height: image.clientHeight > image.naturalHeight,
274 };
275
276 // In case image is in correct size, do not continue.
277 if (
278 ! props.bigger_width &&
279 ! props.bigger_height &&
280 ! props.smaller_width &&
281 ! props.smaller_height
282 ) {
283 continue;
284 }
285
286 const imgType =
287 props.bigger_width || props.bigger_height
288 ? 'bigger'
289 : 'smaller',
290 imageClass =
291 'smush-image-' + ( this.images[ imgType ].length + 1 );
292
293 // Fill the images arrays.
294 this.images[ imgType ].push( {
295 src: image,
296 props,
297 class: imageClass,
298 } );
299
300 /**
301 * Add class to original image.
302 * Can't add two classes in single add(), because no support in IE11.
303 * image.classList.add('smush-detected-img', imageClass);
304 */
305 image.classList.add( 'smush-detected-img' );
306 image.classList.add( imageClass );
307 }
308 }, // End detectImages()
309
310 /**
311 * Allows refreshing the list. A good way is to refresh on lazyload actions.
312 *
313 * @since 3.6.0
314 */
315 refresh() {
316 // Clear out classes on DOM.
317 for ( let id in this.images.bigger ) {
318 if ( this.images.bigger.hasOwnProperty( id ) ) {
319 this.images.bigger[ id ].src.classList.remove(
320 'smush-detected-img'
321 );
322 this.images.bigger[ id ].src.classList.remove(
323 'smush-image-' + ++id
324 );
325 }
326 }
327
328 for ( let id in this.images.smaller ) {
329 if ( this.images.smaller.hasOwnProperty( id ) ) {
330 this.images.smaller[ id ].src.classList.remove(
331 'smush-detected-img'
332 );
333 this.images.smaller[ id ].src.classList.remove(
334 'smush-image-' + ++id
335 );
336 }
337 }
338
339 this.images = {
340 bigger: [],
341 smaller: [],
342 };
343
344 // This might be overkill - there will probably never be a situation when there are less images than on
345 // initial page load.
346 const elements = document.getElementsByClassName(
347 'smush-resize-box'
348 );
349 while ( elements.length > 0 ) {
350 elements[ 0 ].remove();
351 }
352
353 this.process();
354 },
355 }; // End WP_Smush_IRS
356
357 /**
358 * After page load, initialize toggle event.
359 */
360 window.addEventListener( 'DOMContentLoaded', () => SmushIRS.init() );
361 window.addEventListener( 'lazyloaded', () => SmushIRS.refresh() );
362 }() );
1 import '../scss/common.scss';
2
3 /* global ajaxurl */
4
5 document.addEventListener('DOMContentLoaded', function () {
6 const dismissNoticeButton = document.querySelectorAll(
7 '.smush-dismissible-notice .smush-dismiss-notice-button'
8 );
9 dismissNoticeButton.forEach((button) => {
10 button.addEventListener('click', dismissNotice);
11 });
12
13 function dismissNotice(event) {
14 event.preventDefault();
15
16 const button = event.target;
17 const notice = button.closest('.smush-dismissible-notice');
18 const key = notice.getAttribute('data-key');
19
20 const xhr = new XMLHttpRequest();
21 xhr.open(
22 'POST',
23 ajaxurl + '?action=smush_dismiss_notice&key=' + key + '&_ajax_nonce=' + smush_global.nonce,
24 true
25 );
26 xhr.onload = () => {
27 if (notice) {
28 notice.querySelector('button.notice-dismiss').dispatchEvent(new MouseEvent('click', {
29 view: window,
30 bubbles: true,
31 cancelable: true
32 }));
33 }
34 };
35 xhr.send();
36 }
37 });
1 /* global wp_smush_mixpanel */
2
3 import mixpanel from "mixpanel-browser";
4
5 export default class MixPanel {
6 constructor() {
7 this.mixpanelInstance = mixpanel.init(wp_smush_mixpanel.token, {
8 opt_out_tracking_by_default: !wp_smush_mixpanel.opt_in,
9 loaded: (mixpanel) => {
10 mixpanel.identify(wp_smush_mixpanel.unique_id);
11 mixpanel.register(wp_smush_mixpanel.super_properties);
12 }
13 }, 'smush');
14 }
15
16 track(event, properties = {}) {
17 this.mixpanelInstance.track(event, properties);
18 }
19
20 trackBulkSmushCompleted(totalSavingsSize, totalImageCount, optimizationPercentage, savingsPercentage) {
21 this.track('Bulk Smush Completed', {
22 'Total Savings': totalSavingsSize,
23 'Total Images': totalImageCount,
24 'Media Optimization Percentage': optimizationPercentage,
25 'Percentage of Savings': savingsPercentage
26 });
27 }
28
29 trackBulkSmushCancel() {
30 this.track('Bulk Smush Cancelled');
31 }
32 }
1 jQuery(function ($) {
2 'use strict';
3
4 /**
5 * Handle the Smush Stats link click
6 */
7 $('body').on('click', 'a.smush-stats-details', function (e) {
8 //If disabled
9 if ($(this).prop('disabled')) {
10 return false;
11 }
12
13 // prevent the default action
14 e.preventDefault();
15 //Replace the `+` with a `-`
16 const slide_symbol = $(this).find('.stats-toggle');
17 $(this).parents().eq(1).find('.smush-stats-wrapper').slideToggle();
18 slide_symbol.text(slide_symbol.text() == '+' ? '-' : '+');
19 });
20 });
...\ No newline at end of file ...\ No newline at end of file
1 /* global WP_Smush */
2 /* global ajaxurl */
3 /* global _ */
4
5 import MixPanel from "../mixpanel";
6
7 /**
8 * Bulk restore JavaScript code.
9 *
10 * @since 3.2.2
11 */
12 (function () {
13 'use strict';
14
15 /**
16 * Bulk restore modal.
17 *
18 * @since 3.2.2
19 */
20 WP_Smush.restore = {
21 modal: document.getElementById('smush-restore-images-dialog'),
22 contentContainer: document.getElementById('smush-bulk-restore-content'),
23 settings: {
24 slide: 'start', // start, progress or finish.
25 success: 0,
26 errors: [],
27 },
28 items: [], // total items, 1 item = 1 step.
29 success: [], // successful items restored.
30 errors: [], // failed items.
31 currentStep: 0,
32 totalSteps: 0,
33
34 /**
35 * Init module.
36 */
37 init() {
38 if (!this.modal) {
39 return;
40 }
41
42 this.settings = {
43 slide: 'start',
44 success: 0,
45 errors: [],
46 };
47
48 this.mixPanel = new MixPanel();
49
50 this.resetModalWidth();
51 this.renderTemplate();
52
53 // Show the modal.
54
55 window.SUI.openModal(
56 'smush-restore-images-dialog',
57 'wpbody-content',
58 undefined,
59 false
60 );
61 },
62
63 /**
64 * Update the template, register new listeners.
65 */
66 renderTemplate() {
67 const template = WP_Smush.onboarding.template('smush-bulk-restore');
68 const content = template(this.settings);
69
70 if (content) {
71 this.contentContainer.innerHTML = content;
72 }
73
74 this.bindSubmit();
75 },
76
77 /**
78 * Reset modal width.
79 *
80 * @since 3.6.0
81 */
82 resetModalWidth() {
83 this.modal.style.maxWidth = '460px';
84 this.modal.querySelector('.sui-box').style.maxWidth = '460px';
85 },
86
87 /**
88 * Catch "Finish setup wizard" button click.
89 */
90 bindSubmit() {
91 const confirmButton = this.modal.querySelector(
92 'button[id="smush-bulk-restore-button"]'
93 );
94 const self = this;
95
96 if (confirmButton) {
97 confirmButton.addEventListener('click', function (e) {
98 e.preventDefault();
99 self.resetModalWidth();
100
101 self.settings = { slide: 'progress' };
102 self.errors = [];
103
104 self.renderTemplate();
105 self.initScan();
106
107 self.mixPanel.track('Bulk Restore Triggered');
108 });
109 }
110 },
111
112 /**
113 * Cancel the bulk restore.
114 */
115 cancel() {
116 if (
117 'start' === this.settings.slide ||
118 'finish' === this.settings.slide
119 ) {
120 // Hide the modal.
121 window.SUI.closeModal();
122 } else {
123 this.updateProgressBar(true);
124 window.location.reload();
125 }
126 },
127
128 /**
129 * Update progress bar during directory smush.
130 *
131 * @param {boolean} cancel Cancel status.
132 */
133 updateProgressBar(cancel = false) {
134 let progress = 0;
135 if (0 < this.currentStep) {
136 progress = Math.min(
137 Math.round((this.currentStep * 100) / this.totalSteps),
138 99
139 );
140 }
141
142 if (progress > 100) {
143 progress = 100;
144 }
145
146 // Update progress bar
147 this.modal.querySelector('.sui-progress-text span').innerHTML =
148 progress + '%';
149 this.modal.querySelector('.sui-progress-bar span').style.width =
150 progress + '%';
151
152 const statusDiv = this.modal.querySelector(
153 '.sui-progress-state-text'
154 );
155 if (progress >= 90) {
156 statusDiv.innerHTML = 'Finalizing...';
157 } else if (cancel) {
158 statusDiv.innerHTML = 'Cancelling...';
159 } else {
160 statusDiv.innerHTML =
161 this.currentStep +
162 '/' +
163 this.totalSteps +
164 ' ' +
165 'images restored';
166 }
167 },
168
169 /**
170 * First step in bulk restore - get the bulk attachment count.
171 */
172 initScan() {
173 const self = this;
174 const _nonce = document.getElementById('_wpnonce');
175
176 const xhr = new XMLHttpRequest();
177 xhr.open('POST', ajaxurl + '?action=get_image_count', true);
178 xhr.setRequestHeader(
179 'Content-type',
180 'application/x-www-form-urlencoded'
181 );
182 xhr.onload = () => {
183 if (200 === xhr.status) {
184 const res = JSON.parse(xhr.response);
185 if ('undefined' !== typeof res.data.items) {
186 self.items = res.data.items;
187 self.totalSteps = res.data.items.length;
188 self.step();
189 }
190 } else {
191 window.console.log(
192 'Request failed. Returned status of ' + xhr.status
193 );
194 }
195 };
196 xhr.send('_ajax_nonce=' + _nonce.value);
197 },
198
199 /**
200 * Execute a scan step recursively
201 */
202 step() {
203 const self = this;
204 const _nonce = document.getElementById('_wpnonce');
205
206 if (0 < this.items.length) {
207 const item = this.items.pop();
208 const xhr = new XMLHttpRequest();
209 xhr.open('POST', ajaxurl + '?action=restore_step', true);
210 xhr.setRequestHeader(
211 'Content-type',
212 'application/x-www-form-urlencoded'
213 );
214 xhr.onload = () => {
215 this.currentStep++;
216
217 if (200 === xhr.status) {
218 const res = JSON.parse(xhr.response);
219 if (
220 'undefined' !== typeof res.data.success &&
221 res.data.success
222 ) {
223 self.success.push(item);
224 } else {
225 self.errors.push({
226 id: item,
227 src: res.data.src,
228 thumb: res.data.thumb,
229 link: res.data.link,
230 });
231 }
232 }
233
234 self.updateProgressBar();
235 self.step();
236 };
237 xhr.send('item=' + item + '&_ajax_nonce=' + _nonce.value);
238 } else {
239 // Finish.
240 this.settings = {
241 slide: 'finish',
242 success: this.success.length,
243 errors: this.errors,
244 total: this.totalSteps,
245 };
246
247 self.renderTemplate();
248 if (0 < this.errors.length) {
249 this.modal.style.maxWidth = '660px';
250 this.modal.querySelector('.sui-box').style.maxWidth =
251 '660px';
252 }
253 }
254 },
255 };
256
257 /**
258 * Template function (underscores based).
259 *
260 * @type {Function}
261 */
262 WP_Smush.restore.template = _.memoize((id) => {
263 let compiled;
264 const options = {
265 evaluate: /<#([\s\S]+?)#>/g,
266 interpolate: /{{{([\s\S]+?)}}}/g,
267 escape: /{{([^}]+?)}}(?!})/g,
268 variable: 'data',
269 };
270
271 return (data) => {
272 _.templateSettings = options;
273 compiled =
274 compiled || _.template(document.getElementById(id).innerHTML);
275 return compiled(data);
276 };
277 });
278 })();
1 /* global WP_Smush */
2 /* global ajaxurl */
3
4 /**
5 * Bulk Smush functionality.
6 *
7 * @since 2.9.0 Moved from admin.js
8 */
9
10 import Smush from '../smush/smush';
11 import Fetcher from '../utils/fetcher';
12
13 ( function( $ ) {
14 'use strict';
15
16 WP_Smush.bulk = {
17 init: () => {
18 /**
19 * Handle the Bulk Smush/Bulk re-Smush button click.
20 */
21 $( '.wp-smush-all' ).on( 'click', function( e ) {
22 e.preventDefault();
23
24 const bulkRunning = document.getElementById(
25 'wp-smush-running-notice'
26 );
27 bulkRunning.classList.add( 'sui-hidden' );
28
29 // Remove limit exceeded styles.
30 const progress = $( '.wp-smush-bulk-progress-bar-wrapper' );
31 // TODO: we don't have wp-smush-exceed-limit remove the following line and test
32 progress.removeClass( 'wp-smush-exceed-limit' );
33 progress
34 .find( '.sui-progress-block .wp-smush-all' )
35 .addClass( 'sui-hidden' );
36 progress
37 .find( '.sui-progress-block .wp-smush-cancel-bulk' )
38 .removeClass( 'sui-hidden' );
39 if ( bulkRunning ) {
40 document
41 .getElementById( 'bulk-smush-resume-button' )
42 .classList.add( 'sui-hidden' );
43 }
44
45 // remove smush-limit-reached-notice.
46 const limitReachedNotice = document.getElementById( 'smush-limit-reached-notice' );
47 if ( limitReachedNotice ) {
48 limitReachedNotice.classList.add( 'sui-hidden' );
49 }
50
51 // Disable re-Smush and scan button.
52 // TODO: refine what is disabled.
53 $(
54 '.wp-resmush.wp-smush-action, .wp-smush-scan, .wp-smush-all:not(.sui-progress-close), a.wp-smush-lossy-enable, button.wp-smush-resize-enable, button#save-settings-button'
55 ).prop( 'disabled', true );
56
57 // Check for IDs, if there is none (unsmushed or lossless), don't call Smush function.
58 /** @param {Array} wp_smushit_data.unsmushed */
59 if (
60 'undefined' === typeof window.wp_smushit_data ||
61 ( 0 === window.wp_smushit_data.unsmushed.length &&
62 0 === window.wp_smushit_data.resmush.length )
63 ) {
64 return false;
65 }
66
67 $( '.wp-smush-remaining' ).addClass( 'sui-hidden' );
68
69 WP_Smush.bulk.maybeShowCDNUpsellForPreSiteOnStart();
70
71 // Show loader.
72 progress
73 .find( '.sui-progress-block i.sui-icon-info' )
74 .removeClass( 'sui-icon-info' )
75 .addClass( 'sui-loading' )
76 .addClass( 'sui-icon-loader' );
77
78 new Smush( $( this ), true );
79 } );
80
81 /**
82 * Ignore file from bulk Smush.
83 *
84 * @since 2.9.0
85 */
86 $( 'body' ).on( 'click', '.smush-ignore-image', function( e ) {
87 e.preventDefault();
88
89 const self = $( this );
90
91 self.prop( 'disabled', true );
92 self.attr( 'data-tooltip' );
93 self.removeClass( 'sui-tooltip' );
94 $.post( ajaxurl, {
95 action: 'ignore_bulk_image',
96 id: self.attr( 'data-id' ),
97 _ajax_nonce: wp_smush_msgs.nonce,
98 } ).done( ( response ) => {
99 if ( self.is( 'a' ) && response.success && 'undefined' !== typeof response.data.links ) {
100 if ( e.target.closest( '.smush-status-links' ) ) {
101 const smushStatus = self.parent().parent().find( '.smush-status' );
102 smushStatus.text( wp_smush_msgs.ignored );
103 smushStatus.addClass('smush-ignored');
104 e.target.closest( '.smush-status-links' ).innerHTML = response.data.links;
105 } else if (e.target.closest( '.smush-bulk-error-row' ) ){
106 self.addClass('disabled');
107 e.target.closest( '.smush-bulk-error-row' ).style.opacity = 0.5;
108 }
109 }
110 } );
111 } );
112
113 /**
114 * Ignore file from bulk Smush.
115 *
116 * @since 3.12.0
117 */
118 const ignoreAll = document.querySelector('.wp_smush_ignore_all_failed_items');
119 if ( ignoreAll ) {
120 ignoreAll.onclick = (e) => {
121 e.preventDefault();
122 e.target.setAttribute('disabled','');
123 e.target.style.cursor = 'progress';
124 const type = e.target.dataset.type || null;
125 e.target.classList.remove('sui-tooltip');
126 Fetcher.smush.ignoreAll(type).then((res) => {
127 if ( res.success ) {
128 window.location.reload();
129 } else {
130 e.target.style.cursor = 'pointer';
131 e.target.removeAttribute('disabled');
132 WP_Smush.helpers.showNotice( res );
133 }
134 });
135 }
136 }
137 },
138 maybeShowCDNUpsellForPreSiteOnStart: () => {
139 // Show upsell cdn.
140 const upsell_cdn = document.querySelector('.wp-smush-upsell-cdn');
141 if ( upsell_cdn ) {
142 upsell_cdn.classList.remove('sui-hidden');
143 }
144 }
145 };
146
147 WP_Smush.bulk.init();
148 } )( jQuery );
1 /* global WP_Smush */
2 /* global ajaxurl */
3
4 /**
5 * Directory Smush module JavaScript code.
6 *
7 * @since 2.8.1 Separated from admin.js into dedicated file.
8 */
9
10 import { createTree } from 'jquery.fancytree';
11 import Scanner from '../smush/directory-scanner';
12
13 ( function( $ ) {
14 'use strict';
15
16 WP_Smush.directory = {
17 selected: [],
18 tree: [],
19 wp_smush_msgs: [],
20 triggered: false,
21
22 init() {
23 const self = this,
24 progressDialog = $( '#wp-smush-progress-dialog' );
25
26 let totalSteps = 0,
27 currentScanStep = 0;
28
29 // Make sure directory smush vars are set.
30 if ( typeof window.wp_smushit_data.dir_smush !== 'undefined' ) {
31 totalSteps = window.wp_smushit_data.dir_smush.totalSteps;
32 currentScanStep =
33 window.wp_smushit_data.dir_smush.currentScanStep;
34 }
35
36 // Init image scanner.
37 this.scanner = new Scanner( totalSteps, currentScanStep );
38
39 /**
40 * Smush translation strings.
41 *
42 * @param {Array} wp_smush_msgs
43 */
44 this.wp_smush_msgs = window.wp_smush_msgs || {};
45
46 /**
47 * Open the "Select Smush directory" modal.
48 */
49 $( 'button.wp-smush-browse, a#smush-directory-open-modal' ).on(
50 'click',
51 function( e ) {
52 e.preventDefault();
53
54 if ( $( e.currentTarget ).hasClass( 'wp-smush-browse' ) ) {
55 // Hide all the notices.
56 $( 'div.wp-smush-scan-result div.wp-smush-notice' ).hide();
57
58 // Remove notice.
59 $( 'div.wp-smush-info' ).remove();
60 }
61
62 window.SUI.openModal(
63 'wp-smush-list-dialog',
64 e.currentTarget,
65 $(
66 '#wp-smush-list-dialog .sui-box-header [data-modal-close]'
67 )[0],
68 true
69 );
70 //Display File tree for Directory Smush
71 self.initFileTree();
72 }
73 );
74
75 /**
76 * Smush images: Smush in Choose Directory modal clicked
77 */
78 $( '#wp-smush-select-dir' ).on( 'click', function( e ) {
79 e.preventDefault();
80
81 $( 'div.wp-smush-list-dialog div.sui-box-body' ).css( {
82 opacity: '0.8',
83 } );
84 $( 'div.wp-smush-list-dialog div.sui-box-body a' ).off(
85 'click'
86 );
87
88 const button = $( this );
89
90 // Display the spinner.
91 button.addClass('sui-button-onload');
92
93 const selectedFolders = self.tree.getSelectedNodes();
94
95 const paths = [];
96 selectedFolders.forEach( function( folder ) {
97 paths.push( folder.key );
98 } );
99
100 // Send a ajax request to get a list of all the image files
101 const param = {
102 action: 'image_list',
103 smush_path: paths,
104 image_list_nonce: $(
105 'input[name="image_list_nonce"]'
106 ).val(),
107 };
108
109 $.post( ajaxurl, param, function( response ) {
110 if ( response.success ) {
111 // Close the modal.
112 window.SUI.closeModal();
113
114 self.scanner = new Scanner( response.data, 0 );
115 self.showProgressDialog( response.data );
116 self.scanner.scan();
117 } else {
118 // Remove the spinner.
119 button.removeClass('sui-button-onload');
120
121 window.SUI.openNotice(
122 'wp-smush-ajax-notice',
123 response.data.message,
124 { type: 'warning' }
125 );
126 }
127 } );
128 } );
129
130 /**
131 * Cancel scan.
132 */
133 progressDialog.on(
134 'click',
135 '#cancel-directory-smush, #dialog-close-div, .wp-smush-cancel-dir',
136 function( e ) {
137 e.preventDefault();
138 // Display the spinner
139 $( '.wp-smush-cancel-dir' ).addClass( 'sui-button-onload' );
140 self.scanner
141 .cancel()
142 .done(
143 () =>
144 ( window.location.href =
145 self.wp_smush_msgs.directory_url )
146 );
147 }
148 );
149
150 /**
151 * Continue scan.
152 */
153 progressDialog.on(
154 'click',
155 '.sui-icon-play, .wp-smush-resume-scan',
156 function( e ) {
157 e.preventDefault();
158 self.scanner.resume();
159 }
160 );
161
162 /**
163 * Check to see if we should open the directory module.
164 * Used to redirect from dashboard page.
165 *
166 * @since 3.8.6
167 */
168 const queryString = window.location.search;
169 const urlParams = new URLSearchParams( queryString );
170 if ( urlParams.has( 'start' ) && ! this.triggered ) {
171 this.triggered = true;
172 $( 'button.wp-smush-browse' ).trigger( 'click' );
173 }
174 },
175
176 /**
177 * Init fileTree.
178 */
179 initFileTree() {
180 const self = this,
181 smushButton = $( 'button#wp-smush-select-dir' ),
182 ajaxSettings = {
183 type: 'GET',
184 url: ajaxurl,
185 data: {
186 action: 'smush_get_directory_list',
187 list_nonce: $( 'input[name="list_nonce"]' ).val(),
188 },
189 cache: false,
190 };
191
192 // Object already defined.
193 if ( Object.entries( self.tree ).length > 0 ) {
194 return;
195 }
196
197 self.tree = createTree( '.wp-smush-list-dialog .content', {
198 autoCollapse: true, // Automatically collapse all siblings, when a node is expanded
199 clickFolderMode: 3, // 1:activate, 2:expand, 3:activate and expand, 4:activate (dblclick expands)
200 checkbox: true, // Show checkboxes
201 debugLevel: 0, // 0:quiet, 1:errors, 2:warnings, 3:infos, 4:debug
202 selectMode: 3, // 1:single, 2:multi, 3:multi-hier
203 tabindex: '0', // Whole tree behaves as one single control
204 keyboard: true, // Support keyboard navigation
205 quicksearch: true, // Navigate to next node by typing the first letters
206 source: ajaxSettings,
207 lazyLoad: ( event, data ) => {
208 data.result = new Promise( function( resolve, reject ) {
209 ajaxSettings.data.dir = data.node.key;
210 $.ajax( ajaxSettings )
211 .done( ( response ) => resolve( response ) )
212 .fail( reject );
213 } );
214 },
215 loadChildren: ( event, data ) =>
216 data.node.fixSelection3AfterClick(), // Apply parent's state to new child nodes:
217 select: () =>
218 smushButton.prop(
219 'disabled',
220 ! +self.tree.getSelectedNodes().length
221 ),
222 init: () => smushButton.prop( 'disabled', true ),
223 } );
224 },
225
226 /**
227 * Show progress dialog.
228 *
229 * @param {number} items Number of items in the scan.
230 */
231 showProgressDialog( items ) {
232 // Update items status and show the progress dialog..
233 $( '.wp-smush-progress-dialog .sui-progress-state-text' ).html(
234 '0/' + items + ' ' + self.wp_smush_msgs.progress_smushed
235 );
236
237 window.SUI.openModal(
238 'wp-smush-progress-dialog',
239 'dialog-close-div',
240 undefined,
241 false
242 );
243 },
244
245 /**
246 * Update progress bar during directory smush.
247 *
248 * @param {number} progress Current progress in percent.
249 * @param {boolean} cancel Cancel status.
250 */
251 updateProgressBar( progress, cancel = false ) {
252 if ( progress > 100 ) {
253 progress = 100;
254 }
255
256 // Update progress bar
257 $( '.sui-progress-block .sui-progress-text span' ).text(
258 progress + '%'
259 );
260 $( '.sui-progress-block .sui-progress-bar span' ).width(
261 progress + '%'
262 );
263
264 if ( progress >= 90 ) {
265 $( '.sui-progress-state .sui-progress-state-text' ).text(
266 'Finalizing...'
267 );
268 }
269
270 if ( cancel ) {
271 $( '.sui-progress-state .sui-progress-state-text' ).text(
272 'Cancelling...'
273 );
274 }
275 },
276 };
277
278 WP_Smush.directory.init();
279 }( jQuery ) );
1 /* global WP_Smush */
2 /* global ajaxurl */
3 /* global wp_smush_msgs */
4
5 /**
6 * Helpers functions.
7 *
8 * @since 2.9.0 Moved from admin.js
9 */
10 ( function() {
11 'use strict';
12
13 WP_Smush.helpers = {
14 cacheUpsellErrorCodes: [],
15 init: () => {},
16
17 /**
18 * Convert bytes to human-readable form.
19 *
20 * @param {number} a Bytes
21 * @param {number} b Number of digits
22 * @return {*} Formatted Bytes
23 */
24 formatBytes: ( a, b ) => {
25 const thresh = 1024,
26 units = [ 'KB', 'MB', 'GB', 'TB', 'PB' ];
27
28 if ( Math.abs( a ) < thresh ) {
29 return a + ' B';
30 }
31
32 let u = -1;
33
34 do {
35 a /= thresh;
36 ++u;
37 } while ( Math.abs( a ) >= thresh && u < units.length - 1 );
38
39 return a.toFixed( b ) + ' ' + units[ u ];
40 },
41
42 /**
43 * Get size from a string.
44 *
45 * @param {string} formattedSize Formatter string
46 * @return {*} Formatted Bytes
47 */
48 getSizeFromString: ( formattedSize ) => {
49 return formattedSize.replace( /[a-zA-Z]/g, '' ).trim();
50 },
51
52 /**
53 * Get type from formatted string.
54 *
55 * @param {string} formattedSize Formatted string
56 * @return {*} Formatted Bytes
57 */
58 getFormatFromString: ( formattedSize ) => {
59 return formattedSize.replace( /[0-9.]/g, '' ).trim();
60 },
61
62 /**
63 * Stackoverflow: http://stackoverflow.com/questions/1726630/formatting-a-number-with-exactly-two-decimals-in-javascript
64 *
65 * @param {number} num
66 * @param {number} decimals
67 * @return {number} Number
68 */
69 precise_round: ( num, decimals ) => {
70 const sign = num >= 0 ? 1 : -1;
71 // Keep the percentage below 100.
72 num = num > 100 ? 100 : num;
73 return (
74 Math.round( num * Math.pow( 10, decimals ) + sign * 0.001 ) /
75 Math.pow( 10, decimals )
76 );
77 },
78
79 /**
80 * Displays a floating error message using the #wp-smush-ajax-notice container.
81 *
82 * @since 3.8.0
83 *
84 * @param {string} message
85 */
86 showErrorNotice: ( message ) => {
87 if ( 'undefined' === typeof message ) {
88 return;
89 }
90
91 const noticeMessage = `<p>${ message }</p>`,
92 noticeOptions = {
93 type: 'error',
94 icon: 'info',
95 };
96
97 SUI.openNotice( 'wp-smush-ajax-notice', noticeMessage, noticeOptions );
98
99 const loadingButton = document.querySelector( '.sui-button-onload' );
100 if ( loadingButton ) {
101 loadingButton.classList.remove( 'sui-button-onload' );
102 }
103 },
104
105 /**
106 * Reset settings.
107 *
108 * @since 3.2.0
109 */
110 resetSettings: () => {
111 const _nonce = document.getElementById( 'wp_smush_reset' );
112 const xhr = new XMLHttpRequest();
113 xhr.open( 'POST', ajaxurl + '?action=reset_settings', true );
114 xhr.setRequestHeader(
115 'Content-type',
116 'application/x-www-form-urlencoded'
117 );
118 xhr.onload = () => {
119 if ( 200 === xhr.status ) {
120 const res = JSON.parse( xhr.response );
121 if ( 'undefined' !== typeof res.success && res.success ) {
122 window.location.href = wp_smush_msgs.smush_url;
123 }
124 } else {
125 window.console.log(
126 'Request failed. Returned status of ' + xhr.status
127 );
128 }
129 };
130 xhr.send( '_ajax_nonce=' + _nonce.value );
131 },
132
133 /**
134 * Prepare error row. Will only allow to hide errors for WP media attachments (not nextgen).
135 *
136 * @since 1.9.0
137 * @since 3.12.0 Moved from Smush.
138 *
139 * @param {string} errorMsg Error message.
140 * @param {string} fileName File name.
141 * @param {string} thumbnail Thumbnail for image (if available).
142 * @param {number} id Image ID.
143 * @param {string} type Smush type: media or netxgen.
144 * @param {string} errorCode Error code.
145 *
146 * @return {string} Row with error.
147 */
148 prepareBulkSmushErrorRow: (errorMsg, fileName, thumbnail, id, type, errorCode) => {
149 const thumbDiv =
150 'undefined' === typeof thumbnail ?
151 '<i class="sui-icon-photo-picture" aria-hidden="true"></i>' :
152 thumbnail;
153 const editLink = window.wp_smush_msgs.edit_link.replace('{{id}}', id);
154 const fileLink =
155 'undefined' === fileName || 'undefined' === typeof fileName ?
156 'undefined' :
157 fileName;
158
159 let tableDiv =
160 '<div class="smush-bulk-error-row" data-error-code="'+ errorCode + '">' +
161 '<div class="smush-bulk-image-data">' +
162 '<div class="smush-bulk-image-title">' +
163 thumbDiv +
164 '<span class="smush-image-name">' +
165 fileLink +
166 '</span>' +
167 '</div>' +
168 '<div class="smush-image-error">' +
169 errorMsg +
170 '</div>' +
171 '</div>';
172
173 if ('media' === type) {
174 tableDiv =
175 tableDiv +
176 '<div class="smush-bulk-image-actions">' +
177 '<a href="javascript:void(0)" class="sui-tooltip sui-tooltip-constrained sui-tooltip-left smush-ignore-image" data-tooltip="' +
178 window.wp_smush_msgs.error_ignore +
179 '" data-id="' +
180 id +
181 '">' +
182 window.wp_smush_msgs.btn_ignore +
183 '</a>' +
184 '<a class="smush-link-detail" href="' + editLink + '">' +
185 window.wp_smush_msgs.view_detail +
186 '</a>' +
187 '</div>';
188 }
189
190 tableDiv = tableDiv + '</div>';
191
192 tableDiv += WP_Smush.helpers.upsellWithError(errorCode);
193
194 return tableDiv;
195 },
196 /**
197 * Get upsell base on error code.
198 * @param {string} errorCode Error code.
199 * @returns {string}
200 *
201 * Do not use arrow function to use `this`.
202 */
203 upsellWithError: function (errorCode) {
204 if (!errorCode || !window.wp_smush_msgs['error_' + errorCode] || this.isUpsellRendered( errorCode )) {
205 return '';
206 }
207 this.cacheRenderedUpsell( errorCode );
208 return '<div class="smush-bulk-error-row smush-error-upsell">' +
209 '<div class="smush-bulk-image-title">' +
210 '<span class="smush-image-error">' +
211 window.wp_smush_msgs['error_' + errorCode] +
212 '</span>' +
213 '</div></div>';
214 },
215 // Do not use arrow function to use `this`.
216 isUpsellRendered: function( errorCode ) {
217 return this.cacheUpsellErrorCodes.includes( errorCode );
218 },
219 // Do not use arrow function to use `this`.
220 cacheRenderedUpsell: function ( errorCode ) {
221 this.cacheUpsellErrorCodes.push( errorCode );
222 },
223 /**
224 * Get error message from Ajax response or Error.
225 * @param {Object} resp
226 */
227 getErrorMessage: ( resp ) => {
228 return resp.message || resp.data && resp.data.message ||
229 resp.responseJSON && resp.responseJSON.data && resp.responseJSON.data.message ||
230 window.wp_smush_msgs.generic_ajax_error ||
231 resp.status && 'Request failed. Returned status of ' + resp.status
232 },
233
234 /**
235 * Displays a floating message from response,
236 * using the #wp-smush-ajax-notice container.
237 *
238 * @param {Object|string} notice
239 * @param {Object} noticeOptions
240 */
241 showNotice: function( notice, noticeOptions ) {
242 let message;
243 if ( 'object' === typeof notice ) {
244 message = this.getErrorMessage( notice );
245 } else {
246 message = notice;
247 }
248
249 if ( ! message ) {
250 return;
251 }
252
253 noticeOptions = noticeOptions || {};
254 noticeOptions = Object.assign({
255 showdismiss: false,
256 autoclose: true,
257 },noticeOptions);
258 noticeOptions = {
259 type: noticeOptions.type || 'error',
260 icon: noticeOptions.icon || ( 'success' === noticeOptions.type ? 'check-tick' : 'info' ),
261 dismiss: {
262 show: noticeOptions.showdismiss,
263 label: window.wp_smush_msgs.noticeDismiss,
264 tooltip: window.wp_smush_msgs.noticeDismissTooltip,
265 },
266 autoclose: {
267 show: noticeOptions.autoclose
268 }
269 };
270
271 const noticeMessage = `<p>${ message }</p>`;
272
273 SUI.openNotice( 'wp-smush-ajax-notice', noticeMessage, noticeOptions );
274 return Promise.resolve( '#wp-smush-ajax-notice' );
275 },
276 renderActivationCDNNotice: function( noticeMessage ) {
277 const animatedNotice = document.getElementById('wp-smush-animated-upsell-notice');
278 if ( animatedNotice ) {
279 return;
280 }
281 const upsellHtml = `<div class="sui-notice sui-notice-info sui-margin-top" id="wp-smush-animated-upsell-notice">
282 <div class="sui-notice-content">
283 <div class="sui-notice-message">
284 <i class="sui-notice-icon sui-icon-info" aria-hidden="true"></i>
285 <p>${noticeMessage}</p>
286 </div>
287 </div>
288 </div>`;
289 document.querySelector( '#smush-box-bulk .wp-smush-bulk-wrapper' ).outerHTML += upsellHtml;
290 }
291 };
292
293 WP_Smush.helpers.init();
294 }() );
1 /* global ajaxurl */
2 /* global wp_smush_msgs */
3
4 ( function( $ ) {
5 'use strict';
6
7 const s3alert = $( '#wp-smush-s3support-alert' );
8
9 /**
10 * S3 support alert.
11 *
12 * @since 3.6.2 Moved from class-s3.php
13 */
14 if ( s3alert.length ) {
15 const noticeOptions = {
16 type: 'warning',
17 icon: 'info',
18 dismiss: {
19 show: true,
20 label: wp_smush_msgs.noticeDismiss,
21 tooltip: wp_smush_msgs.noticeDismissTooltip,
22 },
23 };
24
25 window.SUI.openNotice(
26 'wp-smush-s3support-alert',
27 s3alert.data( 'message' ),
28 noticeOptions
29 );
30 }
31
32 // Dismiss S3 support alert.
33 s3alert.on( 'click', 'button', () => {
34 $.post( ajaxurl,
35 {
36 action: 'dismiss_s3support_alert',
37 _ajax_nonce: window.wp_smush_msgs.nonce,
38 }
39 );
40 } );
41
42 // Remove API message.
43 $( '#wp-smush-api-message button.sui-button-icon' ).on( 'click', function( e ) {
44 e.preventDefault();
45 const notice = $( '#wp-smush-api-message' );
46 notice.slideUp( 'slow', function() {
47 notice.remove();
48 } );
49 $.post( ajaxurl,
50 {
51 action: 'hide_api_message',
52 _ajax_nonce: window.wp_smush_msgs.nonce,
53 }
54 );
55 } );
56
57 // Hide the notice after a CTA button was clicked
58 function removeNotice( e ) {
59 const $notice = $( e.currentTarget ).closest( '.smush-notice' );
60 $notice.fadeTo( 100, 0, () =>
61 $notice.slideUp( 100, () => $notice.remove() )
62 );
63 }
64
65 // Only used for the Dashboard notification for now.
66 $( '.smush-notice .smush-notice-act' ).on( 'click', ( e ) => {
67 removeNotice( e );
68 } );
69
70 // Dismiss the update notice.
71 $( '.wp-smush-update-info' ).on( 'click', '.notice-dismiss', ( e ) => {
72 e.preventDefault();
73 removeNotice( e );
74 $.post( ajaxurl,
75 {
76 action: 'dismiss_update_info',
77 _ajax_nonce: window.wp_smush_msgs.nonce,
78 }
79 );
80 } );
81 }( jQuery ) );
1 /* global WP_Smush */
2 /* global ajaxurl */
3
4 /**
5 * Modals JavaScript code.
6 */
7 ( function() {
8 'use strict';
9
10 /**
11 * Onboarding modal.
12 *
13 * @since 3.1
14 */
15 WP_Smush.onboarding = {
16 membership: 'free', // Assume free by default.
17 onboardingModal: document.getElementById( 'smush-onboarding-dialog' ),
18 scanFilesModal: document.getElementById( 'checking-files-dialog' ),
19 first_slide: 'usage',
20 settings: {
21 first: true,
22 last: false,
23 slide: 'usage',
24 value: false,
25 },
26 selection: {
27 usage: false,
28 auto: true,
29 lossy: true,
30 strip_exif: true,
31 original: false,
32 lazy_load: true,
33 },
34 contentContainer: document.getElementById( 'smush-onboarding-content' ),
35 onboardingSlides: [
36 'usage',
37 'auto',
38 'lossy',
39 'strip_exif',
40 'original',
41 'lazy_load',
42 ],
43 touchX: null,
44 touchY: null,
45
46 /**
47 * Init module.
48 */
49 init() {
50 if ( ! this.onboardingModal ) {
51 return;
52 }
53
54 const dialog = document.getElementById( 'smush-onboarding' );
55
56 this.membership = dialog.dataset.type;
57
58 if ( 'pro' !== this.membership ) {
59 this.onboardingSlides = [
60 'usage',
61 'auto',
62 'lossy',
63 'strip_exif',
64 'lazy_load',
65 ];
66 }
67
68 if ( 'false' === dialog.dataset.tracking ) {
69 this.onboardingSlides.pop();
70 }
71
72 this.renderTemplate();
73
74 // Skip setup.
75 const skipButton = this.onboardingModal.querySelector(
76 '.smush-onboarding-skip-link'
77 );
78 if ( skipButton ) {
79 skipButton.addEventListener( 'click', this.skipSetup );
80 }
81
82 // Show the modal.
83 window.SUI.openModal(
84 'smush-onboarding-dialog',
85 'checking-files-dialog',
86 undefined,
87 false
88 );
89 },
90
91 /**
92 * Get swipe coordinates.
93 *
94 * @param {Object} e
95 */
96 handleTouchStart( e ) {
97 const firstTouch = e.touches[ 0 ];
98 this.touchX = firstTouch.clientX;
99 this.touchY = firstTouch.clientY;
100 },
101
102 /**
103 * Process swipe left/right.
104 *
105 * @param {Object} e
106 */
107 handleTouchMove( e ) {
108 if ( ! this.touchX || ! this.touchY ) {
109 return;
110 }
111
112 const xUp = e.touches[ 0 ].clientX,
113 yUp = e.touches[ 0 ].clientY,
114 xDiff = this.touchX - xUp,
115 yDiff = this.touchY - yUp;
116
117 if ( Math.abs( xDiff ) > Math.abs( yDiff ) ) {
118 if ( xDiff > 0 ) {
119 if ( false === WP_Smush.onboarding.settings.last ) {
120 WP_Smush.onboarding.next( null, 'next' );
121 }
122 } else if ( false === WP_Smush.onboarding.settings.first ) {
123 WP_Smush.onboarding.next( null, 'prev' );
124 }
125 }
126
127 this.touchX = null;
128 this.touchY = null;
129 },
130
131 /**
132 * Update the template, register new listeners.
133 *
134 * @param {string} directionClass Accepts: fadeInRight, fadeInLeft, none.
135 */
136 renderTemplate( directionClass = 'none' ) {
137 // Grab the selected value.
138 const input = this.onboardingModal.querySelector(
139 'input[type="checkbox"]'
140 );
141 if ( input ) {
142 this.selection[ input.id ] = input.checked;
143 }
144
145 const template = WP_Smush.onboarding.template( 'smush-onboarding' );
146 const content = template( this.settings );
147
148 if ( content ) {
149 this.contentContainer.innerHTML = content;
150
151 if ( 'none' === directionClass ) {
152 this.contentContainer.classList.add( 'loaded' );
153 } else {
154 this.contentContainer.classList.remove( 'loaded' );
155 this.contentContainer.classList.add( directionClass );
156 setTimeout( () => {
157 this.contentContainer.classList.add( 'loaded' );
158 this.contentContainer.classList.remove(
159 directionClass
160 );
161 }, 600 );
162 }
163 }
164
165 this.onboardingModal.addEventListener(
166 'touchstart',
167 this.handleTouchStart,
168 false
169 );
170 this.onboardingModal.addEventListener(
171 'touchmove',
172 this.handleTouchMove,
173 false
174 );
175
176 this.bindSubmit();
177 },
178
179 /**
180 * Catch "Finish setup wizard" button click.
181 */
182 bindSubmit() {
183 const submitButton = this.onboardingModal.querySelector(
184 'button[type="submit"]'
185 );
186 const self = this;
187
188 if ( submitButton ) {
189 submitButton.addEventListener( 'click', function( e ) {
190 e.preventDefault();
191
192 // Because we are not rendering the template, we need to update the last element value.
193 const input = self.onboardingModal.querySelector(
194 'input[type="checkbox"]'
195 );
196 if ( input ) {
197 self.selection[ input.id ] = input.checked;
198 }
199
200 const _nonce = document.getElementById(
201 'smush_quick_setup_nonce'
202 );
203
204 const xhr = new XMLHttpRequest();
205 xhr.open( 'POST', ajaxurl + '?action=smush_setup', true );
206 xhr.setRequestHeader(
207 'Content-type',
208 'application/x-www-form-urlencoded'
209 );
210 xhr.onload = () => {
211 if ( 200 === xhr.status ) {
212 WP_Smush.onboarding.showScanDialog();
213 } else {
214 window.console.log(
215 'Request failed. Returned status of ' +
216 xhr.status
217 );
218 }
219 };
220 xhr.send(
221 'smush_settings=' +
222 JSON.stringify( self.selection ) +
223 '&_ajax_nonce=' +
224 _nonce.value
225 );
226 } );
227 }
228 },
229
230 /**
231 * Handle navigation.
232 *
233 * @param {Object} e
234 * @param {null|string} whereTo
235 */
236 next( e, whereTo = null ) {
237 const index = this.onboardingSlides.indexOf( this.settings.slide );
238 let newIndex = 0;
239
240 if ( ! whereTo ) {
241 newIndex =
242 null !== e && e.classList.contains( 'next' )
243 ? index + 1
244 : index - 1;
245 } else {
246 newIndex = 'next' === whereTo ? index + 1 : index - 1;
247 }
248
249 const directionClass =
250 null !== e && e.classList.contains( 'next' )
251 ? 'fadeInRight'
252 : 'fadeInLeft';
253
254 this.settings = {
255 first: 0 === newIndex,
256 last: newIndex + 1 === this.onboardingSlides.length, // length !== index
257 slide: this.onboardingSlides[ newIndex ],
258 value: this.selection[ this.onboardingSlides[ newIndex ] ],
259 };
260
261 this.renderTemplate( directionClass );
262 },
263
264 /**
265 * Handle circle navigation.
266 *
267 * @param {string} target
268 */
269 goTo( target ) {
270 const newIndex = this.onboardingSlides.indexOf( target );
271
272 this.settings = {
273 first: 0 === newIndex,
274 last: newIndex + 1 === this.onboardingSlides.length, // length !== index
275 slide: target,
276 value: this.selection[ target ],
277 };
278
279 this.renderTemplate();
280 },
281
282 /**
283 * Skip onboarding experience.
284 */
285 skipSetup: () => {
286 const _nonce = document.getElementById( 'smush_quick_setup_nonce' );
287
288 const xhr = new XMLHttpRequest();
289 xhr.open(
290 'POST',
291 ajaxurl + '?action=skip_smush_setup&_ajax_nonce=' + _nonce.value
292 );
293 xhr.onload = () => {
294 if ( 200 === xhr.status ) {
295 WP_Smush.onboarding.showScanDialog();
296 } else {
297 window.console.log(
298 'Request failed. Returned status of ' + xhr.status
299 );
300 }
301 };
302 xhr.send();
303 },
304
305 /**
306 * Show checking files dialog.
307 */
308 showScanDialog() {
309 window.SUI.closeModal();
310 // Do not need to re-check images if we are in bulk smush page.
311 if ( window.location.search.indexOf('page=smush-bulk') > -1 ) {
312 return;
313 }
314 window.SUI.openModal(
315 'checking-files-dialog',
316 'wpbody-content',
317 undefined,
318 false
319 );
320
321 const nonce = document.getElementById( 'wp_smush_options_nonce' );
322
323 setTimeout( () => {
324 const xhr = new XMLHttpRequest();
325 xhr.open( 'POST', ajaxurl + '?action=scan_for_resmush', true );
326 xhr.setRequestHeader(
327 'Content-type',
328 'application/x-www-form-urlencoded'
329 );
330 xhr.onload = () => {
331 const elem = document.querySelector(
332 '#smush-onboarding-dialog'
333 );
334 elem.parentNode.removeChild( elem );
335
336 if ( 200 === xhr.status ) {
337 setTimeout( function() {
338 window.location.search = 'page=smush-bulk';
339 }, 1000 );
340 } else {
341 window.console.log(
342 'Request failed. Returned status of ' + xhr.status
343 );
344 }
345 };
346 xhr.send(
347 'type=media&get_ui=false&process_settings=false&wp_smush_options_nonce=' +
348 nonce.value
349 );
350 }, 3000 );
351 },
352
353 /**
354 * Hide new features modal.
355 *
356 * @param {string} redirectUrl Redirect url after dismissing the new feature modal.
357 * @since 3.7.0
358 * @since 3.12.2 Add a new parameter redirectUrl
359 */
360 hideUpgradeModal: ( redirectUrl ) => {
361 window.SUI.closeModal( 'smush-updated-dialog' );
362 const xhr = new XMLHttpRequest();
363 xhr.open( 'POST', ajaxurl + '?action=hide_new_features&_ajax_nonce=' + window.wp_smush_msgs.nonce );
364 xhr.onload = () => {
365 if ( 200 === xhr.status ) {
366 if ( redirectUrl ) {
367 window.location.href = redirectUrl;
368 }
369 } else {
370 window.console.log(
371 'Request failed. Returned status of ' + xhr.status
372 );
373 }
374 }
375 xhr.send();
376 },
377 };
378
379 /**
380 * Template function (underscores based).
381 *
382 * @type {Function}
383 */
384 WP_Smush.onboarding.template = _.memoize( ( id ) => {
385 let compiled;
386 const options = {
387 evaluate: /<#([\s\S]+?)#>/g,
388 interpolate: /{{{([\s\S]+?)}}}/g,
389 escape: /{{([^}]+?)}}(?!})/g,
390 variable: 'data',
391 };
392
393 return ( data ) => {
394 _.templateSettings = options;
395 compiled =
396 compiled ||
397 _.template( document.getElementById( id ).innerHTML );
398 data.first_slide = WP_Smush.onboarding.first_slide;
399 return compiled( data );
400 };
401 } );
402
403 window.addEventListener( 'load', () => WP_Smush.onboarding.init() );
404 }() );
1 /**
2 * Shared UI JS libraries. Use only what we need to keep the vendor file size smaller.
3 *
4 * @package
5 */
6 require( '@wpmudev/shared-ui/dist/js/_src/code-snippet' );
7 require( '@wpmudev/shared-ui/dist/js/_src/modal-dialog' );
8 require( '@wpmudev/shared-ui/dist/js/_src/notifications' );
9 require( '@wpmudev/shared-ui/dist/js/_src/select2.full' );
10 require( '@wpmudev/shared-ui/dist/js/_src/select2' );
11 require( '@wpmudev/shared-ui/dist/js/_src/tabs' );
12 require( '@wpmudev/shared-ui/dist/js/_src/upload' ); // Used on lazy load page (since 3.2.2).
13 require( '@wpmudev/shared-ui/dist/js/_src/reviews' );
1 /**
2 * BLOCK: extend image block
3 */
4 const { createHigherOrderComponent } = wp.compose,
5 { Fragment } = wp.element,
6 { InspectorControls } = wp.blockEditor,
7 { PanelBody } = wp.components;
8
9 /**
10 * Transform bytes to human readable format.
11 *
12 * @param {number} bytes
13 * @return {string} Readable size string.
14 */
15 function humanFileSize( bytes ) {
16 const thresh = 1024,
17 units = [ 'kB', 'MB', 'GB', 'TB' ];
18
19 if ( Math.abs( bytes ) < thresh ) {
20 return bytes + ' B';
21 }
22
23 let u = -1;
24 do {
25 bytes /= thresh;
26 ++u;
27 } while ( Math.abs( bytes ) >= thresh && u < units.length - 1 );
28
29 return bytes.toFixed( 1 ) + ' ' + units[ u ];
30 }
31
32 /**
33 * Generate Smush stats table.
34 *
35 * @param {number} id
36 * @param {Object} stats
37 * @return {*} Smush stats.
38 */
39 export function smushStats( id, stats ) {
40 if ( 'undefined' === typeof stats ) {
41 return window.smush_vars.strings.gb.select_image;
42 } else if ( 'string' === typeof stats ) {
43 return stats;
44 }
45
46 return (
47 <div
48 id="smush-stats"
49 className="sui-smush-media smush-stats-wrapper hidden"
50 style={ { display: 'block' } }
51 >
52 <table className="wp-smush-stats-holder">
53 <thead>
54 <tr>
55 <th className="smush-stats-header">
56 { window.smush_vars.strings.gb.size }
57 </th>
58 <th className="smush-stats-header">
59 { window.smush_vars.strings.gb.savings }
60 </th>
61 </tr>
62 </thead>
63 <tbody>
64 { Object.keys( stats.sizes )
65 .filter( ( item ) => 0 < stats.sizes[ item ].percent )
66 .map( ( item, i ) => (
67 <tr key={ i }>
68 <td>{ item.toUpperCase() }</td>
69 <td>
70 { humanFileSize(
71 stats.sizes[ item ].bytes
72 ) }{ ' ' }
73 ( { stats.sizes[ item ].percent }% )
74 </td>
75 </tr>
76 ) ) }
77 </tbody>
78 </table>
79 </div>
80 );
81 }
82
83 /**
84 * Fetch image data. If image is Smushing, update in 3 seconds.
85 *
86 * TODO: this could be optimized not to query so much.
87 *
88 * @param {Object} props
89 */
90 export function fetchProps( props ) {
91 const image = new wp.api.models.Media( { id: props.attributes.id } ),
92 smushData = props.attributes.smush;
93
94 image.fetch( { attribute: 'smush' } ).done( function( img ) {
95 if ( 'string' === typeof img.smush ) {
96 props.setAttributes( { smush: img.smush } );
97 //setTimeout( () => fetch( props ), 3000 );
98 } else if (
99 'undefined' !== typeof img.smush &&
100 ( 'undefined' === typeof smushData ||
101 JSON.stringify( smushData ) !== JSON.stringify( img.smush ) )
102 ) {
103 props.setAttributes( { smush: img.smush } );
104 }
105 } );
106 }
107
108 /**
109 * Modify the block’s edit component.
110 * Receives the original block BlockEdit component and returns a new wrapped component.
111 */
112 const smushStatsControl = createHigherOrderComponent( ( BlockEdit ) => {
113 return ( props ) => {
114 // If not image block or not selected, return unmodified block.
115 if (
116 'core/image' !== props.name ||
117 ! props.isSelected ||
118 'undefined' === typeof props.attributes.id
119 ) {
120 return (
121 <Fragment>
122 <BlockEdit { ...props } />
123 </Fragment>
124 );
125 }
126
127 const smushData = props.attributes.smush;
128 fetchProps( props );
129
130 return (
131 <Fragment>
132 <BlockEdit { ...props } />
133 <InspectorControls>
134 <PanelBody title={ window.smush_vars.strings.gb.stats }>
135 { smushStats( props.attributes.id, smushData ) }
136 </PanelBody>
137 </InspectorControls>
138 </Fragment>
139 );
140 };
141 }, 'withInspectorControl' );
142
143 wp.hooks.addFilter(
144 'editor.BlockEdit',
145 'wp-smush/smush-data-control',
146 smushStatsControl
147 );
1 /* global WP_Smush */
2 /* global ajaxurl */
3
4 /**
5 * CDN functionality.
6 *
7 * @since 3.0
8 */
9 ( function() {
10 'use strict';
11
12 WP_Smush.CDN = {
13 cdnEnableButton: document.getElementById( 'smush-enable-cdn' ),
14 cdnDisableButton: document.getElementById( 'smush-cancel-cdn' ),
15 cdnStatsBox: document.querySelector( '.smush-cdn-stats' ),
16
17 init() {
18 /**
19 * Handle "Get Started" button click on disabled CDN page.
20 */
21 if ( this.cdnEnableButton ) {
22 this.cdnEnableButton.addEventListener( 'click', ( e ) => {
23 e.preventDefault();
24 e.currentTarget.classList.add( 'sui-button-onload' );
25 this.toggle_cdn( true );
26 } );
27 }
28
29 /**
30 * Handle "Deactivate' button click on CDN page.
31 */
32 if ( this.cdnDisableButton ) {
33 this.cdnDisableButton.addEventListener( 'click', ( e ) => {
34 e.preventDefault();
35 e.currentTarget.classList.add( 'sui-button-onload' );
36
37 this.toggle_cdn( false );
38 } );
39 }
40
41 this.updateStatsBox();
42 },
43
44 /**
45 * Toggle CDN.
46 *
47 * @since 3.0
48 *
49 * @param {boolean} enable
50 */
51 toggle_cdn( enable ) {
52 const nonceField = document.getElementsByName(
53 'wp_smush_options_nonce'
54 );
55
56 const xhr = new XMLHttpRequest();
57 xhr.open( 'POST', ajaxurl + '?action=smush_toggle_cdn', true );
58 xhr.setRequestHeader(
59 'Content-type',
60 'application/x-www-form-urlencoded'
61 );
62 xhr.onload = () => {
63 if ( 200 === xhr.status ) {
64 const res = JSON.parse( xhr.response );
65 if ( 'undefined' !== typeof res.success && res.success ) {
66 window.location.search = 'page=smush-cdn';
67 } else if ( 'undefined' !== typeof res.data.message ) {
68 WP_Smush.helpers.showErrorNotice( res.data.message );
69 }
70 } else {
71 WP_Smush.helpers.showErrorNotice( 'Request failed. Returned status of ' + xhr.status );
72 }
73 };
74 xhr.send(
75 'param=' + enable + '&_ajax_nonce=' + nonceField[ 0 ].value
76 );
77 },
78
79 /**
80 * Update the CDN stats box in summary meta box. Only fetch new data when on CDN page.
81 *
82 * @since 3.0
83 */
84 updateStatsBox() {
85 if (
86 'undefined' === typeof this.cdnStatsBox ||
87 ! this.cdnStatsBox
88 ) {
89 return;
90 }
91
92 // Only fetch the new stats, when user is on CDN page.
93 if ( ! window.location.search.includes( 'page=smush-cdn' ) ) {
94 return;
95 }
96
97 this.toggleElements();
98
99 const xhr = new XMLHttpRequest();
100 xhr.open( 'POST', ajaxurl + '?action=get_cdn_stats', true );
101 xhr.onload = () => {
102 if ( 200 === xhr.status ) {
103 const res = JSON.parse( xhr.response );
104 if ( 'undefined' !== typeof res.success && res.success ) {
105 this.toggleElements();
106 } else if ( 'undefined' !== typeof res.data.message ) {
107 WP_Smush.helpers.showErrorNotice( res.data.message );
108 }
109 } else {
110 WP_Smush.helpers.showErrorNotice( 'Request failed. Returned status of ' + xhr.status );
111 }
112 };
113 xhr.send();
114 },
115
116 /**
117 * Show/hide elements during status update in the updateStatsBox()
118 *
119 * @since 3.1 Moved out from updateStatsBox()
120 */
121 toggleElements() {
122 const spinner = this.cdnStatsBox.querySelector(
123 '.sui-icon-loader'
124 );
125 const elements = this.cdnStatsBox.querySelectorAll(
126 '.wp-smush-stats > :not(.sui-icon-loader)'
127 );
128
129 for ( let i = 0; i < elements.length; i++ ) {
130 elements[ i ].classList.toggle( 'sui-hidden' );
131 }
132
133 spinner.classList.toggle( 'sui-hidden' );
134 },
135 };
136
137 WP_Smush.CDN.init();
138 } )();
1 /* global WP_Smush */
2 /* global ajaxurl */
3
4 /**
5 * Directory scanner module that will Smush images in the Directory Smush modal.
6 *
7 * @since 2.8.1
8 *
9 * @param {string|number} totalSteps
10 * @param {string|number} currentStep
11 * @return {Object} Scan object.
12 * @class
13 */
14 const DirectoryScanner = ( totalSteps, currentStep ) => {
15 totalSteps = parseInt( totalSteps );
16 currentStep = parseInt( currentStep );
17
18 let cancelling = false,
19 failedItems = 0,
20 skippedItems = 0;
21
22 const obj = {
23 scan() {
24 const remainingSteps = totalSteps - currentStep;
25 if ( currentStep !== 0 ) {
26 // Scan started on a previous page load.
27 step( remainingSteps ).fail( this.showScanError );
28 } else {
29 jQuery
30 .post( ajaxurl, {
31 action: 'directory_smush_start',
32 _ajax_nonce: window.wp_smush_msgs.nonce
33 }, () =>
34 step( remainingSteps ).fail( this.showScanError )
35 )
36 .fail( this.showScanError );
37 }
38 },
39
40 cancel() {
41 cancelling = true;
42 return jQuery.post( ajaxurl, {
43 action: 'directory_smush_cancel',
44 _ajax_nonce: window.wp_smush_msgs.nonce
45 } );
46 },
47
48 getProgress() {
49 if ( cancelling ) {
50 return 0;
51 }
52 // O M G ... Logic at it's finest!
53 const remainingSteps = totalSteps - currentStep;
54 return Math.min(
55 Math.round(
56 ( parseInt( totalSteps - remainingSteps ) * 100 ) /
57 totalSteps
58 ),
59 99
60 );
61 },
62
63 onFinishStep( progress ) {
64 jQuery( '.wp-smush-progress-dialog .sui-progress-state-text' ).html(
65 currentStep -
66 failedItems +
67 '/' +
68 totalSteps +
69 ' ' +
70 window.wp_smush_msgs.progress_smushed
71 );
72 WP_Smush.directory.updateProgressBar( progress );
73 },
74
75 onFinish() {
76 WP_Smush.directory.updateProgressBar( 100 );
77 window.location.href =
78 window.wp_smush_msgs.directory_url + '&scan=done';
79 },
80
81 /**
82 * Displays an error when the scan request fails.
83 *
84 * @param {Object} res XHR object.
85 */
86 showScanError( res ) {
87 const dialog = jQuery( '#wp-smush-progress-dialog' );
88
89 // Add the error class to show/hide elements in the dialog.
90 dialog
91 .removeClass( 'wp-smush-exceed-limit' )
92 .addClass( 'wp-smush-scan-error' );
93
94 // Add the error status and description to the error message.
95 dialog
96 .find( '#smush-scan-error' )
97 .text( `${ res.status } ${ res.statusText }` );
98
99 // Show/hide the 403 error specific instructions.
100 const forbiddenMessage = dialog.find( '.smush-403-error-message' );
101 if ( 403 !== res.status ) {
102 forbiddenMessage.addClass( 'sui-hidden' );
103 } else {
104 forbiddenMessage.removeClass( 'sui-hidden' );
105 }
106 },
107
108 limitReached() {
109 const dialog = jQuery( '#wp-smush-progress-dialog' );
110
111 dialog.addClass( 'wp-smush-exceed-limit' );
112 dialog
113 .find( '#cancel-directory-smush' )
114 .attr( 'data-tooltip', window.wp_smush_msgs.bulk_resume );
115 dialog
116 .find( '.sui-box-body .sui-icon-close' )
117 .removeClass( 'sui-icon-close' )
118 .addClass( 'sui-icon-play' );
119 dialog
120 .find( '#cancel-directory-smush' )
121 .attr( 'id', 'cancel-directory-smush-disabled' );
122 },
123
124 resume() {
125 const dialog = jQuery( '#wp-smush-progress-dialog' );
126 const resume = dialog.find( '#cancel-directory-smush-disabled' );
127
128 dialog.removeClass( 'wp-smush-exceed-limit' );
129 dialog
130 .find( '.sui-box-body .sui-icon-play' )
131 .removeClass( 'sui-icon-play' )
132 .addClass( 'sui-icon-close' );
133 resume.attr( 'data-tooltip', 'Cancel' );
134 resume.attr( 'id', 'cancel-directory-smush' );
135
136 obj.scan();
137 },
138 };
139
140 /**
141 * Execute a scan step recursively
142 *
143 * Private to avoid overriding
144 *
145 * @param {number} remainingSteps
146 */
147 const step = function( remainingSteps ) {
148 if ( remainingSteps >= 0 ) {
149 currentStep = totalSteps - remainingSteps;
150 return jQuery.post(
151 ajaxurl,
152 {
153 action: 'directory_smush_check_step',
154 _ajax_nonce: window.wp_smush_msgs.nonce,
155 step: currentStep,
156 },
157 ( response ) => {
158 // We're good - continue on.
159 if (
160 'undefined' !== typeof response.success &&
161 response.success
162 ) {
163 if (
164 'undefined' !== typeof response.data &&
165 'undefined' !== typeof response.data.skipped &&
166 true === response.data.skipped
167 ) {
168 skippedItems++;
169 }
170
171 currentStep++;
172 remainingSteps = remainingSteps - 1;
173 obj.onFinishStep( obj.getProgress() );
174 step( remainingSteps ).fail( obj.showScanError );
175 } else if (
176 'undefined' !== typeof response.data.error &&
177 'dir_smush_limit_exceeded' === response.data.error
178 ) {
179 // Limit reached. Stop.
180 obj.limitReached();
181 } else {
182 // Error? never mind, continue, but count them.
183 failedItems++;
184 currentStep++;
185 remainingSteps = remainingSteps - 1;
186 obj.onFinishStep( obj.getProgress() );
187 step( remainingSteps ).fail( obj.showScanError );
188 }
189 }
190 );
191 }
192 return jQuery.post(
193 ajaxurl,
194 {
195 action: 'directory_smush_finish',
196 _ajax_nonce: window.wp_smush_msgs.nonce,
197 items: totalSteps - ( failedItems + skippedItems ),
198 failed: failedItems,
199 skipped: skippedItems,
200 },
201 ( response ) => obj.onFinish( response )
202 );
203 };
204
205 return obj;
206 };
207
208 export default DirectoryScanner;
1 /* global WP_Smush */
2 /* global ajaxurl */
3
4 /**
5 * Lazy loading functionality.
6 *
7 * @since 3.0
8 */
9 ( function() {
10 'use strict';
11
12 WP_Smush.Lazyload = {
13 lazyloadEnableButton: document.getElementById(
14 'smush-enable-lazyload'
15 ),
16 lazyloadDisableButton: document.getElementById(
17 'smush-cancel-lazyload'
18 ),
19
20 init() {
21 const self = this;
22
23 /**
24 * Handle "Activate" button click on disabled Lazy load page.
25 */
26 if ( this.lazyloadEnableButton ) {
27 this.lazyloadEnableButton.addEventListener( 'click', ( e ) => {
28 e.preventDefault();
29 e.currentTarget.classList.add( 'sui-button-onload' );
30
31 this.toggle_lazy_load( true );
32 } );
33 }
34
35 /**
36 * Handle "Deactivate' button click on Lazy load page.
37 */
38 if ( this.lazyloadDisableButton ) {
39 this.lazyloadDisableButton.addEventListener( 'click', ( e ) => {
40 e.preventDefault();
41 e.currentTarget.classList.add( 'sui-button-onload' );
42
43 this.toggle_lazy_load( false );
44 } );
45 }
46
47 /**
48 * Handle "Remove icon" button click on Lazy load page.
49 *
50 * This removes the image from the upload placeholder.
51 *
52 * @since 3.2.2
53 */
54 const removeSpinner = document.getElementById(
55 'smush-remove-spinner'
56 );
57 if ( removeSpinner ) {
58 removeSpinner.addEventListener( 'click', ( e ) => {
59 e.preventDefault();
60 this.removeLoaderIcon();
61 } );
62 }
63 const removePlaceholder = document.getElementById(
64 'smush-remove-placeholder'
65 );
66 if ( removePlaceholder ) {
67 removePlaceholder.addEventListener( 'click', ( e ) => {
68 e.preventDefault();
69 this.removeLoaderIcon( 'placeholder' );
70 } );
71 }
72
73 /**
74 * Handle "Remove" icon click.
75 *
76 * This removes the select icon from the list (not same as above functions).
77 *
78 * @since 3.2.2
79 */
80 const items = document.querySelectorAll( '.smush-ll-remove' );
81 if ( items && 0 < items.length ) {
82 items.forEach( function( el ) {
83 el.addEventListener( 'click', ( e ) => {
84 e.preventDefault();
85 e.target.closest( 'li' ).style.display = 'none';
86 self.remove(
87 e.target.dataset.id,
88 e.target.dataset.type
89 );
90 } );
91 } );
92 }
93
94 this.handlePredefinedPlaceholders();
95 },
96
97 /**
98 * Handle background color changes for the two predefined placeholders.
99 *
100 * @since 3.7.1
101 */
102 handlePredefinedPlaceholders() {
103 const pl1 = document.getElementById( 'placeholder-icon-1' );
104 if ( pl1 ) {
105 pl1.addEventListener( 'click', () => this.changeColor( '#F3F3F3' ) );
106 }
107
108 const pl2 = document.getElementById( 'placeholder-icon-2' );
109 if ( pl2 ) {
110 pl2.addEventListener( 'click', () => this.changeColor( '#333333' ) );
111 }
112 },
113
114 /**
115 * Set color.
116 *
117 * @since 3.7.1
118 * @param {string} color
119 */
120 changeColor( color ) {
121 document.getElementById( 'smush-color-picker' ).value = color;
122 document.querySelector( '.sui-colorpicker-hex .sui-colorpicker-value > span > span' ).style.backgroundColor = color;
123 document.querySelector( '.sui-colorpicker-hex .sui-colorpicker-value > input' ).value = color;
124 },
125
126 /**
127 * Toggle lazy loading.
128 *
129 * @since 3.2.0
130 *
131 * @param {string} enable
132 */
133 toggle_lazy_load( enable ) {
134 const nonceField = document.getElementsByName(
135 'wp_smush_options_nonce'
136 );
137
138 const xhr = new XMLHttpRequest();
139 xhr.open(
140 'POST',
141 ajaxurl + '?action=smush_toggle_lazy_load',
142 true
143 );
144 xhr.setRequestHeader(
145 'Content-type',
146 'application/x-www-form-urlencoded'
147 );
148 xhr.onload = () => {
149 if ( 200 === xhr.status ) {
150 const res = JSON.parse( xhr.response );
151 if ( 'undefined' !== typeof res.success && res.success ) {
152 window.location.search = 'page=smush-lazy-load';
153 } else if ( 'undefined' !== typeof res.data.message ) {
154 WP_Smush.helpers.showErrorNotice( res.data.message );
155 document.querySelector( '.sui-button-onload' ).classList.remove( 'sui-button-onload' );
156 }
157 } else {
158 WP_Smush.helpers.showErrorNotice( 'Request failed. Returned status of ' + xhr.status );
159 document.querySelector( '.sui-button-onload' ).classList.remove( 'sui-button-onload' );
160 }
161 };
162 xhr.send(
163 'param=' + enable + '&_ajax_nonce=' + nonceField[ 0 ].value
164 );
165 },
166
167 /**
168 * Add lazy load spinner icon.
169 *
170 * @since 3.2.2
171 * @param {string} type Accepts: spinner, placeholder.
172 */
173 addLoaderIcon( type = 'spinner' ) {
174 let frame;
175
176 // If the media frame already exists, reopen it.
177 if ( frame ) {
178 frame.open();
179 return;
180 }
181
182 // Create a new media frame
183 frame = wp.media( {
184 title: 'Select or upload an icon',
185 button: {
186 text: 'Select icon',
187 },
188 multiple: false, // Set to true to allow multiple files to be selected
189 } );
190
191 // When an image is selected in the media frame...
192 frame.on( 'select', function() {
193 // Get media attachment details from the frame state
194 const attachment = frame
195 .state()
196 .get( 'selection' )
197 .first()
198 .toJSON();
199
200 // Send the attachment URL to our custom image input field.
201 const imageIcon = document.getElementById(
202 'smush-' + type + '-icon-preview'
203 );
204 imageIcon.style.backgroundImage =
205 'url("' + attachment.url + '")';
206 imageIcon.style.display = 'block';
207
208 // Send the attachment id to our hidden input
209 document
210 .getElementById( 'smush-' + type + '-icon-file' )
211 .setAttribute( 'value', attachment.id );
212
213 // Hide the add image link
214 document.getElementById(
215 'smush-upload-' + type
216 ).style.display = 'none';
217
218 // Unhide the remove image link
219 const removeDiv = document.getElementById(
220 'smush-remove-' + type
221 );
222 removeDiv.querySelector( 'span' ).innerHTML =
223 attachment.filename;
224 removeDiv.style.display = 'block';
225 } );
226
227 // Finally, open the modal on click
228 frame.open();
229 },
230
231 /**
232 * Remove lazy load spinner icon.
233 *
234 * @since 3.2.2
235 * @param {string} type Accepts: spinner, placeholder.
236 */
237 removeLoaderIcon: ( type = 'spinner' ) => {
238 // Clear out the preview image
239 const imageIcon = document.getElementById(
240 'smush-' + type + '-icon-preview'
241 );
242 imageIcon.style.backgroundImage = '';
243 imageIcon.style.display = 'none';
244
245 // Un-hide the add image link
246 document.getElementById( 'smush-upload-' + type ).style.display =
247 'block';
248
249 // Hide the delete image link
250 document.getElementById( 'smush-remove-' + type ).style.display =
251 'none';
252
253 // Delete the image id from the hidden input
254 document
255 .getElementById( 'smush-' + type + '-icon-file' )
256 .setAttribute( 'value', '' );
257 },
258
259 /**
260 * Remove item.
261 *
262 * @param {number} id Image ID.
263 * @param {string} type Accepts: spinner, placeholder.
264 */
265 remove: ( id, type = 'spinner' ) => {
266 const nonceField = document.getElementsByName(
267 'wp_smush_options_nonce'
268 );
269 const xhr = new XMLHttpRequest();
270 xhr.open( 'POST', ajaxurl + '?action=smush_remove_icon', true );
271 xhr.setRequestHeader(
272 'Content-type',
273 'application/x-www-form-urlencoded'
274 );
275 xhr.send(
276 'id=' +
277 id +
278 '&type=' +
279 type +
280 '&_ajax_nonce=' +
281 nonceField[ 0 ].value
282 );
283 },
284 };
285
286 WP_Smush.Lazyload.init();
287 } )();
1 /* global smush_vars */
2 /* global _ */
3
4 /**
5 * Adds a Smush Now button and displays stats in Media Attachment Details Screen
6 */
7 (function ($, _) {
8 'use strict';
9
10 // Local reference to the WordPress media namespace.
11 const smushMedia = wp.media,
12 sharedTemplate =
13 "<span class='setting smush-stats' data-setting='smush'>" +
14 "<span class='name'><%= label %></span>" +
15 "<span class='value'><%= value %></span>" +
16 '</span>',
17 template = _.template(sharedTemplate);
18
19 /**
20 * Create the template.
21 *
22 * @param {string} smushHTML
23 * @return {Object} Template object
24 */
25 const prepareTemplate = function (smushHTML) {
26 /**
27 * @param {Array} smush_vars.strings Localization strings.
28 * @param {Object} smush_vars Object from wp_localize_script()
29 */
30 return template({
31 label: smush_vars.strings.stats_label,
32 value: smushHTML,
33 });
34 };
35
36 if (
37 'undefined' !== typeof smushMedia.view &&
38 'undefined' !== typeof smushMedia.view.Attachment.Details.TwoColumn
39 ) {
40 // Local instance of the Attachment Details TwoColumn used in the edit attachment modal view
41 const smushMediaTwoColumn =
42 smushMedia.view.Attachment.Details.TwoColumn;
43
44 /**
45 * Add Smush details to attachment.
46 *
47 * A similar view to media.view.Attachment.Details
48 * for use in the Edit Attachment modal.
49 *
50 * @see wp-includes/js/media-grid.js
51 */
52 smushMedia.view.Attachment.Details.TwoColumn = smushMediaTwoColumn.extend(
53 {
54 initialize() {
55 smushMediaTwoColumn.prototype.initialize.apply(this, arguments);
56 this.listenTo(this.model, 'change:smush', this.render);
57 },
58
59 render() {
60 // Ensure that the main attachment fields are rendered.
61 smushMedia.view.Attachment.prototype.render.apply(
62 this,
63 arguments
64 );
65
66 const smushHTML = this.model.get('smush');
67 if (typeof smushHTML === 'undefined') {
68 return this;
69 }
70
71 this.model.fetch();
72
73 /**
74 * Detach the views, append our custom fields, make sure that our data is fully updated
75 * and re-render the updated view.
76 */
77 this.views.detach();
78 this.$el
79 .find('.settings')
80 .append(prepareTemplate(smushHTML));
81 this.views.render();
82
83 return this;
84 },
85 }
86 );
87 }
88
89 // Local instance of the Attachment Details TwoColumn used in the edit attachment modal view
90 const smushAttachmentDetails = smushMedia.view.Attachment.Details;
91
92 /**
93 * Add Smush details to attachment.
94 */
95 smushMedia.view.Attachment.Details = smushAttachmentDetails.extend({
96 initialize() {
97 smushAttachmentDetails.prototype.initialize.apply(this, arguments);
98 this.listenTo(this.model, 'change:smush', this.render);
99 },
100
101 render() {
102 // Ensure that the main attachment fields are rendered.
103 smushMedia.view.Attachment.prototype.render.apply(this, arguments);
104
105 const smushHTML = this.model.get('smush');
106 if (typeof smushHTML === 'undefined') {
107 return this;
108 }
109
110 this.model.fetch();
111
112 /**
113 * Detach the views, append our custom fields, make sure that our data is fully updated
114 * and re-render the updated view.
115 */
116 this.views.detach();
117 this.$el.append(prepareTemplate(smushHTML));
118
119 return this;
120 },
121 });
122
123 /**
124 * Create a new MediaLibraryTaxonomyFilter we later will instantiate
125 *
126 * @since 3.0
127 */
128 const MediaLibraryTaxonomyFilter = wp.media.view.AttachmentFilters.extend({
129 id: 'media-attachment-smush-filter',
130
131 createFilters() {
132 this.filters = {
133 all: {
134 text: smush_vars.strings.filter_all,
135 props: { stats: 'all' },
136 priority: 10,
137 },
138
139 unsmushed: {
140 text: smush_vars.strings.filter_not_processed,
141 props: { stats: 'unsmushed' },
142 priority: 20,
143 },
144
145 excluded: {
146 text: smush_vars.strings.filter_excl,
147 props: { stats: 'excluded' },
148 priority: 30,
149 },
150
151 failed: {
152 text: smush_vars.strings.filter_failed,
153 props: { stats: 'failed_processing' },
154 priority: 40,
155 },
156 };
157 },
158 });
159
160 /**
161 * Extend and override wp.media.view.AttachmentsBrowser to include our new filter.
162 *
163 * @since 3.0
164 */
165 const AttachmentsBrowser = wp.media.view.AttachmentsBrowser;
166 wp.media.view.AttachmentsBrowser = wp.media.view.AttachmentsBrowser.extend({
167 createToolbar() {
168 // Make sure to load the original toolbar
169 AttachmentsBrowser.prototype.createToolbar.call(this);
170 this.toolbar.set(
171 'MediaLibraryTaxonomyFilter',
172 new MediaLibraryTaxonomyFilter({
173 controller: this.controller,
174 model: this.collection.props,
175 priority: -75,
176 }).render()
177 );
178 },
179 });
180 })(jQuery, _);
1 /* global ajaxurl */
2 /* global wp_smush_msgs */
3 /* global WP_Smush */
4 /* global SUI */
5
6 ( function( $ ) {
7 'use strict';
8
9 /**
10 * Bulk compress page.
11 */
12 $( 'form#smush-bulk-form' ).on( 'submit', function( e ) {
13 e.preventDefault();
14 $( '#save-settings-button' ).addClass( 'sui-button-onload' );
15 saveSettings( $( this ).serialize(), 'bulk' );
16 runReCheck();
17 } );
18
19 /**
20 * Lazy load page.
21 */
22 $( 'form#smush-lazy-load-form' ).on( 'submit', function( e ) {
23 e.preventDefault();
24 $( '#save-settings-button' ).addClass( 'sui-button-onload-text' );
25 saveSettings( $( this ).serialize(), 'lazy-load' );
26 } );
27
28 /**
29 * CDN page.
30 */
31 $( 'form#smush-cdn-form' ).on( 'submit', function( e ) {
32 e.preventDefault();
33 $( '#save-settings-button' ).addClass( 'sui-button-onload-text' );
34 saveSettings( $( this ).serialize(), 'cdn' );
35 } );
36
37 /**
38 * Integrations page.
39 */
40 $( 'form#smush-integrations-form' ).on( 'submit', function( e ) {
41 e.preventDefault();
42 $( '#save-settings-button' ).addClass( 'sui-button-onload-text' );
43 saveSettings( $( this ).serialize(), 'integrations' );
44 } );
45
46 /**
47 * Settings page.
48 */
49 $( 'form#smush-settings-form' ).on( 'submit', function( e ) {
50 e.preventDefault();
51 $( '#save-settings-button' ).addClass( 'sui-button-onload-text' );
52 saveSettings( $( this ).serialize(), 'settings' );
53 } );
54
55 /**
56 * Save settings.
57 *
58 * @param {string} settings JSON string of settings.
59 * @param {string} page Settings page.
60 */
61 function saveSettings( settings, page ) {
62 const xhr = new XMLHttpRequest();
63
64 xhr.open( 'POST', ajaxurl + '?action=smush_save_settings', true );
65 xhr.setRequestHeader(
66 'Content-type',
67 'application/x-www-form-urlencoded'
68 );
69
70 xhr.onload = () => {
71 $( '#save-settings-button' ).removeClass(
72 'sui-button-onload-text sui-button-onload'
73 );
74
75 if ( 200 === xhr.status ) {
76 const res = JSON.parse( xhr.response );
77 if ( 'undefined' !== typeof res.success && res.success ) {
78 showSuccessNotice( wp_smush_msgs.settingsUpdated );
79 } else if ( res.data && res.data.message ) {
80 WP_Smush.helpers.showErrorNotice( res.data.message );
81 } else {
82 WP_Smush.helpers.showErrorNotice( 'Request failed.' );
83 }
84 } else {
85 WP_Smush.helpers.showErrorNotice( 'Request failed. Returned status of ' + xhr.status );
86 }
87 };
88
89 xhr.send( 'page=' + page + '&' + settings + '&_ajax_nonce=' + wp_smush_msgs.nonce );
90 }
91
92 /**
93 * Show successful update notice.
94 *
95 * @param {string} msg Notice message.
96 */
97 function showSuccessNotice( msg ) {
98 const noticeMessage = `<p>${ msg }</p>`,
99 noticeOptions = {
100 type: 'success',
101 icon: 'check',
102 };
103
104 SUI.openNotice( 'wp-smush-ajax-notice', noticeMessage, noticeOptions );
105
106 const loadingButton = document.querySelector( '.sui-button-onload' );
107 if ( loadingButton ) {
108 loadingButton.classList.remove( 'sui-button-onload' );
109 }
110 }
111
112 /**
113 * Re-check images from bulk smush and integrations pages.
114 */
115 function runReCheck() {
116 $( '#save-settings-button' ).addClass( 'sui-button-onload' );
117
118 const param = {
119 action: 'scan_for_resmush',
120 wp_smush_options_nonce: $( '#wp_smush_options_nonce' ).val(),
121 type: 'media',
122 };
123
124 // Send ajax, Update Settings, And Check For resmush.
125 $.post( ajaxurl, $.param( param ) ).done( function() {
126 $( '#save-settings-button' ).removeClass( 'sui-button-onload' );
127 } );
128 }
129
130 /**
131 * Parse remove data change.
132 */
133 $( 'input[name=keep_data]' ).on( 'change', function( e ) {
134 const otherClass =
135 'keep_data-true' === e.target.id
136 ? 'keep_data-false'
137 : 'keep_data-true';
138 e.target.parentNode.classList.add( 'active' );
139 document
140 .getElementById( otherClass )
141 .parentNode.classList.remove( 'active' );
142 } );
143
144 /**
145 * Handle auto-detect checkbox toggle, to show/hide highlighting notice.
146 */
147 $( 'input#detection' ).on( 'click', function() {
148 const noticeDiv = $( '.smush-highlighting-notice' );
149 const warningDiv = $( '.smush-highlighting-warning' );
150
151 // Setting enabled.
152 if ( $( this ).is( ':checked' ) ) {
153 // Highlighting is already active and setting not saved.
154 if ( noticeDiv.length > 0 ) {
155 noticeDiv.show();
156 } else {
157 warningDiv.show();
158 }
159 } else {
160 noticeDiv.hide();
161 warningDiv.hide();
162 }
163 } );
164 }( jQuery ) );
1 /* global WP_Smush */
2 /* global ajaxurl */
3
4 /**
5 * WebP functionality.
6 *
7 * @since 3.8.0
8 */
9
10 (function () {
11 'use strict';
12
13 WP_Smush.WebP = {
14 nonceField: document.getElementsByName('wp_smush_options_nonce'),
15 toggleModuleButton: document.getElementById('smush-toggle-webp-button'),
16 recheckStatusButton: document.getElementById('smush-webp-recheck'),
17 recheckStatusLink: document.getElementById('smush-webp-recheck-link'),
18 showWizardButton: document.getElementById('smush-webp-toggle-wizard'),
19
20 init() {
21 this.maybeShowDeleteAllSuccessNotice();
22
23 /**
24 * Handles the "Deactivate" and "Get Started" buttons on the WebP page.
25 */
26 if (this.toggleModuleButton) {
27 this.toggleModuleButton.addEventListener('click', (e) =>
28 this.toggleWebp(e)
29 );
30 }
31
32 /**
33 * Handle "RE-CHECK STATUS' button click on WebP page.
34 */
35 if (this.recheckStatusButton) {
36 this.recheckStatusButton.addEventListener('click', (e) => {
37 e.preventDefault();
38 this.recheckStatus();
39 });
40 }
41
42 /**
43 * Handle "RE-CHECK STATUS' link click on WebP page.
44 */
45 if (this.recheckStatusLink) {
46 this.recheckStatusLink.addEventListener('click', (e) => {
47 e.preventDefault();
48 this.recheckStatus();
49 });
50 }
51
52 /**
53 * Handles the "Delete WebP images" button.
54 */
55 if (document.getElementById('wp-smush-webp-delete-all')) {
56 document
57 .getElementById('wp-smush-webp-delete-all')
58 .addEventListener('click', (e) => this.deleteAll(e));
59 }
60
61 if (this.showWizardButton) {
62 this.showWizardButton.addEventListener(
63 'click',
64 this.toggleWizard
65 );
66 }
67 },
68
69 /**
70 * Toggle WebP module.
71 *
72 * @param {Event} e
73 */
74 toggleWebp(e) {
75 e.preventDefault();
76
77 const button = e.currentTarget,
78 doEnable = 'enable' === button.dataset.action;
79
80 button.classList.add('sui-button-onload');
81
82 const xhr = new XMLHttpRequest();
83 xhr.open('POST', ajaxurl + '?action=smush_webp_toggle', true);
84 xhr.setRequestHeader(
85 'Content-type',
86 'application/x-www-form-urlencoded'
87 );
88
89 xhr.onload = () => {
90 const res = JSON.parse(xhr.response);
91
92 if (200 === xhr.status) {
93 if ('undefined' !== typeof res.success && res.success) {
94 const scanPromise = this.runScan();
95 scanPromise.onload = () => {
96 window.location.href =
97 window.wp_smush_msgs.localWebpURL;
98 };
99 } else if ('undefined' !== typeof res.data.message) {
100 this.showNotice(res.data.message);
101 button.classList.remove('sui-button-onload');
102 }
103 } else {
104 let message = window.wp_smush_msgs.generic_ajax_error;
105 if (res && 'undefined' !== typeof res.data.message) {
106 message = res.data.message;
107 }
108 this.showNotice(message);
109 button.classList.remove('sui-button-onload');
110 }
111 };
112
113 xhr.send(
114 'param=' + doEnable + '&_ajax_nonce=' + this.nonceField[0].value
115 );
116 },
117
118 /**
119 * re-check server configuration for WebP.
120 */
121 recheckStatus() {
122 this.recheckStatusButton.classList.add('sui-button-onload');
123
124 const xhr = new XMLHttpRequest();
125 xhr.open('POST', ajaxurl + '?action=smush_webp_get_status', true);
126 xhr.setRequestHeader(
127 'Content-type',
128 'application/x-www-form-urlencoded'
129 );
130 xhr.onload = () => {
131 this.recheckStatusButton.classList.remove('sui-button-onload');
132 let message = false;
133 const res = JSON.parse(xhr.response);
134 if (200 === xhr.status) {
135 const isConfigured = res.success ? '1' : '0';
136 if (
137 isConfigured !==
138 this.recheckStatusButton.dataset.isConfigured
139 ) {
140 // Reload the page when the configuration status changed.
141 location.reload();
142 }
143 } else {
144 message = window.wp_smush_msgs.generic_ajax_error;
145 }
146
147 if (res && res.data) {
148 message = res.data;
149 }
150
151 if (message) {
152 this.showNotice(message);
153 }
154 };
155 xhr.send('_ajax_nonce=' + window.wp_smush_msgs.webp_nonce);
156 },
157
158 deleteAll(e) {
159 const button = e.currentTarget;
160 button.classList.add('sui-button-onload');
161
162 let message = false;
163 const xhr = new XMLHttpRequest();
164 xhr.open('POST', ajaxurl + '?action=smush_webp_delete_all', true);
165 xhr.setRequestHeader(
166 'Content-type',
167 'application/x-www-form-urlencoded'
168 );
169
170 xhr.onload = () => {
171 const res = JSON.parse(xhr.response);
172 if (200 === xhr.status) {
173 if ('undefined' !== typeof res.success && res.success) {
174 const scanPromise = this.runScan();
175 scanPromise.onload = () => {
176 location.search =
177 location.search + '&notice=webp-deleted';
178 };
179 } else {
180 message = window.wp_smush_msgs.generic_ajax_error;
181 }
182 } else {
183 message = window.wp_smush_msgs.generic_ajax_error;
184 }
185
186 if (res && res.data && res.data.message) {
187 message = res.data.message;
188 }
189
190 if (message) {
191 button.classList.remove('sui-button-onload');
192
193 const noticeMessage = `<p style="text-align: left;">${message}</p>`;
194 const noticeOptions = {
195 type: 'error',
196 icon: 'info',
197 autoclose: {
198 show: false,
199 },
200 };
201
202 window.SUI.openNotice(
203 'wp-smush-webp-delete-all-error-notice',
204 noticeMessage,
205 noticeOptions
206 );
207 }
208 };
209
210 xhr.send('_ajax_nonce=' + this.nonceField[0].value);
211 },
212
213 toggleWizard(e) {
214 e.currentTarget.classList.add('sui-button-onload');
215
216 const xhr = new XMLHttpRequest();
217 xhr.open(
218 'GET',
219 ajaxurl +
220 '?action=smush_toggle_webp_wizard&_ajax_nonce=' +
221 window.wp_smush_msgs.webp_nonce,
222 true
223 );
224 xhr.onload = () => location.reload();
225 xhr.send();
226 },
227
228 /**
229 * Triggers the scanning of images for updating the images to re-smush.
230 *
231 * @since 3.8.0
232 */
233 runScan() {
234 const xhr = new XMLHttpRequest(),
235 nonceField = document.getElementsByName(
236 'wp_smush_options_nonce'
237 );
238
239 xhr.open('POST', ajaxurl + '?action=scan_for_resmush', true);
240 xhr.setRequestHeader(
241 'Content-type',
242 'application/x-www-form-urlencoded'
243 );
244
245 xhr.send('_ajax_nonce=' + nonceField[0].value);
246
247 return xhr;
248 },
249
250 /**
251 * Show message (notice).
252 *
253 * @param {string} message
254 * @param {string} type
255 */
256 showNotice(message, type) {
257 if ('undefined' === typeof message) {
258 return;
259 }
260
261 const noticeMessage = `<p>${message}</p>`;
262 const noticeOptions = {
263 type: type || 'error',
264 icon: 'info',
265 dismiss: {
266 show: true,
267 label: window.wp_smush_msgs.noticeDismiss,
268 tooltip: window.wp_smush_msgs.noticeDismissTooltip,
269 },
270 autoclose: {
271 show: false,
272 },
273 };
274
275 window.SUI.openNotice(
276 'wp-smush-ajax-notice',
277 noticeMessage,
278 noticeOptions
279 );
280 },
281
282 /**
283 * Show delete all webp success notice.
284 */
285 maybeShowDeleteAllSuccessNotice() {
286 if (!document.getElementById('wp-smush-webp-delete-all-notice')) {
287 return;
288 }
289 const noticeMessage = `<p>${
290 document.getElementById('wp-smush-webp-delete-all-notice')
291 .dataset.message
292 }</p>`;
293
294 const noticeOptions = {
295 type: 'success',
296 icon: 'check-tick',
297 dismiss: {
298 show: true,
299 },
300 };
301
302 window.SUI.openNotice(
303 'wp-smush-webp-delete-all-notice',
304 noticeMessage,
305 noticeOptions
306 );
307 },
308 };
309
310 WP_Smush.WebP.init();
311 })();
1 /* global ajaxurl */
2
3 /**
4 * External dependencies
5 */
6 import React from 'react';
7 import ReactDOM from 'react-dom';
8
9 /**
10 * WordPress dependencies
11 */
12 import domReady from '@wordpress/dom-ready';
13
14 /**
15 * SUI dependencies
16 */
17 import { TutorialsList, TutorialsSlider } from '@wpmudev/shared-tutorials';
18
19 import MixPanel from "./mixpanel"
20
21 function hideTutorials() {
22 const xhr = new XMLHttpRequest();
23
24 xhr.open( 'POST', ajaxurl + '?action=smush_hide_tutorials', true );
25 xhr.setRequestHeader( 'Content-type', 'application/x-www-form-urlencoded' );
26
27 xhr.onload = () => {
28 if ( 200 === xhr.status ) {
29 const noticeMessage = `<p>${ window.wp_smush_msgs.tutorialsRemoved }</p>`,
30 noticeOptions = {
31 type: 'success',
32 icon: 'check',
33 };
34
35 window.SUI.openNotice(
36 'wp-smush-ajax-notice',
37 noticeMessage,
38 noticeOptions
39 );
40 }
41 };
42
43 xhr.send( '_ajax_nonce=' + window.wp_smush_msgs.nonce );
44 }
45
46 /**
47 * Render the "Tutorials List" component.
48 *
49 * @since 2.8.5
50 */
51 domReady( function() {
52 // Tutorials section on Dashboard page.
53 const tutorialsDiv = document.getElementById( 'smush-dash-tutorials' );
54 if ( tutorialsDiv ) {
55 ReactDOM.render(
56 <TutorialsSlider
57 category="11228"
58 title={ window.smush_tutorials.tutorials }
59 viewAll={ window.smush_tutorials.tutorials_link }
60 onCloseClick={ hideTutorials }
61 />,
62 tutorialsDiv
63 );
64 }
65
66 // Tutorials page.
67 const tutorialsPageBox = document.getElementById( 'smush-box-tutorials' );
68 if ( tutorialsPageBox ) {
69 ReactDOM.render(
70 <TutorialsList
71 category="11228"
72 title={ window.smush_tutorials.tutorials }
73 translate={ window.smush_tutorials.tutorials_strings }
74 />,
75 tutorialsPageBox
76 );
77 }
78 } );
79
80 jQuery(function ($) {
81 $(document).on('click', '#smush-box-tutorials li > [role="link"], #smush-dash-tutorials li > [role="link"]', function () {
82 const $tutorial = $(this);
83 const isDashPage = !!$tutorial.closest('#smush-dash-tutorials').length;
84 const decodeHtml = (html) => {
85 const txt = document.createElement("textarea");
86 txt.innerHTML = html;
87 return txt.value;
88 };
89 const title = decodeHtml($tutorial.attr('title'));
90
91 (new MixPanel()).track('Tutorial Opened', {
92 'Tutorial Name': title,
93 'Triggered From': isDashPage ? 'Dashboard' : 'Tutorials Tab'
94 });
95 });
96 });
1 /* global ajaxurl */
2
3 /**
4 * External dependencies
5 */
6 import assign from 'lodash/assign';
7
8 /**
9 * Wrapper function for ajax calls to WordPress.
10 *
11 * @since 3.12.0
12 */
13 function SmushFetcher() {
14 /**
15 * Request ajax with a promise.
16 * Use FormData Object as data if you need to upload file
17 *
18 * @param {string} action
19 * @param {Object|FormData} data
20 * @param {string} method
21 * @return {Promise<any>} Request results.
22 */
23 function request(action, data = {}, method = 'POST') {
24 const args = {
25 url: ajaxurl,
26 method,
27 cache: false
28 };
29
30 if (data instanceof FormData) {
31 data.append('action', action);
32 data.append('_ajax_nonce', window.wp_smush_msgs.nonce);
33 args.contentType = false;
34 args.processData = false;
35 } else {
36 data._ajax_nonce = data._ajax_nonce || window.wp_smush_msgs.nonce;
37 data.action = action;
38 }
39 args.data = data;
40 return new Promise((resolve, reject) => {
41 jQuery.ajax(args).done(resolve).fail(reject);
42 }).then((response) => {
43 if (typeof response !== 'object') {
44 response = JSON.parse(response);
45 }
46 return response;
47 }).catch((error) => {
48 console.error('Error:', error);
49 });
50 }
51
52 const methods = {
53 /**
54 * Manage ajax for background.
55 */
56 background: {
57 /**
58 * Start background process.
59 */
60 start: () => {
61 return request('bulk_smush_start');
62 },
63
64 /**
65 * Cancel background process.
66 */
67 cancel: () => {
68 return request('bulk_smush_cancel');
69 },
70
71 /**
72 * Initial State - Get stats on the first time.
73 */
74 initState: () => {
75 return request('bulk_smush_get_status');
76 },
77
78 /**
79 * Get stats.
80 */
81 getStatus: () => {
82 return request('bulk_smush_get_status');
83 },
84
85 getStats: () => {
86 return request('bulk_smush_get_global_stats');
87 }
88 },
89 smush: {
90 /**
91 * Sync stats.
92 */
93 syncStats: ( data ) => {
94 data = data || {};
95 return request('get_stats', data);
96 },
97
98 /**
99 * Ignore All.
100 */
101 ignoreAll: ( type ) => {
102 return request('wp_smush_ignore_all_failed_items', {
103 type: type,
104 });
105 },
106 },
107
108 /**
109 * Manage ajax for other requests
110 */
111 common: {
112 /**
113 * Dismiss Notice.
114 *
115 * @param {string} dismissId Notification id.
116 */
117 dismissNotice: (dismissId) => {
118 return request('smush_dismiss_notice', {
119 key: dismissId
120 });
121 },
122
123 /**
124 * Hide the new features modal.
125 *
126 * @param {string} modalID Notification id.
127 */
128 hideModal: (modalID) => request('hide_modal', {
129 modal_id: modalID,
130 }),
131
132 /**
133 * Custom request.
134 *
135 * @param {Object} data
136 */
137 request: (data) => data.action && request(data.action, data),
138 },
139 };
140
141 assign(this, methods);
142 }
143
144 const SmushAjax = new SmushFetcher();
145 export default SmushAjax;
...\ No newline at end of file ...\ No newline at end of file
1 /**
2 * External dependencies
3 */
4 import React from 'react';
5 import ReactDOM from 'react-dom';
6
7 /**
8 * WordPress dependencies
9 */
10 import domReady from '@wordpress/dom-ready';
11 const { __, sprintf } = wp.i18n;
12
13 /**
14 * SUI dependencies
15 */
16 import { Presets } from '@wpmudev/shared-presets';
17
18 export const Configs = ({ isWidget }) => {
19 // TODO: Handle the html interpolation and translation better.
20 const proDescription = (
21 <>
22 {__(
23 'You can easily apply configs to multiple sites at once via ',
24 'wp-smushit'
25 )}
26 <a
27 href={window.smushReact.links.hubConfigs}
28 target="_blank"
29 rel="noreferrer"
30 >
31 {__('the Hub.')}
32 </a>
33 </>
34 );
35
36 const closeIcon = __('Close this dialog window', 'wp-smushit'),
37 cancelButton = __('Cancel', 'wp-smushit');
38
39 const lang = {
40 title: __('Preset Configs', 'wp-smushit'),
41 upload: __('Upload', 'wp-smushit'),
42 save: __('Save config', 'wp-smushit'),
43 loading: __('Updating the config list…', 'wp-smushit'),
44 emptyNotice: __(
45 'You don’t have any available config. Save preset configurations of Smush’s settings, then upload and apply them to your other sites in just a few clicks!',
46 'wp-smushit'
47 ),
48 baseDescription: __(
49 'Use configs to save preset configurations of Smush’s settings, then upload and apply them to your other sites in just a few clicks!',
50 'wp-smushit'
51 ),
52 proDescription,
53 syncWithHubText: __(
54 'Created or updated configs via the Hub?',
55 'wp-smushit'
56 ),
57 syncWithHubButton: __('Check again', 'wp-smushit'),
58 apply: __('Apply', 'wp-smushit'),
59 download: __('Download', 'wp-smushit'),
60 edit: __('Name and Description', 'wp-smushit'),
61 delete: __('Delete', 'wp-smushit'),
62 notificationDismiss: __('Dismiss notice', 'wp-smushit'),
63 freeButtonLabel: __('Try The Hub', 'wp-smushit'),
64 defaultRequestError: sprintf(
65 /* translators: %s request status */
66 __(
67 'Request failed. Status: %s. Please reload the page and try again.',
68 'wp-smushit'
69 ),
70 '{status}'
71 ),
72 uploadActionSuccessMessage: sprintf(
73 /* translators: %s request status */
74 __(
75 '%s config has been uploaded successfully – you can now apply it to this site.',
76 'wp-smushit'
77 ),
78 '{configName}'
79 ),
80 uploadWrongPluginErrorMessage: sprintf(
81 /* translators: %s {pluginName} */
82 __(
83 'The uploaded file is not a %s Config. Please make sure the uploaded file is correct.',
84 'wp-smushit'
85 ),
86 '{pluginName}'
87 ),
88 applyAction: {
89 closeIcon,
90 cancelButton,
91 title: __('Apply Config', 'wp-smushit'),
92 description: sprintf(
93 /* translators: %s config name */
94 __(
95 'Are you sure you want to apply the %s config to this site? We recommend you have a backup available as your existing settings configuration will be overridden.',
96 'wp-smushit'
97 ),
98 '{configName}'
99 ),
100 actionButton: __('Apply', 'wp-smushit'),
101 successMessage: sprintf(
102 /* translators: %s. config name */
103 __('%s config has been applied successfully.', 'wp-smushit'),
104 '{configName}'
105 ),
106 },
107 deleteAction: {
108 closeIcon,
109 cancelButton,
110 title: __('Delete Configuration File', 'wp-smushit'),
111 description: sprintf(
112 /* translators: %s config name */
113 __(
114 'Are you sure you want to delete %s? You will no longer be able to apply it to this or other connected sites.',
115 'wp-smushit'
116 ),
117 '{configName}'
118 ),
119 actionButton: __('Delete', 'wp-smushit'),
120 },
121 editAction: {
122 closeIcon,
123 cancelButton,
124 nameInput: __('Config name', 'wp-smushit'),
125 descriptionInput: __('Description', 'wp-smushit'),
126 emptyNameError: __('The config name is required', 'wp-smushit'),
127 actionButton: __('Save', 'wp-smushit'),
128 editTitle: __('Rename Config', 'wp-smushit'),
129 editDescription: __(
130 'Change your config name to something recognizable.',
131 'wp-smushit'
132 ),
133 createTitle: __('Save Config', 'wp-smushit'),
134 createDescription: __(
135 'Save your current settings configuration. You’ll be able to then download and apply it to your other sites.',
136 'wp-smushit'
137 ),
138 successMessage: sprintf(
139 /* translators: %s. config name */
140 __('%s config created successfully.', 'wp-smushit'),
141 '{configName}'
142 ),
143 },
144 settingsLabels: {
145 bulk_smush: __('Bulk Smush', 'wp-smushit'),
146 integrations: __('Integrations', 'wp-smushit'),
147 lazy_load: __('Lazy Load', 'wp-smushit'),
148 cdn: __('CDN', 'wp-smushit'),
149 webp_mod: __('Local WebP', 'wp-smushit'),
150 settings: __('Settings', 'wp-smushit'),
151 networkwide: __('Subsite Controls', 'wp-smushit'),
152 },
153 };
154
155 return (
156 <Presets
157 isWidget={isWidget}
158 isPro={window.smushReact.isPro}
159 isWhitelabel={window.smushReact.hideBranding}
160 sourceLang={lang}
161 sourceUrls={window.smushReact.links}
162 requestsData={window.smushReact.requestsData}
163 />
164 );
165 };
166
167 domReady(function () {
168 const configsPageBox = document.getElementById('smush-box-configs');
169 if (configsPageBox) {
170 ReactDOM.render(<Configs isWidget={false} />, configsPageBox);
171 }
172 const configsWidgetBox = document.getElementById('smush-widget-configs');
173 if (configsWidgetBox) {
174 ReactDOM.render(<Configs isWidget={true} />, configsWidgetBox);
175 }
176 });
1 /* global ajaxurl */
2
3 /**
4 * External dependencies
5 */
6 import React from 'react';
7 import ReactDOM from 'react-dom';
8
9 /**
10 * WordPress dependencies
11 */
12 import domReady from '@wordpress/dom-ready';
13
14 /**
15 * Internal dependencies
16 */
17 import StepsBar from '../views/webp/steps-bar';
18 import StepContent from '../views/webp/step-content';
19 import FreeContent from '../views/webp/free-content';
20 import StepFooter from '../views/webp/step-footer';
21
22 export const WebpPage = ({ smushData }) => {
23 const [currentStep, setCurrentStep] = React.useState(
24 parseInt(smushData.startStep)
25 );
26
27 React.useEffect(() => {
28 if (2 === currentStep) {
29 window.SUI.suiCodeSnippet();
30 }
31 }, [currentStep]);
32
33 const [serverType, setServerType] = React.useState(
34 smushData.detectedServer
35 );
36 const [rulesMethod, setRulesMethod] = React.useState('automatic');
37 const [rulesError, setRulesError] = React.useState(false);
38
39 const makeRequest = (action, verb = 'GET') => {
40 return new Promise((resolve, reject) => {
41 const xhr = new XMLHttpRequest();
42 xhr.open(
43 verb,
44 `${ajaxurl}?action=${action}&_ajax_nonce=${smushData.nonce}`,
45 true
46 );
47
48 xhr.setRequestHeader(
49 'Content-type',
50 'application/x-www-form-urlencoded'
51 );
52
53 xhr.onload = () => {
54 if (xhr.status >= 200 && xhr.status < 300) {
55 resolve(JSON.parse(xhr.response));
56 } else {
57 reject(xhr);
58 }
59 };
60 xhr.onerror = () => reject(xhr);
61 xhr.send();
62 });
63 };
64
65 const stepContent = smushData.isPro ? (
66 <StepContent
67 currentStep={currentStep}
68 serverType={serverType}
69 rulesMethod={rulesMethod}
70 setRulesMethod={setRulesMethod}
71 rulesError={rulesError}
72 setServerType={setServerType}
73 smushData={smushData}
74 makeRequest={makeRequest}
75 />
76 ) : (
77 <FreeContent smushData={smushData} />
78 );
79
80 return (
81 <React.Fragment>
82 <div className="sui-box-body sui-no-padding">
83 <div className="sui-row-with-sidenav">
84 <StepsBar smushData={smushData} currentStep={currentStep} />
85 {stepContent}
86 </div>
87 </div>
88 {smushData.isPro && (
89 <StepFooter
90 currentStep={currentStep}
91 setCurrentStep={setCurrentStep}
92 serverType={serverType}
93 rulesMethod={rulesMethod}
94 setRulesError={setRulesError}
95 makeRequest={makeRequest}
96 />
97 )}
98 </React.Fragment>
99 );
100 };
101
102 domReady(function () {
103 const webpPageBox = document.getElementById('smush-box-webp-wizard');
104 if (webpPageBox) {
105 ReactDOM.render(
106 <WebpPage smushData={window.smushReact} />,
107 webpPageBox
108 );
109 }
110 });
1 /**
2 * External dependencies
3 */
4 import React from 'react';
5
6 /**
7 * WordPress dependencies
8 */
9 const { __ } = wp.i18n;
10
11 export default ({ smushData }) => {
12 return (
13 <div className="sui-box-body">
14 <div className="sui-message">
15 <img
16 className="sui-image"
17 src={smushData.urls.freeImg}
18 alt={__('Smush WebP', 'wp-smushit')}
19 />
20
21 <div className="sui-message-content">
22 <p>
23 {__(
24 'Fix the "Serve images in next-gen format" Google PageSpeed recommendation by setting up this feature. Serve WebP versions of your images to supported browsers, and gracefully fall back on JPEGs and PNGs for browsers that don\'t support WebP.',
25 'wp-smushit'
26 )}
27 </p>
28
29 <ol className="sui-upsell-list">
30 <li>
31 <span
32 className="sui-icon-check sui-sm"
33 aria-hidden="true"
34 />
35 {__(
36 'Add or automatically apply the rules to enable Local WebP feature.',
37 'wp-smushit'
38 )}
39 </li>
40 <li>
41 <span
42 className="sui-icon-check sui-sm"
43 aria-hidden="true"
44 />
45 {__(
46 'Fix “Serve images in next-gen format" Google PageSpeed recommendation.',
47 'wp-smushit'
48 )}
49 </li>
50 <li>
51 <span
52 className="sui-icon-check sui-sm"
53 aria-hidden="true"
54 />
55 {__(
56 'Serve WebP version of images in the browsers that support it and fall back to JPEGs and PNGs for non supported browsers.',
57 'wp-smushit'
58 )}
59 </li>
60 </ol>
61
62 <p className="sui-margin-top">
63 <a
64 href={smushData.urls.upsell}
65 className="sui-button sui-button-purple"
66 style={{ marginRight: '30px' }}
67 target="_blank"
68 rel="noreferrer"
69 >
70 {__('Try WebP for free', 'wp-smushit')}
71 </a>
72 <a
73 href={smushData.urls.webpDoc}
74 style={{ color: '#8D00B1' }}
75 target="_blank"
76 rel="noreferrer"
77 >
78 {__('Learn more', 'wp-smushit')}
79 </a>
80 </p>
81 </div>
82 </div>
83 </div>
84 );
85 };
1 /**
2 * External dependencies
3 */
4 import React from 'react';
5
6 /**
7 * WordPress dependencies
8 */
9 const { __ } = wp.i18n;
10
11 export default ({
12 currentStep,
13 setCurrentStep,
14 serverType,
15 rulesMethod,
16 setRulesError,
17 makeRequest,
18 }) => {
19 const genericRequestError = __(
20 'Something went wrong with the request.',
21 'wp-smushit'
22 );
23
24 const checkStatus = () => {
25 setRulesError(false);
26
27 makeRequest('smush_webp_get_status')
28 .then((res) => {
29 if (res.success) {
30 setCurrentStep(currentStep + 1);
31 } else {
32 setRulesError(res.data);
33 }
34 })
35 .catch(() => setRulesError(genericRequestError));
36 };
37
38 const applyRules = () => {
39 setRulesError(false);
40
41 makeRequest('smush_webp_apply_htaccess_rules')
42 .then((res) => {
43 if (res.success) {
44 return checkStatus();
45 }
46
47 setRulesError(res.data);
48 })
49 .catch(() => setRulesError(genericRequestError));
50 };
51
52 const hideWizard = (e) => {
53 e.currentTarget.classList.add(
54 'sui-button-onload',
55 'sui-button-onload-text'
56 );
57 makeRequest('smush_toggle_webp_wizard').then(() => location.reload());
58 };
59
60 // Markup stuff.
61 let buttonsLeft;
62
63 const quitButton = (
64 <button
65 type="button"
66 className="sui-button sui-button-ghost"
67 onClick={hideWizard}
68 >
69 <span className="sui-loading-text">
70 <span className="sui-icon-logout" aria-hidden="true"></span>
71 <span className="sui-hidden-xs">
72 {__('Quit setup', 'wp-smushit')}
73 </span>
74 <span className="sui-hidden-sm sui-hidden-md sui-hidden-lg">
75 {__('Quit', 'wp-smushit')}
76 </span>
77 </span>
78
79 <span
80 className="sui-icon-loader sui-loading"
81 aria-hidden="true"
82 ></span>
83 </button>
84 );
85
86 if (1 !== currentStep) {
87 buttonsLeft = (
88 <button
89 type="button"
90 className="sui-button sui-button-compound sui-button-ghost"
91 onClick={() => setCurrentStep(currentStep - 1)}
92 >
93 <span className="sui-compound-desktop" aria-hidden="true">
94 <span className="sui-icon-arrow-left"></span>
95 {__('Previous', 'wp-smushit')}
96 </span>
97
98 <span className="sui-compound-mobile" aria-hidden="true">
99 <span className="sui-icon-arrow-left"></span>
100 </span>
101
102 <span className="sui-screen-reader-text">
103 {__('Previous', 'wp-smushit')}
104 </span>
105 </button>
106 );
107 }
108
109 const getButtonsRight = () => {
110 if (1 === currentStep) {
111 return (
112 <button
113 type="button"
114 className="sui-button sui-button-blue sui-button-icon-right"
115 onClick={() => setCurrentStep(currentStep + 1)}
116 >
117 {__('Next', 'wp-smushit')}
118 <span
119 className="sui-icon-arrow-right"
120 aria-hidden="true"
121 ></span>
122 </button>
123 );
124 }
125
126 if (2 === currentStep) {
127 if ('apache' === serverType && 'automatic' === rulesMethod) {
128 return (
129 <button
130 type="button"
131 className="sui-button sui-button-blue"
132 onClick={applyRules}
133 >
134 {__('Apply rules', 'wp-smushit')}
135 </button>
136 );
137 }
138
139 return (
140 <button
141 type="button"
142 className="sui-button sui-button-blue"
143 onClick={checkStatus}
144 >
145 {__('Check status', 'wp-smushit')}
146 </button>
147 );
148 }
149
150 return (
151 <button
152 type="button"
153 className="sui-button sui-button-blue"
154 onClick={hideWizard}
155 >
156 <span className="sui-button-text-default">
157 {__('Finish', 'wp-smushit')}
158 </span>
159
160 <span className="sui-button-text-onload">
161 <span
162 className="sui-icon-loader sui-loading"
163 aria-hidden="true"
164 ></span>
165 {__('Finishing setup…', 'wp-smushit')}
166 </span>
167 </button>
168 );
169 };
170
171 return (
172 <div className="sui-box-footer">
173 <div className="sui-actions-left">
174 {quitButton}
175 {buttonsLeft}
176 </div>
177 <div className="sui-actions-right">{getButtonsRight()}</div>
178 </div>
179 );
180 };
1 /**
2 * External dependencies
3 */
4 import React from 'react';
5
6 /**
7 * WordPress dependencies
8 */
9 const { __ } = wp.i18n;
10
11 export default ({ currentStep, smushData }) => {
12 const getStepClass = (step) => {
13 const stepClass = 'smush-wizard-bar-step';
14
15 if (!smushData.isPro) {
16 return stepClass + ' disabled';
17 }
18
19 if (step > currentStep) {
20 return stepClass;
21 }
22
23 return (
24 stepClass +
25 (step === currentStep ? ' current' : ' sui-tooltip done')
26 );
27 };
28
29 const getStepNumber = (step) => {
30 return currentStep > step ? (
31 <span className="sui-icon-check" aria-hidden="true"></span>
32 ) : (
33 step
34 );
35 };
36
37 const steps = [
38 { number: 1, title: __('Server Type', 'wp-smushit') },
39 { number: 2, title: __('Add Rules', 'wp-smushit') },
40 { number: 3, title: __('Finish Setup', 'wp-smushit') },
41 ];
42
43 return (
44 <div className="sui-sidenav">
45 <span className="smush-wizard-bar-subtitle">
46 {__('Setup', 'wp-smushit')}
47 </span>
48 <div className="smush-sidenav-title">
49 <h4>{__('Local WebP', 'wp-smushit')}</h4>
50 {!smushData.isPro && (
51 <span className="sui-tag sui-tag-pro">
52 {__('Pro', 'wp-smushit')}
53 </span>
54 )}
55 </div>
56
57 <div className="smush-wizard-steps-container">
58 <svg
59 className="smush-svg-mobile"
60 focusable="false"
61 aria-hidden="true"
62 >
63 <line
64 x1="0"
65 x2="50%"
66 stroke={1 !== currentStep ? '#1ABC9C' : '#E6E6E6'}
67 />
68 <line
69 x1="50%"
70 x2="100%"
71 stroke={3 === currentStep ? '#1ABC9C' : '#E6E6E6'}
72 />
73 </svg>
74 <ul>
75 {steps.map((step) => (
76 <React.Fragment key={step.number}>
77 <li
78 className={getStepClass(step.number)}
79 data-tooltip={__(
80 'This stage is already completed.',
81 'wp-smushit'
82 )}
83 >
84 <div className="smush-wizard-bar-step-number">
85 {getStepNumber(step.number)}
86 </div>
87 {step.title}
88 </li>
89 {3 !== step.number && (
90 <svg
91 data={step.number}
92 data2={currentStep}
93 className="smush-svg-desktop"
94 focusable="false"
95 aria-hidden="true"
96 >
97 <line
98 y1="0"
99 y2="40px"
100 stroke={
101 step.number < currentStep
102 ? '#1ABC9C'
103 : '#E6E6E6'
104 }
105 />
106 </svg>
107 )}
108 </React.Fragment>
109 ))}
110 </ul>
111 </div>
112 </div>
113 );
114 };
1 @include body-class(true) {
2 &.sui-color-accessible {
3 .smush-final-log .smush-bulk-error-row {
4 box-shadow: inset 2px 0 0 0 $accessible-dark;
5 .smush-bulk-image-data:before {
6 color: $accessible-dark;
7 }
8 }
9 // Bulk Smush Fancy Tree
10 ul.fancytree-container {
11 .fancytree-selected {
12 background-color: #F8F8F8;
13 span.fancytree-checkbox {
14 border: 1px solid $accessible-dark;
15 background-color: $accessible-dark;
16 }
17 }
18 span.fancytree-expander:before,
19 span.fancytree-icon:before,
20 span.fancytree-title {
21 color: $accessible-dark;
22 }
23 }
24 // CDN
25 .smush-filename-extension {
26 background-color: $accessible-dark;
27 }
28 // Check images button.
29 .sui-button {
30 &.smush-button-check-success:before {
31 color: $accessible-light;
32 }
33 }
34 // Smush submit note.
35 .smush-submit-note {
36 color: $accessible-dark;
37 }
38
39 // Hightlight lazyload spinner.
40 .sui-lazyload .sui-box-selector [name="animation[spinner-icon]"]:checked+span {
41 background-color: rgba(220,220,222, 0.7)!important;
42 }
43 }
44 }
45
46 @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
47 .sui-wrap .sui-toggle-slider {
48 -ms-high-contrast-adjust: none;
49 }
50 }
1 // This needs to be here.
2 @import "modules/variables";
3
4 // Share UI styles
5 @import "~@wpmudev/shared-ui/scss/functions";
6 @import "~@wpmudev/shared-ui/scss/colors";
7 @import "~@wpmudev/shared-ui/scss/variables";
8 $google-fonts-url: 'https://fonts.bunny.net/css?family=Roboto:400,500,700';
9 @import "~@wpmudev/shared-ui/scss/mixins";
10 @import "~@wpmudev/shared-ui/scss/accessibility";
11 @import "~@wpmudev/shared-ui/scss/animations";
12 @import "~@wpmudev/shared-ui/scss/typography";
13 @import "~@wpmudev/shared-ui/scss/icons";
14 @import "~@wpmudev/shared-ui/scss/buttons";
15 @import "~@wpmudev/shared-ui/scss/toggles";
16 @import "~@wpmudev/shared-ui/scss/boxes";
17 @import "~@wpmudev/shared-ui/scss/box-settings";
18 @import "~@wpmudev/shared-ui/scss/layout";
19 @import "~@wpmudev/shared-ui/scss/notifications";
20 @import "~@wpmudev/shared-ui/scss/header";
21 @import "~@wpmudev/shared-ui/scss/summary";
22 @import "~@wpmudev/shared-ui/scss/list";
23 @import "~@wpmudev/shared-ui/scss/tooltips";
24 @import "~@wpmudev/shared-ui/scss/select2";
25 @import "~@wpmudev/shared-ui/scss/tags";
26 @import "~@wpmudev/shared-ui/scss/forms";
27 @import "~@wpmudev/shared-ui/scss/radio-checkbox";
28 @import "~@wpmudev/shared-ui/scss/tabs";
29 @import "~@wpmudev/shared-ui/scss/sidenav";
30 @import "~@wpmudev/shared-ui/scss/dropdowns";
31 @import "~@wpmudev/shared-ui/scss/scores";
32 @import "~@wpmudev/shared-ui/scss/footer";
33 @import "~@wpmudev/shared-ui/scss/progress-bars";
34 @import "~@wpmudev/shared-ui/scss/modals";
35 @import "~@wpmudev/shared-ui/scss/utility";
36 @import "~@wpmudev/shared-ui/scss/wp-admin-notices";
37 @import "~@wpmudev/shared-ui/scss/tables";
38 @import "~@wpmudev/shared-ui/scss/accordions";
39 // Used on lazy loading page (since 3.2.2).
40 @import "~@wpmudev/shared-ui/scss/box-selectors";
41 @import "~@wpmudev/shared-ui/scss/upload";
42 @import "~@wpmudev/shared-ui/scss/_colorpickers.scss";
43 // Upgrade page (since 3.2.3).
44 @import "~@wpmudev/shared-ui/scss/upgrade-page";
45 @import "~@wpmudev/shared-ui/scss/reviews";
46 // Used on WebP page (since 3.8.0).
47 @import "~@wpmudev/shared-ui/scss/_code-snippet.scss";
48 // Upsells (since 3.9.1).
49 @import "~@wpmudev/shared-ui/scss/upsells";
50
51 // App styles
52 @import "modules/admin";
53 @import "modules/directory-smush";
54 @import "modules/cdn";
55 @import "modules/webp";
56
57 // SUI Color Accessibility
58 @import "~@wpmudev/shared-ui/scss/color-accessibility";
59 @import "accessibility/color-accessibility";
1 @import "variables";
2
3 /**
4 * CDN styles
5 *
6 * @since 3.0
7 */
8
9 @include body-class {
10
11 .sui-wrap {
12
13 .sui-box-settings-row .sui-box-settings-col-1 {
14 vertical-align: top;
15 }
16
17 &.wrap-smush-cdn {
18 .sui-block-content-center p {
19 max-width: 600px;
20 margin: 20px auto 30px;
21 }
22 .sui-box-header .sui-actions-right .sui-icon-info{
23 font-size: 16px;
24 position: relative;
25 top: 1.5px;
26 }
27 }
28
29 .sui-cdn {
30 form p:first-of-type {
31 margin-top: 0;
32 }
33 }
34 .wp-smush-stats {
35 display: flex;
36 align-items: center;
37 line-height: 0;
38
39 .sui-tooltip {
40 line-height: 10px;
41 margin-right: 10px;
42 }
43 }
44
45 /* Filename Extensions Icons */
46 .smush-filename-extension {
47 border-radius: 4px;
48 display: inline-block;
49 font-size: 9px;
50 font-weight: 600;
51 color: #fff;
52 text-transform: uppercase;
53 text-align: center;
54 line-height: 43px;
55 height: 30px;
56 margin: 0 5px 0 0;
57 width: 30px;
58
59 &.smush-extension-jpeg,
60 &.smush-extension-jpg { background-color: #F7E100; }
61 &.smush-extension-png { background-color: #FFB694; }
62 &.smush-extension-gif { background-color: #72D5D4; }
63 &.smush-extension-webp { background-color: #72ADD5; }
64 &.smush-extension-svg { background-color: #88D572; }
65 &.smush-extension-iframe {
66 background-color: #8772D5;
67 font-size: 7px;
68 }
69 }
70 }
71
72 }
1 /* ****************************************************************************
2 * MEDIA AREA SCSS FILE
3 */
4 @import "~@wpmudev/shared-ui/scss/functions";
5 @import "~@wpmudev/shared-ui/scss/colors";
6 @import "~@wpmudev/shared-ui/scss/variables";
7 // Override body class
8 $sui-version: 'smush-media';
9 $sui-wrap-class: false;
10 @import "~@wpmudev/shared-ui/scss/mixins";
11 @import "~@wpmudev/shared-ui/scss/tooltips";
12
13 /* ****************************************************************************
14 * MEDIA AREA STYLES
15 */
16
17 // Set column width.
18 .manage-column.column-smushit {
19 width: 260px;
20 }
21
22 // Margin for buttons.
23 .sui-smush-media {
24 .button {
25 margin-right: 5px;
26 &:last-of-type {
27 margin-right: 0;
28 }
29 }
30 }
31
32 // Smush button loading icon.
33 #ngg-listimages,
34 .column-smushit {
35 .spinner {
36 float: none;
37
38 &.visible {
39 visibility: visible;
40 }
41 }
42 }
43 .smush-status-links{
44 .smush-upgrade-link {
45 color: #8D00B1;
46 font-size: 12px;
47 }
48 .smush-ignore-utm,.smush-revert-utm{
49 display: block;
50 margin: 6px 0 4px;
51 }
52 a {
53 text-decoration: none;
54 }
55 span {
56 float: none !important;;
57 }
58 .smush-cdn-notice {
59 color: #50575E;
60 a {
61 color:#2271B1;
62 &:focus {
63 box-shadow: none;
64 opacity: 0.7;
65 }
66 }
67 }
68 }
69 .smush-status {
70 &.smush-warning,&.smush-ignored,&.smush-success{
71 padding-left:17px;
72 position: relative;
73 &:before{
74 content:"";
75 background: url('../images/icon-warning.png' ) no-repeat 0 0;
76 position: absolute;
77 width:12px;
78 height:12px;
79 background-size: contain;
80 left: 0;
81 top:3px;
82 }
83 }
84 &.smush-ignored{
85 &:before{
86 background-image: url('../images/icon-ignored.png' ) !important;
87 }
88 }
89 &.smush-success{
90 &:before{
91 background-image: url('../images/icon-success.png' ) !important;
92 }
93 }
94 .sui-icon-warning-media-lib {
95 margin-right:4px;
96 position:relative;
97 top:1px;
98 }
99 }
100 .column-smushit .smush-status{
101 color:#50575E;
102 }
103 // Stats table.
104 .sui-smush-media {
105 table.wp-smush-stats-holder {
106 width: 100%;
107 border: 1px solid #E6E6E6;
108 border-radius: 4px;
109 margin-top: 6px;
110 border-collapse: collapse;
111 border-spacing: 0;
112 thead {
113 th.smush-stats-header {
114 padding: 8px 10px;
115 border-bottom: 1px solid #E6E6E6 !important;
116 color: #32373D;
117 font-size: 12px;
118 font-weight: bold;
119 letter-spacing: -0.23px;
120 line-height: 16px;
121 text-align: left;
122 }
123 }
124 tr {
125 border: 1px solid #E6E6E6;
126 }
127 td {
128 overflow-wrap: break-word;
129 vertical-align: middle;
130 padding: 8px 10px;
131 color: #555555;
132 font-size: 11px;
133 letter-spacing: -0.21px;
134 line-height: 16px;
135 border-bottom: 1px solid #E6E6E6;
136 &:first-of-type {
137 max-width: 110px;
138 font-weight: 500;
139 }
140 }
141 }
142 }
143
144 // Override !important set from WordPress.
145 #the-list {
146 .sui-smush-media {
147 thead {
148 th.smush-stats-header {
149 border-bottom: 1px solid #E6E6E6 !important;
150 }
151 }
152 }
153 }
154
155 // Responsive table for list mode.
156 @media screen and (max-width: 1024px) {
157 .wp-list-table .smushit {
158 table.wp-smush-stats-holder {
159 th {
160 display: table-cell;
161 box-sizing: border-box;
162 }
163 tr td {
164 word-wrap: break-word;
165 display: table-cell !important;
166 &:first-child {
167 border-right: none;
168 box-sizing: border-box;
169 }
170 &:last-child {
171 box-sizing: border-box;
172 float: none;
173 overflow: visible;
174 }
175 }
176 }
177 }
178 }
179
180 // NextGen Integration.
181 .iedit .wp-smush-action,
182 .iedit .smush-stats-details {
183 font-size: 11px;
184 }
185
186 /*NextGen Gallery stats*/
187 #ngg-listimages {
188 table.wp-smush-stats-holder {
189 table-layout: fixed;
190 border: 1px solid lightgray;
191 border-collapse: collapse;
192 width: 100%;
193 td,
194 th {
195 border: 1px solid #CECECE;
196 }
197 }
198 .column-7 {
199 width: 300px;
200 }
201 .spinner {
202 width: auto;
203 padding-left: 30px;
204 }
205 }
206
207 /** NextGen Gallery tr height, to show the progress bar properly for alternate rows **/
208 .alternate.iedit {
209 height: 120px;
210 }
211
212 /** Allows to click on button, otherwise row-actions from NextGen interferes **/
213 .wp-smush-nextgen-send {
214 position: relative;
215 z-index: 2;
216 }
1 /* ****************************************************************************
2 * MODULE: VARIABLES
3 */
4
5 $font--path: "../fonts" !default;
6 $img--path: "../images" !default;
7
8 $sui-font-path: '~@wpmudev/shared-ui/dist/fonts/';
9
10 $summary-image: '#{$img--path}/smush-graphic-dashboard-summary.svg';
11
12 // Promo banners for free footer
13 $cross-sell-1: 'hummingbird';
14 $cross-sell-2: 'defender';
15 $cross-sell-3: 'smartcrawl';
16
17 $upgrade-image: '../../app/assets/images/hero@2x.png';
18 $upgrade-image-mobile: '../../app/assets/images/hero.png';
...\ No newline at end of file ...\ No newline at end of file
1 /**
2 * Webp styles
3 *
4 * @since 3.8.0
5 */
6
7 @include body-class(true) {
8
9 #smush-box-webp-webp {
10
11 .smush-webp-supported-browser {
12 height: 30px;
13 width: 30px;
14 padding: 5px;
15 margin-right: 10px;
16 border-radius: 4px;
17 background-color: #F2F2F2;
18 display: inline-block;
19
20 img {
21 height: 20px;
22 width: 20px;
23 }
24 }
25 }
26
27 #smush-box-webp-wizard {
28 .sui-row-with-sidenav {
29 margin-bottom: 0;
30
31 .sui-sidenav {
32 padding: 30px;
33 border-top-left-radius: $border-radius;
34 background-color: #F8F8F8;
35
36 .smush-wizard-bar-subtitle {
37 font-size: 11px;
38 line-height: 20px;
39 font-weight: 700;
40 color: #AAAAAA;
41 text-transform: uppercase;
42 }
43
44 .smush-sidenav-title {
45 display: flex;
46 align-items: center;
47 margin-bottom: 33px;
48
49 h4 {
50 margin: 0;
51 line-height: 20px;
52 }
53 }
54
55 .smush-wizard-steps-container {
56
57 ul {
58 margin: 0;
59
60 @include media(max-width, lg) {
61 display: flex;
62 flex-direction: row;
63 justify-content: space-between;
64 }
65 }
66
67 .smush-wizard-bar-step {
68 display: inline-block;
69 font-size: 13px;
70 color: #AAAAAA;
71 line-height: 22px;
72 font-weight: 500;
73 margin-bottom: 0;
74
75 .smush-wizard-bar-step-number {
76 display: inline-block;
77 margin-right: 10px;
78 text-align: center;
79 border-radius: 50%;
80 width: 22px;
81 height: 22px;
82 font-size: 11px;
83 background-color: #F2F2F2;
84 border: 1px solid #DDDDDD;
85
86 @include media(max-width, lg) {
87 display: block;
88 margin: 0 auto 5px auto;
89 }
90 }
91
92 &.disabled {
93 color: #DDDDDD;
94 }
95
96 &.current {
97 color: #333333;
98
99 .smush-wizard-bar-step-number {
100 background-color: #FFFFFF;
101 border-color: #333333;
102 }
103 }
104
105 &.done .smush-wizard-bar-step-number {
106 background-color: #1ABC9C;
107 border-color: #1ABC9C;
108
109 .sui-icon-check::before {
110 color: #FFFFFF;
111 }
112 }
113
114 @include media(min-width, lg) {
115 display: block;
116 }
117
118 @include media(max-width, lg) {
119 width: 70px;
120 text-align: center;
121 }
122 }
123
124 svg {
125
126 line {
127 stroke-width: 4px;
128 }
129
130 &.smush-svg-desktop {
131 height: 40px;
132 width: 22px;
133 margin-left: 10px;
134 display: none;
135
136 @include media(min-width, lg) {
137 display: block;
138 }
139 }
140
141 &.smush-svg-mobile {
142 width: 100%;
143 height: 4px;
144 display: block;
145 margin-bottom: -14px;
146 padding: 0 35px;
147
148 @include media(min-width, lg) {
149 display: none;
150 }
151 }
152 }
153 }
154
155 @include media(max-width, sm) {
156 padding: 20px;
157 }
158 }
159
160 .smush-wizard-steps-content-wrapper {
161 padding: 20px;
162
163 .smush-wizard-steps-content {
164 padding: 0 70px;
165 text-align: center;
166
167 &:first-child {
168 padding-top: 30px;
169 }
170
171 .smush-step-indicator {
172 font-size: 11px;
173 font-weight: 500;
174 color: #888888;
175 }
176
177 h2 {
178 margin: 0;
179 }
180
181 @include media(max-width, sm) {
182 padding: 0;
183 }
184 }
185
186 &.smush-wizard-step-1 {
187
188 .sui-box-selectors {
189 padding-left: 115px;
190 padding-right: 115px;
191 margin-bottom: 15px;
192 }
193 }
194
195 &.smush-wizard-step-2 {
196
197 .smush-wizard-rules-wrapper {
198 text-align: left;
199 }
200
201 .sui-tabs-menu {
202 justify-content: center;
203 }
204 }
205
206 @include media(min-width, sm) {
207 padding: 30px;
208 }
209 }
210 }
211 }
212 }
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.