ccb73512 by Jeff Balicki

smush

1 parent d967a066
Showing 286 changed files with 4924 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/onboarding' );
38 require( './modules/directory-smush' );
39 require( './smush/cdn' );
40 require( './smush/webp' );
41 require( './smush/lazy-load' );
42 require( './modules/bulk-restore' );
43 require( './smush/settings' );
44
45 /**
46 * Notice scripts.
47 *
48 * Notices are used in the following functions:
49 *
50 * @used-by \Smush\Core\Modules\Smush::smush_updated()
51 * @used-by \Smush\Core\Integrations\S3::3_support_required_notice()
52 * @used-by \Smush\App\Abstract_Page::installation_notice()
53 *
54 * TODO: should this be moved out in a separate file like common.scss?
55 */
56 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();
...\ No newline at end of file ...\ No newline at end of file
1 import lazySizes from 'lazysizes';
2
3 lazySizes.init();
...\ No newline at end of file ...\ No newline at end of file
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 ${
172 image.props.computed_height
173 }px</span>
174 <i class="smush-front-icons smush-front-icon-arrows-in" aria-hidden="true">&nbsp;</i>
175 <span class="smush-tag smush-tag-success">${ image.props.real_width } × ${
176 image.props.real_height
177 }px</span>
178 </div>
179 <div class="smush-image-description">${ tooltip }</div>
180 `;
181
182 document
183 .getElementById( 'smush-image-bar-items-' + type )
184 .appendChild( item );
185 } );
186 },
187
188 /**
189 * Show/hide sections based on images.
190 */
191 toggleDivs() {
192 const types = [ 'bigger', 'smaller' ];
193 types.forEach( ( type ) => {
194 const div = document.getElementById(
195 'smush-image-bar-items-' + type
196 );
197 if ( 0 === this.images[ type ].length ) {
198 div.style.display = 'none';
199 } else {
200 div.style.display = 'block';
201 }
202 } );
203 },
204
205 /**
206 * Scroll the selected image into view and highlight it.
207 *
208 * @param {Object} e
209 */
210 highlightImage( e ) {
211 this.removeSelection();
212
213 const el = document.getElementsByClassName(
214 e.currentTarget.dataset.image
215 );
216 if ( 'undefined' !== typeof el[ 0 ] ) {
217 // Display description box.
218 e.currentTarget.classList.toggle( 'show-description' );
219
220 // Scroll and flash image.
221 el[ 0 ].scrollIntoView( {
222 behavior: 'smooth',
223 block: 'center',
224 inline: 'nearest',
225 } );
226 el[ 0 ].style.opacity = '0.5';
227 setTimeout( () => {
228 el[ 0 ].style.opacity = '1';
229 }, 1000 );
230 }
231 },
232
233 /**
234 * Handle click on the toggle item.
235 */
236 handleToggleClick() {
237 this.bar.classList.toggle( 'closed' );
238 this.toggle.classList.toggle( 'closed' );
239 this.removeSelection();
240 },
241
242 /**
243 * Remove selected items.
244 */
245 removeSelection() {
246 const items = document.getElementsByClassName( 'show-description' );
247 if ( items.length > 0 ) {
248 Array.from( items ).forEach( ( div ) =>
249 div.classList.remove( 'show-description' )
250 );
251 }
252 },
253
254 /**
255 * Function to highlight all scaled images.
256 *
257 * Add yellow border and then show one small box to
258 * resize the images as per the required size, on fly.
259 */
260 detectImages() {
261 const images = document.getElementsByTagName( 'img' );
262 for ( const image of images ) {
263 if ( this.shouldSkipImage( image ) ) {
264 continue;
265 }
266
267 // Get defined width and height.
268 const props = {
269 real_width: image.clientWidth,
270 real_height: image.clientHeight,
271 computed_width: image.naturalWidth,
272 computed_height: image.naturalHeight,
273 bigger_width: image.clientWidth * 1.5 < image.naturalWidth,
274 bigger_height:
275 image.clientHeight * 1.5 < image.naturalHeight,
276 smaller_width: image.clientWidth > image.naturalWidth,
277 smaller_height: image.clientHeight > image.naturalHeight,
278 };
279
280 // In case image is in correct size, do not continue.
281 if (
282 ! props.bigger_width &&
283 ! props.bigger_height &&
284 ! props.smaller_width &&
285 ! props.smaller_height
286 ) {
287 continue;
288 }
289
290 const imgType =
291 props.bigger_width || props.bigger_height
292 ? 'bigger'
293 : 'smaller',
294 imageClass =
295 'smush-image-' + ( this.images[ imgType ].length + 1 );
296
297 // Fill the images arrays.
298 this.images[ imgType ].push( {
299 src: image,
300 props,
301 class: imageClass,
302 } );
303
304 /**
305 * Add class to original image.
306 * Can't add two classes in single add(), because no support in IE11.
307 * image.classList.add('smush-detected-img', imageClass);
308 */
309 image.classList.add( 'smush-detected-img' );
310 image.classList.add( imageClass );
311 }
312 }, // End detectImages()
313
314 /**
315 * Allows refreshing the list. A good way is to refresh on lazyload actions.
316 *
317 * @since 3.6.0
318 */
319 refresh() {
320 // Clear out classes on DOM.
321 for ( let id in this.images.bigger ) {
322 if ( this.images.bigger.hasOwnProperty( id ) ) {
323 this.images.bigger[ id ].src.classList.remove(
324 'smush-detected-img'
325 );
326 this.images.bigger[ id ].src.classList.remove(
327 'smush-image-' + ++id
328 );
329 }
330 }
331
332 for ( let id in this.images.smaller ) {
333 if ( this.images.smaller.hasOwnProperty( id ) ) {
334 this.images.smaller[ id ].src.classList.remove(
335 'smush-detected-img'
336 );
337 this.images.smaller[ id ].src.classList.remove(
338 'smush-image-' + ++id
339 );
340 }
341 }
342
343 this.images = {
344 bigger: [],
345 smaller: [],
346 };
347
348 // This might be overkill - there will probably never be a situation when there are less images than on
349 // initial page load.
350 const elements = document.getElementsByClassName(
351 'smush-resize-box'
352 );
353 while ( elements.length > 0 ) {
354 elements[ 0 ].remove();
355 }
356
357 this.process();
358 },
359 }; // End WP_Smush_IRS
360
361 /**
362 * After page load, initialize toggle event.
363 */
364 window.addEventListener( 'DOMContentLoaded', () => SmushIRS.init() );
365 window.addEventListener( 'lazyloaded', () => SmushIRS.refresh() );
366 } )();
1 import '../scss/common.scss';
2
3 /* global ajaxurl */
4
5 document.addEventListener( 'DOMContentLoaded', function() {
6 const dismissNoticeBtn = document.getElementById(
7 'smush-dismiss-conflict-notice'
8 );
9 if ( dismissNoticeBtn ) {
10 dismissNoticeBtn.addEventListener( 'click', dismissNotice );
11 }
12
13 const dismissNoticeTop = document.querySelector(
14 '#smush-conflict-notice > .notice-dismiss'
15 );
16 if ( dismissNoticeTop ) {
17 dismissNoticeTop.addEventListener( 'click', dismissNotice );
18 }
19
20 function dismissNotice() {
21 const xhr = new XMLHttpRequest();
22 xhr.open(
23 'POST',
24 ajaxurl + '?action=dismiss_check_for_conflicts',
25 true
26 );
27 xhr.onload = () => {
28 const btn = document.querySelector(
29 '#smush-conflict-notice > button.notice-dismiss'
30 );
31 if ( btn ) {
32 btn.trigger('click');
33 }
34 };
35 xhr.send();
36 }
37 } );
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 /**
6 * Bulk restore JavaScript code.
7 *
8 * @since 3.2.2
9 */
10 (function () {
11 'use strict';
12
13 /**
14 * Bulk restore modal.
15 *
16 * @since 3.2.2
17 */
18 WP_Smush.restore = {
19 modal: document.getElementById('smush-restore-images-dialog'),
20 contentContainer: document.getElementById('smush-bulk-restore-content'),
21 settings: {
22 slide: 'start', // start, progress or finish.
23 success: 0,
24 errors: [],
25 },
26 items: [], // total items, 1 item = 1 step.
27 success: [], // successful items restored.
28 errors: [], // failed items.
29 currentStep: 0,
30 totalSteps: 0,
31
32 /**
33 * Init module.
34 */
35 init() {
36 if (!this.modal) {
37 return;
38 }
39
40 this.settings = {
41 slide: 'start',
42 success: 0,
43 errors: [],
44 };
45
46 this.resetModalWidth();
47 this.renderTemplate();
48
49 // Show the modal.
50
51 window.SUI.openModal(
52 'smush-restore-images-dialog',
53 'wpbody-content',
54 undefined,
55 false
56 );
57 },
58
59 /**
60 * Update the template, register new listeners.
61 */
62 renderTemplate() {
63 const template = WP_Smush.onboarding.template('smush-bulk-restore');
64 const content = template(this.settings);
65
66 if (content) {
67 this.contentContainer.innerHTML = content;
68 }
69
70 this.bindSubmit();
71 },
72
73 /**
74 * Reset modal width.
75 *
76 * @since 3.6.0
77 */
78 resetModalWidth() {
79 this.modal.style.maxWidth = '460px';
80 this.modal.querySelector('.sui-box').style.maxWidth = '460px';
81 },
82
83 /**
84 * Catch "Finish setup wizard" button click.
85 */
86 bindSubmit() {
87 const confirmButton = this.modal.querySelector(
88 'button[id="smush-bulk-restore-button"]'
89 );
90 const self = this;
91
92 if (confirmButton) {
93 confirmButton.addEventListener('click', function (e) {
94 e.preventDefault();
95 self.resetModalWidth();
96
97 self.settings = { slide: 'progress' };
98 self.errors = [];
99
100 self.renderTemplate();
101 self.initScan();
102 });
103 }
104 },
105
106 /**
107 * Cancel the bulk restore.
108 */
109 cancel() {
110 if (
111 'start' === this.settings.slide ||
112 'finish' === this.settings.slide
113 ) {
114 // Hide the modal.
115 window.SUI.closeModal();
116 } else {
117 this.updateProgressBar(true);
118 window.location.reload();
119 }
120 },
121
122 /**
123 * Update progress bar during directory smush.
124 *
125 * @param {boolean} cancel Cancel status.
126 */
127 updateProgressBar(cancel = false) {
128 let progress = 0;
129 if (0 < this.currentStep) {
130 progress = Math.min(
131 Math.round((this.currentStep * 100) / this.totalSteps),
132 99
133 );
134 }
135
136 if (progress > 100) {
137 progress = 100;
138 }
139
140 // Update progress bar
141 this.modal.querySelector('.sui-progress-text span').innerHTML =
142 progress + '%';
143 this.modal.querySelector('.sui-progress-bar span').style.width =
144 progress + '%';
145
146 const statusDiv = this.modal.querySelector(
147 '.sui-progress-state-text'
148 );
149 if (progress >= 90) {
150 statusDiv.innerHTML = 'Finalizing...';
151 } else if (cancel) {
152 statusDiv.innerHTML = 'Cancelling...';
153 } else {
154 statusDiv.innerHTML =
155 this.currentStep +
156 '/' +
157 this.totalSteps +
158 ' ' +
159 'images restored';
160 }
161 },
162
163 /**
164 * First step in bulk restore - get the bulk attachment count.
165 */
166 initScan() {
167 const self = this;
168 const _nonce = document.getElementById('_wpnonce');
169
170 const xhr = new XMLHttpRequest();
171 xhr.open('POST', ajaxurl + '?action=get_image_count', true);
172 xhr.setRequestHeader(
173 'Content-type',
174 'application/x-www-form-urlencoded'
175 );
176 xhr.onload = () => {
177 if (200 === xhr.status) {
178 const res = JSON.parse(xhr.response);
179 if ('undefined' !== typeof res.data.items) {
180 self.items = res.data.items;
181 self.totalSteps = res.data.items.length;
182 self.step();
183 }
184 } else {
185 window.console.log(
186 'Request failed. Returned status of ' + xhr.status
187 );
188 }
189 };
190 xhr.send('_ajax_nonce=' + _nonce.value);
191 },
192
193 /**
194 * Execute a scan step recursively
195 */
196 step() {
197 const self = this;
198 const _nonce = document.getElementById('_wpnonce');
199
200 if (0 < this.items.length) {
201 const item = this.items.pop();
202 const xhr = new XMLHttpRequest();
203 xhr.open('POST', ajaxurl + '?action=restore_step', true);
204 xhr.setRequestHeader(
205 'Content-type',
206 'application/x-www-form-urlencoded'
207 );
208 xhr.onload = () => {
209 this.currentStep++;
210
211 if (200 === xhr.status) {
212 const res = JSON.parse(xhr.response);
213 if (
214 'undefined' !== typeof res.data.success &&
215 res.data.success
216 ) {
217 self.success.push(item);
218 } else {
219 self.errors.push({
220 id: item,
221 src: res.data.src,
222 thumb: res.data.thumb,
223 link: res.data.link,
224 });
225 }
226 }
227
228 self.updateProgressBar();
229 self.step();
230 };
231 xhr.send('item=' + item + '&_ajax_nonce=' + _nonce.value);
232 } else {
233 // Finish.
234 this.settings = {
235 slide: 'finish',
236 success: this.success.length,
237 errors: this.errors,
238 total: this.totalSteps,
239 };
240
241 self.renderTemplate();
242 if (0 < this.errors.length) {
243 this.modal.style.maxWidth = '660px';
244 this.modal.querySelector('.sui-box').style.maxWidth =
245 '660px';
246 }
247 }
248 },
249 };
250
251 /**
252 * Template function (underscores based).
253 *
254 * @type {Function}
255 */
256 WP_Smush.restore.template = _.memoize((id) => {
257 let compiled;
258 const options = {
259 evaluate: /<#([\s\S]+?)#>/g,
260 interpolate: /{{{([\s\S]+?)}}}/g,
261 escape: /{{([^}]+?)}}(?!})/g,
262 variable: 'data',
263 };
264
265 return (data) => {
266 _.templateSettings = options;
267 compiled =
268 compiled || _.template(document.getElementById(id).innerHTML);
269 return compiled(data);
270 };
271 });
272 })();
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
12 ( function( $ ) {
13 'use strict';
14
15 WP_Smush.bulk = {
16 init: () => {
17 /**
18 * Handle the Bulk Smush/Bulk re-Smush button click.
19 */
20 $( '.wp-smush-all' ).on( 'click', function( e ) {
21 e.preventDefault();
22
23 $( '.sui-notice-top.sui-notice-success' ).remove();
24
25 const bulkWarning = document.getElementById(
26 'bulk_smush_warning'
27 );
28 bulkWarning.classList.add( 'sui-hidden' );
29
30 // Remove limit exceeded styles.
31 const progress = $( '.wp-smush-bulk-progress-bar-wrapper' );
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 ( bulkWarning ) {
40 document
41 .getElementById( 'bulk-smush-resume-button' )
42 .classList.add( 'sui-hidden' );
43 }
44
45 // Disable re-Smush and scan button.
46 // TODO: refine what is disabled.
47 $(
48 '.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'
49 ).prop( 'disabled', true );
50
51 // Check for IDs, if there is none (unsmushed or lossless), don't call Smush function.
52 /** @param {Array} wp_smushit_data.unsmushed */
53 if (
54 'undefined' === typeof window.wp_smushit_data ||
55 ( 0 === window.wp_smushit_data.unsmushed.length &&
56 0 === window.wp_smushit_data.resmush.length )
57 ) {
58 return false;
59 }
60
61 $( '.wp-smush-remaining' ).addClass( 'sui-hidden' );
62
63 // Show loader.
64 progress
65 .find( '.sui-progress-block i.sui-icon-info' )
66 .removeClass( 'sui-icon-info' )
67 .addClass( 'sui-loading' )
68 .addClass( 'sui-icon-loader' );
69
70 new Smush( $( this ), true );
71 } );
72
73 /**
74 * Ignore file from bulk Smush.
75 *
76 * @since 2.9.0
77 */
78 $( 'body' ).on( 'click', '.smush-ignore-image', function( e ) {
79 e.preventDefault();
80
81 const self = $( this );
82
83 self.prop( 'disabled', true );
84 self.attr( 'data-tooltip' );
85 self.removeClass( 'sui-tooltip' );
86 $.post( ajaxurl, {
87 action: 'ignore_bulk_image',
88 id: self.attr( 'data-id' ),
89 } ).done( ( response ) => {
90 if (
91 self.is( 'a' ) &&
92 response.success &&
93 'undefined' !== typeof response.data.links
94 ) {
95 self.parent()
96 .parent()
97 .find( '.smush-status' )
98 .text( wp_smush_msgs.ignored );
99 e.target.closest( '.smush-status-links' ).innerHTML =
100 response.data.links;
101 }
102 } );
103 } );
104
105 /**
106 * Show upsell on free version and when there are no images to compress.
107 *
108 * @since 3.7.2
109 */
110 const upsellBox = document.getElementById( 'smush-box-bulk-upgrade' );
111 if (
112 upsellBox &&
113 !window.wp_smushit_data.unsmushed.length &&
114 !window.wp_smushit_data.resmush.length
115 ) {
116 upsellBox.classList.remove( 'sui-hidden' );
117 }
118 },
119 };
120
121 WP_Smush.bulk.init();
122 } )( 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 // If disabled, do not process
82 if ( $( this ).prop( 'disabled' ) ) {
83 return;
84 }
85
86 const button = $( this );
87
88 $( 'div.wp-smush-list-dialog div.sui-box-body' ).css( {
89 opacity: '0.8',
90 } );
91 $( 'div.wp-smush-list-dialog div.sui-box-body a' ).off(
92 'click'
93 );
94
95 // Disable button
96 button.prop( 'disabled', true );
97
98 // Display the spinner.
99 button.addClass('sui-button-onload');
100
101 const selectedFolders = self.tree.getSelectedNodes();
102
103 const paths = [];
104 selectedFolders.forEach( function( folder ) {
105 paths.push( folder.key );
106 } );
107
108 // Send a ajax request to get a list of all the image files
109 const param = {
110 action: 'image_list',
111 smush_path: paths,
112 image_list_nonce: $(
113 'input[name="image_list_nonce"]'
114 ).val(),
115 };
116
117 $.post( ajaxurl, param, function( response ) {
118 window.SUI.closeModal();
119
120 if ( response.success ) {
121 self.scanner = new Scanner( response.data, 0 );
122 self.showProgressDialog( response.data );
123 self.scanner.scan();
124 } else {
125 window.SUI.openNotice(
126 'wp-smush-ajax-notice',
127 response.data.message,
128 { type: 'warning' }
129 );
130 }
131 } );
132 } );
133
134 /**
135 * Cancel scan.
136 */
137 progressDialog.on(
138 'click',
139 '#cancel-directory-smush, #dialog-close-div, .wp-smush-cancel-dir',
140 function (e) {
141 e.preventDefault();
142 // Display the spinner
143 $('.wp-smush-cancel-dir').addClass('sui-button-onload');
144 self.scanner
145 .cancel()
146 .done(
147 () =>
148 ( window.location.href =
149 self.wp_smush_msgs.directory_url )
150 );
151 }
152 );
153
154 /**
155 * Continue scan.
156 */
157 progressDialog.on(
158 'click',
159 '.sui-icon-play, .wp-smush-resume-scan',
160 function( e ) {
161 e.preventDefault();
162 self.scanner.resume();
163 }
164 );
165
166 /**
167 * Check to see if we should open the directory module.
168 * Used to redirect from dashboard page.
169 *
170 * @since 3.8.6
171 */
172 const queryString = window.location.search;
173 const urlParams = new URLSearchParams(queryString);
174 if (urlParams.has('start') && !this.triggered) {
175 this.triggered = true;
176 $('button.wp-smush-browse').trigger('click');
177 }
178 },
179
180 /**
181 * Init fileTree.
182 */
183 initFileTree() {
184 const self = this,
185 smushButton = $( 'button#wp-smush-select-dir' ),
186 ajaxSettings = {
187 type: 'GET',
188 url: ajaxurl,
189 data: {
190 action: 'smush_get_directory_list',
191 list_nonce: $( 'input[name="list_nonce"]' ).val(),
192 },
193 cache: false,
194 };
195
196 // Object already defined.
197 if ( Object.entries( self.tree ).length > 0 ) {
198 return;
199 }
200
201 self.tree = createTree( '.wp-smush-list-dialog .content', {
202 autoCollapse: true, // Automatically collapse all siblings, when a node is expanded
203 clickFolderMode: 3, // 1:activate, 2:expand, 3:activate and expand, 4:activate (dblclick expands)
204 checkbox: true, // Show checkboxes
205 debugLevel: 0, // 0:quiet, 1:errors, 2:warnings, 3:infos, 4:debug
206 selectMode: 3, // 1:single, 2:multi, 3:multi-hier
207 tabindex: '0', // Whole tree behaves as one single control
208 keyboard: true, // Support keyboard navigation
209 quicksearch: true, // Navigate to next node by typing the first letters
210 source: ajaxSettings,
211 lazyLoad: ( event, data ) => {
212 data.result = new Promise( function( resolve, reject ) {
213 ajaxSettings.data.dir = data.node.key;
214 $.ajax( ajaxSettings )
215 .done( ( response ) => resolve( response ) )
216 .fail( reject );
217 } );
218 },
219 loadChildren: ( event, data ) =>
220 data.node.fixSelection3AfterClick(), // Apply parent's state to new child nodes:
221 select: () =>
222 smushButton.prop(
223 'disabled',
224 ! +self.tree.getSelectedNodes().length
225 ),
226 init: () => smushButton.prop( 'disabled', true ),
227 } );
228 },
229
230 /**
231 * Show progress dialog.
232 *
233 * @param {number} items Number of items in the scan.
234 */
235 showProgressDialog( items ) {
236 // Update items status and show the progress dialog..
237 $( '.wp-smush-progress-dialog .sui-progress-state-text' ).html(
238 '0/' + items + ' ' + self.wp_smush_msgs.progress_smushed
239 );
240
241 window.SUI.openModal(
242 'wp-smush-progress-dialog',
243 'dialog-close-div',
244 undefined,
245 false
246 );
247 },
248
249 /**
250 * Update progress bar during directory smush.
251 *
252 * @param {number} progress Current progress in percent.
253 * @param {boolean} cancel Cancel status.
254 */
255 updateProgressBar( progress, cancel = false ) {
256 if ( progress > 100 ) {
257 progress = 100;
258 }
259
260 // Update progress bar
261 $( '.sui-progress-block .sui-progress-text span' ).text(
262 progress + '%'
263 );
264 $( '.sui-progress-block .sui-progress-bar span' ).width(
265 progress + '%'
266 );
267
268 if ( progress >= 90 ) {
269 $( '.sui-progress-state .sui-progress-state-text' ).text(
270 'Finalizing...'
271 );
272 }
273
274 if ( cancel ) {
275 $( '.sui-progress-state .sui-progress-state-text' ).text(
276 'Cancelling...'
277 );
278 }
279 },
280 };
281
282 WP_Smush.directory.init();
283 } )( 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 init: () => {},
15
16 /**
17 * Convert bytes to human readable form.
18 *
19 * @param {number} a Bytes
20 * @param {number} b Number of digits
21 * @return {*} Formatted Bytes
22 */
23 formatBytes: ( a, b ) => {
24 const thresh = 1024,
25 units = [ 'KB', 'MB', 'GB', 'TB', 'PB' ];
26
27 if ( Math.abs( a ) < thresh ) {
28 return a + ' B';
29 }
30
31 let u = -1;
32
33 do {
34 a /= thresh;
35 ++u;
36 } while ( Math.abs( a ) >= thresh && u < units.length - 1 );
37
38 return a.toFixed( b ) + ' ' + units[ u ];
39 },
40
41 /**
42 * Get size from a string.
43 *
44 * @param {string} formattedSize Formatter string
45 * @return {*} Formatted Bytes
46 */
47 getSizeFromString: ( formattedSize ) => {
48 return formattedSize.replace( /[a-zA-Z]/g, '' ).trim();
49 },
50
51 /**
52 * Get type from formatted string.
53 *
54 * @param {string} formattedSize Formatted string
55 * @return {*} Formatted Bytes
56 */
57 getFormatFromString: ( formattedSize ) => {
58 return formattedSize.replace( /[0-9.]/g, '' ).trim();
59 },
60
61 /**
62 * Stackoverflow: http://stackoverflow.com/questions/1726630/formatting-a-number-with-exactly-two-decimals-in-javascript
63 *
64 * @param {number} num
65 * @param {number} decimals
66 * @return {number} Number
67 */
68 precise_round: ( num, decimals ) => {
69 const sign = num >= 0 ? 1 : -1;
70 // Keep the percentage below 100.
71 num = num > 100 ? 100 : num;
72 return (
73 Math.round( num * Math.pow( 10, decimals ) + sign * 0.001 ) /
74 Math.pow( 10, decimals )
75 );
76 },
77
78 /**
79 * Displays a floating error message using the #wp-smush-ajax-notice container.
80 *
81 * @since 3.8.0
82 *
83 * @param {string} message
84 */
85 showErrorNotice: ( message ) => {
86 if ( 'undefined' === typeof message ) {
87 return;
88 }
89
90 const noticeMessage = `<p>${ message }</p>`,
91 noticeOptions = {
92 type: 'error',
93 icon: 'info',
94 };
95
96 SUI.openNotice( 'wp-smush-ajax-notice', noticeMessage, noticeOptions );
97
98 const loadingButton = document.querySelector( '.sui-button-onload' );
99 if ( loadingButton ) {
100 loadingButton.classList.remove( 'sui-button-onload' );
101 }
102 },
103
104 /**
105 * Reset settings.
106 *
107 * @since 3.2.0
108 */
109 resetSettings: () => {
110 const _nonce = document.getElementById( 'wp_smush_reset' );
111 const xhr = new XMLHttpRequest();
112 xhr.open( 'POST', ajaxurl + '?action=reset_settings', true );
113 xhr.setRequestHeader(
114 'Content-type',
115 'application/x-www-form-urlencoded'
116 );
117 xhr.onload = () => {
118 if ( 200 === xhr.status ) {
119 const res = JSON.parse( xhr.response );
120 if ( 'undefined' !== typeof res.success && res.success ) {
121 window.location.href = wp_smush_msgs.smush_url;
122 }
123 } else {
124 window.console.log(
125 'Request failed. Returned status of ' + xhr.status
126 );
127 }
128 };
129 xhr.send( '_ajax_nonce=' + _nonce.value );
130 },
131 };
132
133 WP_Smush.helpers.init();
134 } )();
1 /* global ajaxurl */
2 /* global wp_smush_msgs */
3
4 /**
5 * @typedef {Object} jQuery
6 * @property
7 */
8 ( function( $ ) {
9 'use strict';
10
11 /**
12 * S3 support alert.
13 *
14 * @since 3.6.2 Moved from class-s3.php
15 */
16 if ( $( '#wp-smush-s3support-alert' ).length ) {
17 const noticeOptions = {
18 type: 'warning',
19 icon: 'info',
20 dismiss: {
21 show: true,
22 label: wp_smush_msgs.noticeDismiss,
23 tooltip: wp_smush_msgs.noticeDismissTooltip,
24 },
25 };
26
27 window.SUI.openNotice(
28 'wp-smush-s3support-alert',
29 $( '#wp-smush-s3support-alert' ).data( 'message' ),
30 noticeOptions
31 );
32 }
33
34 // Dismiss S3 support alert.
35 $( '#wp-smush-s3support-alert' ).on( 'click', 'button', () => {
36 $.post( ajaxurl, { action: 'dismiss_s3support_alert' } );
37 } );
38
39 // Remove API message.
40 $( '#wp-smush-api-message button.sui-button-icon' ).on( 'click', function(
41 e
42 ) {
43 e.preventDefault();
44 const notice = $( '#wp-smush-api-message' );
45 notice.slideUp( 'slow', function() {
46 notice.remove();
47 } );
48 $.post( ajaxurl, { action: 'hide_api_message' } );
49 } );
50
51 // Hide the notice after a CTA button was clicked
52 function removeNotice(e) {
53 const $notice = $(e.currentTarget).closest('.smush-notice');
54 $notice.fadeTo(100, 0, () =>
55 $notice.slideUp(100, () => $notice.remove())
56 );
57 }
58
59 // Only used for the Dashboard notification for now.
60 $('.smush-notice .smush-notice-act').on('click', (e) => {
61 removeNotice(e);
62 });
63
64 // Only used for the upgrade notice.
65 $('.smush-notice .smush-notice-dismiss').on('click', (e) => {
66 removeNotice(e);
67 $.post(ajaxurl, { action: 'dismiss_upgrade_notice' });
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, { action: 'dismiss_update_info' } );
75 } );
76 } )( 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 settings: {
20 first: true,
21 last: false,
22 slide: 'start',
23 value: false,
24 },
25 selection: {
26 auto: true,
27 lossy: true,
28 strip_exif: true,
29 original: false,
30 lazy_load: true,
31 usage: false,
32 },
33 contentContainer: document.getElementById( 'smush-onboarding-content' ),
34 onboardingSlides: [
35 'start',
36 'auto',
37 'lossy',
38 'strip_exif',
39 'original',
40 'lazy_load',
41 'usage',
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 'start',
61 'auto',
62 'strip_exif',
63 'lazy_load',
64 'usage',
65 ];
66 this.selection.lossy = false;
67 }
68
69 if ( 'false' === dialog.dataset.tracking ) {
70 this.onboardingSlides.pop();
71 }
72
73 this.renderTemplate();
74
75 // Skip setup.
76 const skipButton = this.onboardingModal.querySelector(
77 '.smush-onboarding-skip-link'
78 );
79 if ( skipButton ) {
80 skipButton.addEventListener( 'click', this.skipSetup );
81 }
82
83 // Show the modal.
84 window.SUI.openModal(
85 'smush-onboarding-dialog',
86 'checking-files-dialog',
87 undefined,
88 false
89 );
90 },
91
92 /**
93 * Get swipe coordinates.
94 *
95 * @param {Object} e
96 */
97 handleTouchStart( e ) {
98 const firstTouch = e.touches[ 0 ];
99 this.touchX = firstTouch.clientX;
100 this.touchY = firstTouch.clientY;
101 },
102
103 /**
104 * Process swipe left/right.
105 *
106 * @param {Object} e
107 */
108 handleTouchMove( e ) {
109 if ( ! this.touchX || ! this.touchY ) {
110 return;
111 }
112
113 const xUp = e.touches[ 0 ].clientX,
114 yUp = e.touches[ 0 ].clientY,
115 xDiff = this.touchX - xUp,
116 yDiff = this.touchY - yUp;
117
118 if ( Math.abs( xDiff ) > Math.abs( yDiff ) ) {
119 if ( xDiff > 0 ) {
120 if ( false === WP_Smush.onboarding.settings.last ) {
121 WP_Smush.onboarding.next( null, 'next' );
122 }
123 } else if ( false === WP_Smush.onboarding.settings.first ) {
124 WP_Smush.onboarding.next( null, 'prev' );
125 }
126 }
127
128 this.touchX = null;
129 this.touchY = null;
130 },
131
132 /**
133 * Update the template, register new listeners.
134 *
135 * @param {string} directionClass Accepts: fadeInRight, fadeInLeft, none.
136 */
137 renderTemplate( directionClass = 'none' ) {
138 // Grab the selected value.
139 const input = this.onboardingModal.querySelector(
140 'input[type="checkbox"]'
141 );
142 if ( input ) {
143 this.selection[ input.id ] = input.checked;
144 }
145
146 const template = WP_Smush.onboarding.template( 'smush-onboarding' );
147 const content = template( this.settings );
148
149 if ( content ) {
150 this.contentContainer.innerHTML = content;
151
152 if ( 'none' === directionClass ) {
153 this.contentContainer.classList.add( 'loaded' );
154 } else {
155 this.contentContainer.classList.remove( 'loaded' );
156 this.contentContainer.classList.add( directionClass );
157 setTimeout( () => {
158 this.contentContainer.classList.add( 'loaded' );
159 this.contentContainer.classList.remove(
160 directionClass
161 );
162 }, 600 );
163 }
164 }
165
166 this.onboardingModal.addEventListener(
167 'touchstart',
168 this.handleTouchStart,
169 false
170 );
171 this.onboardingModal.addEventListener(
172 'touchmove',
173 this.handleTouchMove,
174 false
175 );
176
177 this.bindSubmit();
178 },
179
180 /**
181 * Catch "Finish setup wizard" button click.
182 */
183 bindSubmit() {
184 const submitButton = this.onboardingModal.querySelector(
185 'button[type="submit"]'
186 );
187 const self = this;
188
189 if ( submitButton ) {
190 submitButton.addEventListener( 'click', function( e ) {
191 e.preventDefault();
192
193 // Because we are not rendering the template, we need to update the last element value.
194 const input = self.onboardingModal.querySelector(
195 'input[type="checkbox"]'
196 );
197 if ( input ) {
198 self.selection[ input.id ] = input.checked;
199 }
200
201 const _nonce = document.getElementById(
202 'smush_quick_setup_nonce'
203 );
204
205 const xhr = new XMLHttpRequest();
206 xhr.open( 'POST', ajaxurl + '?action=smush_setup', true );
207 xhr.setRequestHeader(
208 'Content-type',
209 'application/x-www-form-urlencoded'
210 );
211 xhr.onload = () => {
212 if ( 200 === xhr.status ) {
213 WP_Smush.onboarding.showScanDialog();
214 } else {
215 window.console.log(
216 'Request failed. Returned status of ' +
217 xhr.status
218 );
219 }
220 };
221 xhr.send(
222 'smush_settings=' +
223 JSON.stringify( self.selection ) +
224 '&_ajax_nonce=' +
225 _nonce.value
226 );
227 } );
228 }
229 },
230
231 /**
232 * Handle navigation.
233 *
234 * @param {Object} e
235 * @param {null|string} whereTo
236 */
237 next( e, whereTo = null ) {
238 const index = this.onboardingSlides.indexOf( this.settings.slide );
239 let newIndex = 0;
240
241 if ( ! whereTo ) {
242 newIndex =
243 null !== e && e.classList.contains( 'next' )
244 ? index + 1
245 : index - 1;
246 } else {
247 newIndex = 'next' === whereTo ? index + 1 : index - 1;
248 }
249
250 const directionClass =
251 null !== e && e.classList.contains( 'next' )
252 ? 'fadeInRight'
253 : 'fadeInLeft';
254
255 this.settings = {
256 first: 0 === newIndex,
257 last: newIndex + 1 === this.onboardingSlides.length, // length !== index
258 slide: this.onboardingSlides[ newIndex ],
259 value: this.selection[ this.onboardingSlides[ newIndex ] ],
260 };
261
262 this.renderTemplate( directionClass );
263 },
264
265 /**
266 * Handle circle navigation.
267 *
268 * @param {string} target
269 */
270 goTo( target ) {
271 const newIndex = this.onboardingSlides.indexOf( target );
272
273 this.settings = {
274 first: 0 === newIndex,
275 last: newIndex + 1 === this.onboardingSlides.length, // length !== index
276 slide: target,
277 value: this.selection[ target ],
278 };
279
280 this.renderTemplate();
281 },
282
283 /**
284 * Skip onboarding experience.
285 */
286 skipSetup: () => {
287 const _nonce = document.getElementById( 'smush_quick_setup_nonce' );
288
289 const xhr = new XMLHttpRequest();
290 xhr.open(
291 'POST',
292 ajaxurl + '?action=skip_smush_setup&_ajax_nonce=' + _nonce.value
293 );
294 xhr.onload = () => {
295 if ( 200 === xhr.status ) {
296 WP_Smush.onboarding.showScanDialog();
297 } else {
298 window.console.log(
299 'Request failed. Returned status of ' + xhr.status
300 );
301 }
302 };
303 xhr.send();
304 },
305
306 /**
307 * Show checking files dialog.
308 */
309 showScanDialog() {
310 window.SUI.closeModal();
311 window.SUI.openModal(
312 'checking-files-dialog',
313 'wpbody-content',
314 undefined,
315 false
316 );
317
318 const nonce = document.getElementById( 'wp_smush_options_nonce' );
319
320 setTimeout( () => {
321 const xhr = new XMLHttpRequest();
322 xhr.open( 'POST', ajaxurl + '?action=scan_for_resmush', true );
323 xhr.setRequestHeader(
324 'Content-type',
325 'application/x-www-form-urlencoded'
326 );
327 xhr.onload = () => {
328 const elem = document.querySelector(
329 '#smush-onboarding-dialog'
330 );
331 elem.parentNode.removeChild( elem );
332
333 if ( 200 === xhr.status ) {
334 setTimeout( function() {
335 window.location.search = 'page=smush-bulk';
336 }, 1000 );
337 } else {
338 window.console.log(
339 'Request failed. Returned status of ' + xhr.status
340 );
341 }
342 };
343 xhr.send(
344 'type=media&get_ui=false&process_settings=false&wp_smush_options_nonce=' +
345 nonce.value
346 );
347 }, 3000 );
348 },
349
350 /**
351 * Hide new features modal.
352 *
353 * @since 3.7.0
354 */
355 hideUpgradeModal: () => {
356 const xhr = new XMLHttpRequest();
357 xhr.open('POST', ajaxurl + '?action=hide_new_features');
358 xhr.send();
359 },
360 };
361
362 /**
363 * Template function (underscores based).
364 *
365 * @type {Function}
366 */
367 WP_Smush.onboarding.template = _.memoize( ( id ) => {
368 let compiled;
369 const options = {
370 evaluate: /<#([\s\S]+?)#>/g,
371 interpolate: /{{{([\s\S]+?)}}}/g,
372 escape: /{{([^}]+?)}}(?!})/g,
373 variable: 'data',
374 };
375
376 return ( data ) => {
377 _.templateSettings = options;
378 compiled =
379 compiled ||
380 _.template( document.getElementById( id ).innerHTML );
381 return compiled( data );
382 };
383 } );
384
385 window.addEventListener( 'load', () => WP_Smush.onboarding.init() );
386 } )();
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/scores' );
10 require( '@wpmudev/shared-ui/dist/js/_src/select2.full' );
11 require( '@wpmudev/shared-ui/dist/js/_src/select2' );
12 require( '@wpmudev/shared-ui/dist/js/_src/tabs' );
13 require( '@wpmudev/shared-ui/dist/js/_src/upload' ); // Used on lazy load page (since 3.2.2).
14 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( 'view=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, { action: 'directory_smush_start' }, () =>
31 step(remainingSteps).fail(this.showScanError)
32 )
33 .fail(this.showScanError);
34 }
35 },
36
37 cancel() {
38 cancelling = true;
39 return jQuery.post( ajaxurl, { action: 'directory_smush_cancel' } );
40 },
41
42 getProgress() {
43 if ( cancelling ) {
44 return 0;
45 }
46 // O M G ... Logic at it's finest!
47 const remainingSteps = totalSteps - currentStep;
48 return Math.min(
49 Math.round(
50 ( parseInt( totalSteps - remainingSteps ) * 100 ) /
51 totalSteps
52 ),
53 99
54 );
55 },
56
57 onFinishStep( progress ) {
58 jQuery( '.wp-smush-progress-dialog .sui-progress-state-text' ).html(
59 currentStep -
60 failedItems +
61 '/' +
62 totalSteps +
63 ' ' +
64 window.wp_smush_msgs.progress_smushed
65 );
66 WP_Smush.directory.updateProgressBar( progress );
67 },
68
69 onFinish() {
70 WP_Smush.directory.updateProgressBar( 100 );
71 window.location.href =
72 window.wp_smush_msgs.directory_url + '&scan=done';
73 },
74
75 /**
76 * Displays an error when the scan request fails.
77 *
78 * @param {Object} res XHR object.
79 */
80 showScanError(res) {
81 const dialog = jQuery('#wp-smush-progress-dialog');
82
83 // Add the error class to show/hide elements in the dialog.
84 dialog
85 .removeClass('wp-smush-exceed-limit')
86 .addClass('wp-smush-scan-error');
87
88 // Add the error status and description to the error message.
89 dialog
90 .find('#smush-scan-error')
91 .text(`${res.status} ${res.statusText}`);
92
93 // Show/hide the 403 error specific instructions.
94 const forbiddenMessage = dialog.find('.smush-403-error-message');
95 if (403 !== res.status) {
96 forbiddenMessage.addClass('sui-hidden');
97 } else {
98 forbiddenMessage.removeClass('sui-hidden');
99 }
100 },
101
102 limitReached() {
103 const dialog = jQuery( '#wp-smush-progress-dialog' );
104
105 dialog.addClass( 'wp-smush-exceed-limit' );
106 dialog
107 .find( '#cancel-directory-smush' )
108 .attr( 'data-tooltip', window.wp_smush_msgs.bulk_resume );
109 dialog
110 .find( '.sui-box-body .sui-icon-close' )
111 .removeClass( 'sui-icon-close' )
112 .addClass( 'sui-icon-play' );
113 dialog
114 .find( '#cancel-directory-smush' )
115 .attr( 'id', 'cancel-directory-smush-disabled' );
116 },
117
118 resume() {
119 const dialog = jQuery( '#wp-smush-progress-dialog' );
120 const resume = dialog.find( '#cancel-directory-smush-disabled' );
121
122 dialog.removeClass( 'wp-smush-exceed-limit' );
123 dialog
124 .find( '.sui-box-body .sui-icon-play' )
125 .removeClass( 'sui-icon-play' )
126 .addClass( 'sui-icon-close' );
127 resume.attr( 'data-tooltip', 'Cancel' );
128 resume.attr( 'id', 'cancel-directory-smush' );
129
130 obj.scan();
131 },
132 };
133
134 /**
135 * Execute a scan step recursively
136 *
137 * Private to avoid overriding
138 *
139 * @param {number} remainingSteps
140 */
141 const step = function( remainingSteps ) {
142 if ( remainingSteps >= 0 ) {
143 currentStep = totalSteps - remainingSteps;
144 return jQuery.post(
145 ajaxurl,
146 {
147 action: 'directory_smush_check_step',
148 step: currentStep,
149 },
150 ( response ) => {
151 // We're good - continue on.
152 if (
153 'undefined' !== typeof response.success &&
154 response.success
155 ) {
156 if (
157 'undefined' !== typeof response.data &&
158 'undefined' !== typeof response.data.skipped &&
159 true === response.data.skipped
160 ) {
161 skippedItems++;
162 }
163
164 currentStep++;
165 remainingSteps = remainingSteps - 1;
166 obj.onFinishStep( obj.getProgress() );
167 step(remainingSteps).fail(obj.showScanError);
168 } else if (
169 'undefined' !== typeof response.data.error &&
170 'dir_smush_limit_exceeded' === response.data.error
171 ) {
172 // Limit reached. Stop.
173 obj.limitReached();
174 } else {
175 // Error? never mind, continue, but count them.
176 failedItems++;
177 currentStep++;
178 remainingSteps = remainingSteps - 1;
179 obj.onFinishStep( obj.getProgress() );
180 step(remainingSteps).fail(obj.showScanError);
181 }
182 }
183 );
184 }
185 return jQuery.post(
186 ajaxurl,
187 {
188 action: 'directory_smush_finish',
189 items: totalSteps - ( failedItems + skippedItems ),
190 failed: failedItems,
191 skipped: skippedItems,
192 },
193 ( response ) => obj.onFinish( response )
194 );
195 };
196
197 return obj;
198 };
199
200 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 },
152 });
153
154 /**
155 * Extend and override wp.media.view.AttachmentsBrowser to include our new filter.
156 *
157 * @since 3.0
158 */
159 const AttachmentsBrowser = wp.media.view.AttachmentsBrowser;
160 wp.media.view.AttachmentsBrowser = wp.media.view.AttachmentsBrowser.extend({
161 createToolbar() {
162 // Make sure to load the original toolbar
163 AttachmentsBrowser.prototype.createToolbar.call(this);
164 this.toolbar.set(
165 'MediaLibraryTaxonomyFilter',
166 new MediaLibraryTaxonomyFilter({
167 controller: this.controller,
168 model: this.collection.props,
169 priority: -75,
170 }).render()
171 );
172 },
173 });
174 })(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 // TODO: this might be a problem, if we can't recalculate on larger installs.
16 saveSettings($(this).serialize(), 'bulk');
17 runReCheck();
18 });
19
20 /**
21 * Lazy load page.
22 */
23 $('form#smush-lazy-load-form').on('submit', function (e) {
24 e.preventDefault();
25 $('#save-settings-button').addClass('sui-button-onload-text');
26 saveSettings($(this).serialize(), 'lazy-load');
27 });
28
29 /**
30 * CDN page.
31 */
32 $('form#smush-cdn-form').on('submit', function (e) {
33 e.preventDefault();
34 $('#save-settings-button').addClass('sui-button-onload-text');
35 saveSettings($(this).serialize(), 'cdn');
36 });
37
38 /**
39 * Integrations page.
40 */
41 $('form#smush-integrations-form').on('submit', function (e) {
42 e.preventDefault();
43 $('#save-settings-button').addClass('sui-button-onload-text');
44 saveSettings($(this).serialize(), 'integrations');
45 });
46
47 /**
48 * Tools page.
49 */
50 $('form#smush-tools-form').on('submit', function (e) {
51 e.preventDefault();
52 $('#save-settings-button').addClass('sui-button-onload-text');
53 saveSettings($(this).serialize(), 'tools');
54 });
55
56 /**
57 * Settings page.
58 */
59 $('form#smush-settings-form').on('submit', function (e) {
60 e.preventDefault();
61 $('#save-settings-button').addClass('sui-button-onload-text');
62 saveSettings($(this).serialize(), 'settings');
63 });
64
65 /**
66 * Save settings.
67 *
68 * @param {string} settings JSON string of settings.
69 * @param {string} page Settings page.
70 */
71 function saveSettings(settings, page) {
72 const xhr = new XMLHttpRequest();
73
74 xhr.open('POST', ajaxurl + '?action=smush_save_settings', true);
75 xhr.setRequestHeader(
76 'Content-type',
77 'application/x-www-form-urlencoded'
78 );
79
80 xhr.onload = () => {
81 $('#save-settings-button').removeClass(
82 'sui-button-onload-text sui-button-onload'
83 );
84
85 if (200 === xhr.status) {
86 const res = JSON.parse(xhr.response);
87 if ('undefined' !== typeof res.success && res.success) {
88 showSuccessNotice(wp_smush_msgs.settingsUpdated);
89 } else if (res.data && res.data.message) {
90 WP_Smush.helpers.showErrorNotice(res.data.message);
91 } else {
92 WP_Smush.helpers.showErrorNotice('Request failed.');
93 }
94 } else {
95 WP_Smush.helpers.showErrorNotice(
96 'Request failed. Returned status of ' + xhr.status
97 );
98 }
99 };
100
101 xhr.send(
102 'page=' +
103 page +
104 '&' +
105 settings +
106 '&_ajax_nonce=' +
107 wp_smush_msgs.nonce
108 );
109 }
110
111 /**
112 * Show successful update notice.
113 *
114 * @param {string} msg Notice message.
115 */
116 function showSuccessNotice(msg) {
117 const noticeMessage = `<p>${msg}</p>`,
118 noticeOptions = {
119 type: 'success',
120 icon: 'check',
121 };
122
123 SUI.openNotice('wp-smush-ajax-notice', noticeMessage, noticeOptions);
124
125 const loadingButton = document.querySelector('.sui-button-onload');
126 if (loadingButton) {
127 loadingButton.classList.remove('sui-button-onload');
128 }
129 }
130
131 /**
132 * Re-check images from bulk smush and integrations pages.
133 */
134 function runReCheck() {
135 $('#save-settings-button').addClass('sui-button-onload');
136
137 const param = {
138 action: 'scan_for_resmush',
139 wp_smush_options_nonce: $('#wp_smush_options_nonce').val(),
140 type: 'media',
141 };
142
143 // Send ajax, Update Settings, And Check For resmush.
144 $.post(ajaxurl, $.param(param)).done(function () {
145 //showSuccessNotice(r.data.notice);
146 $('#save-settings-button').removeClass('sui-button-onload');
147 });
148 }
149
150 /**
151 * Parse remove data change.
152 */
153 $('input[name=keep_data]').on('change', function (e) {
154 const otherClass =
155 'keep_data-true' === e.target.id
156 ? 'keep_data-false'
157 : 'keep_data-true';
158 e.target.parentNode.classList.add('active');
159 document
160 .getElementById(otherClass)
161 .parentNode.classList.remove('active');
162 });
163
164 /**
165 * Handle auto detect checkbox toggle, to show/hide highlighting notice.
166 */
167 $('input#detection').on('click', function () {
168 const noticeDiv = $('.smush-highlighting-notice');
169 const warningDiv = $('.smush-highlighting-warning');
170
171 // Setting enabled.
172 if ($(this).is(':checked')) {
173 // Highlighting is already active and setting not saved.
174 if (noticeDiv.length > 0) {
175 noticeDiv.show();
176 } else {
177 warningDiv.show();
178 }
179 } else {
180 noticeDiv.hide();
181 warningDiv.hide();
182 }
183 });
184 })(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 function hideTutorials() {
20 const xhr = new XMLHttpRequest();
21
22 xhr.open('POST', ajaxurl + '?action=smush_hide_tutorials', true);
23 xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
24
25 xhr.onload = () => {
26 if (200 === xhr.status) {
27 const noticeMessage = `<p>${window.wp_smush_msgs.tutorialsRemoved}</p>`,
28 noticeOptions = {
29 type: 'success',
30 icon: 'check',
31 };
32
33 window.SUI.openNotice(
34 'wp-smush-ajax-notice',
35 noticeMessage,
36 noticeOptions
37 );
38 }
39 };
40
41 xhr.send('_ajax_nonce=' + window.wp_smush_msgs.nonce);
42 }
43
44 /**
45 * Render the "Tutorials List" component.
46 *
47 * @since 2.8.5
48 */
49 domReady(function () {
50 // Tutorials section on Dashboard page.
51 const tutorialsDiv = document.getElementById('smush-dash-tutorials');
52 if (tutorialsDiv) {
53 ReactDOM.render(
54 <TutorialsSlider
55 category="11228"
56 title={window.smush_tutorials.tutorials}
57 viewAll={window.smush_tutorials.tutorials_link}
58 onCloseClick={hideTutorials}
59 />,
60 tutorialsDiv
61 );
62 }
63
64 // Tutorials page.
65 const tutorialsPageBox = document.getElementById('smush-box-tutorials');
66 if (tutorialsPageBox) {
67 ReactDOM.render(
68 <TutorialsList
69 category="11228"
70 title={window.smush_tutorials.tutorials}
71 translate={window.smush_tutorials.tutorials_strings}
72 />,
73 tutorialsPageBox
74 );
75 }
76 });
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 tools: __('Tools', 'wp-smushit'),
151 settings: __('Settings', 'wp-smushit'),
152 networkwide: __('Subsite Controls', 'wp-smushit'),
153 },
154 };
155
156 return (
157 <Presets
158 isWidget={isWidget}
159 isPro={window.smushReact.isPro}
160 isWhitelabel={window.smushReact.hideBranding}
161 sourceLang={lang}
162 sourceUrls={window.smushReact.links}
163 requestsData={window.smushReact.requestsData}
164 />
165 );
166 };
167
168 domReady(function () {
169 const configsPageBox = document.getElementById('smush-box-configs');
170 if (configsPageBox) {
171 ReactDOM.render(<Configs isWidget={false} />, configsPageBox);
172 }
173 const configsWidgetBox = document.getElementById('smush-widget-configs');
174 if (configsWidgetBox) {
175 ReactDOM.render(<Configs isWidget={true} />, configsWidgetBox);
176 }
177 });
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 .smush-cdn-stats {
29 .sui-circle-score {
30 svg {
31 circle:last-child {
32 stroke: $accessible-dark;
33 }
34 }
35 }
36 }
37 // Check images button.
38 .sui-button {
39 &.smush-button-check-success:before {
40 color: $accessible-light;
41 }
42 }
43 // Smush submit note.
44 .smush-submit-note {
45 color: $accessible-dark;
46 }
47 }
48 }
49
50 @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
51 .sui-wrap .sui-toggle-slider {
52 -ms-high-contrast-adjust: none;
53 }
54 }
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 @import "~@wpmudev/shared-ui/scss/mixins";
9 @import "~@wpmudev/shared-ui/scss/accessibility";
10 @import "~@wpmudev/shared-ui/scss/animations";
11 @import "~@wpmudev/shared-ui/scss/typography";
12 @import "~@wpmudev/shared-ui/scss/icons";
13 @import "~@wpmudev/shared-ui/scss/buttons";
14 @import "~@wpmudev/shared-ui/scss/toggles";
15 @import "~@wpmudev/shared-ui/scss/boxes";
16 @import "~@wpmudev/shared-ui/scss/box-settings";
17 @import "~@wpmudev/shared-ui/scss/layout";
18 @import "~@wpmudev/shared-ui/scss/notifications";
19 @import "~@wpmudev/shared-ui/scss/header";
20 @import "~@wpmudev/shared-ui/scss/summary";
21 @import "~@wpmudev/shared-ui/scss/list";
22 @import "~@wpmudev/shared-ui/scss/tooltips";
23 @import "~@wpmudev/shared-ui/scss/select2";
24 @import "~@wpmudev/shared-ui/scss/tags";
25 @import "~@wpmudev/shared-ui/scss/forms";
26 @import "~@wpmudev/shared-ui/scss/radio-checkbox";
27 @import "~@wpmudev/shared-ui/scss/tabs";;
28 @import "~@wpmudev/shared-ui/scss/sidenav";
29 @import "~@wpmudev/shared-ui/scss/dropdowns";
30 @import "~@wpmudev/shared-ui/scss/scores";
31 @import "~@wpmudev/shared-ui/scss/footer";
32 @import "~@wpmudev/shared-ui/scss/progress-bars";
33 @import "~@wpmudev/shared-ui/scss/modals";
34 @import "~@wpmudev/shared-ui/scss/utility";
35 @import "~@wpmudev/shared-ui/scss/wp-admin-notices";
36 @import "~@wpmudev/shared-ui/scss/tables";
37 @import "~@wpmudev/shared-ui/scss/accordions";
38 // Used on lazy loading page (since 3.2.2).
39 @import "~@wpmudev/shared-ui/scss/box-selectors";
40 @import "~@wpmudev/shared-ui/scss/upload";
41 @import "~@wpmudev/shared-ui/scss/_colorpickers.scss";
42 // Upgrade page (since 3.2.3).
43 @import "~@wpmudev/shared-ui/scss/upgrade-page";
44 @import "~@wpmudev/shared-ui/scss/reviews";
45 // Used on WebP page (since 3.8.0).
46 @import "~@wpmudev/shared-ui/scss/_code-snippet.scss";
47 // Upsells (since 3.9.1).
48 @import "~@wpmudev/shared-ui/scss/upsells";
49
50 // App styles
51 @import "modules/admin";
52 @import "modules/directory-smush";
53 @import "modules/cdn";
54 @import "modules/webp";
55
56 // SUI Color Accessibility
57 @import "~@wpmudev/shared-ui/scss/color-accessibility";
58 @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 .sui-block-content-center p {
18 max-width: 600px;
19 margin: 20px auto 30px;
20 }
21
22 .sui-cdn {
23 form p:first-of-type {
24 margin-top: 0;
25 }
26 }
27
28 .wp-smush-stats {
29 display: flex;
30 align-items: center;
31 line-height: 0;
32
33 .sui-tooltip {
34 line-height: 10px;
35 margin-right: 10px;
36 }
37 }
38
39 /* Filename Extensions Icons */
40 .smush-filename-extension {
41 border-radius: 4px;
42 display: inline-block;
43 font-size: 9px;
44 font-weight: 600;
45 color: #fff;
46 text-transform: uppercase;
47 text-align: center;
48 line-height: 43px;
49 height: 30px;
50 margin: 0 5px 0 0;
51 width: 30px;
52
53 &.smush-extension-jpeg,
54 &.smush-extension-jpg { background-color: #F7E100; }
55 &.smush-extension-png { background-color: #FFB694; }
56 &.smush-extension-gif { background-color: #72D5D4; }
57 &.smush-extension-webp { background-color: #72ADD5; }
58 &.smush-extension-svg { background-color: #88D572; }
59 &.smush-extension-iframe {
60 background-color: #8772D5;
61 font-size: 7px;
62 }
63 }
64 }
65
66 }
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
44 // Stats table.
45 .sui-smush-media {
46 table.wp-smush-stats-holder {
47 width: 100%;
48 border: 1px solid #E6E6E6;
49 border-radius: 4px;
50 margin-top: 6px;
51 border-collapse: collapse;
52 border-spacing: 0;
53 thead {
54 th.smush-stats-header {
55 padding: 8px 10px;
56 border-bottom: 1px solid #E6E6E6 !important;
57 color: #32373D;
58 font-size: 12px;
59 font-weight: bold;
60 letter-spacing: -0.23px;
61 line-height: 16px;
62 text-align: left;
63 }
64 }
65 tr {
66 border: 1px solid #E6E6E6;
67 }
68 td {
69 overflow-wrap: break-word;
70 vertical-align: middle;
71 padding: 8px 10px;
72 color: #555555;
73 font-size: 11px;
74 letter-spacing: -0.21px;
75 line-height: 16px;
76 border-bottom: 1px solid #E6E6E6;
77 &:first-of-type {
78 max-width: 110px;
79 font-weight: 500;
80 }
81 }
82 }
83 }
84
85 // Override !important set from WordPress.
86 #the-list {
87 .sui-smush-media {
88 thead {
89 th.smush-stats-header {
90 border-bottom: 1px solid #E6E6E6 !important;
91 }
92 }
93 }
94 }
95
96 // Responsive table for list mode.
97 @media screen and (max-width: 1024px) {
98 .wp-list-table .smushit {
99 table.wp-smush-stats-holder {
100 th {
101 display: table-cell;
102 box-sizing: border-box;
103 }
104 tr td {
105 word-wrap: break-word;
106 display: table-cell !important;
107 &:first-child {
108 border-right: none;
109 box-sizing: border-box;
110 }
111 &:last-child {
112 box-sizing: border-box;
113 float: none;
114 overflow: visible;
115 }
116 }
117 }
118 }
119 }
120
121 // NextGen Integration.
122 .iedit .wp-smush-action,
123 .iedit .smush-stats-details {
124 font-size: 11px;
125 }
126
127 /*NextGen Gallery stats*/
128 #ngg-listimages {
129 table.wp-smush-stats-holder {
130 table-layout: fixed;
131 border: 1px solid lightgray;
132 border-collapse: collapse;
133 width: 100%;
134 td,
135 th {
136 border: 1px solid #CECECE;
137 }
138 }
139 .column-7 {
140 width: 300px;
141 }
142 .spinner {
143 width: auto;
144 padding-left: 30px;
145 }
146 }
147
148 /** NextGen Gallery tr height, to show the progress bar properly for alternate rows **/
149 .alternate.iedit {
150 height: 120px;
151 }
152
153 /** Allows to click on button, otherwise row-actions from NextGen interferes **/
154 .wp-smush-nextgen-send {
155 position: relative;
156 z-index: 2;
157 }
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 }
1 @import url('https://fonts.googleapis.com/css?family=Roboto:400,500,700');
2 @import "modules/variables";
3 $font--path: '~@wpmudev/shared-ui/dist/fonts/';
4
5 @font-face {
6 font-family: "wpmudev-plugin-icons";
7 src: url('#{$font--path}/wpmudev-plugin-icons.eot?');
8 src: url('#{$font--path}/wpmudev-plugin-icons.eot?') format('embedded-opentype'),
9 url('#{$font--path}/wpmudev-plugin-icons.ttf') format('truetype'),
10 url('#{$font--path}/wpmudev-plugin-icons.woff') format('woff'),
11 url('#{$font--path}/wpmudev-plugin-icons.woff2') format('woff2'),
12 url('#{$font--path}/wpmudev-plugin-icons.svg') format('svg');
13 font-weight: 400;
14 font-style: normal
15 }
16
17 @media screen and ( max-width: 800px ) {
18 #smush-image-bar-toggle,
19 #smush-image-bar {
20 display: none;
21 }
22 }
23
24 @media screen and ( min-width: 800px ) {
25 .smush-detected-img {
26 border-radius: 5px;
27 transition: all 0.5s ease;
28 box-shadow: 0 0 0 5px #FECF2F;
29 }
30
31 #smush-image-bar-toggle {
32 position: fixed;
33 top: 60px;
34 right: 330px;
35 height: 50px;
36 width: 60px;
37 z-index: 9999999;
38 border-radius: 4px 0 0 4px;
39 background-color: #FFF;
40 box-shadow: inset 2px 0 0 0 #FECF2F, -13px 5px 20px 0 rgba(0, 0, 0, 0.1);
41 text-align: center;
42 cursor: pointer;
43 transition-property: all;
44 transition-duration: .5s;
45 transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
46
47 &.closed {
48 right: 0;
49 }
50
51 &.smush-toggle-success {
52 box-shadow: inset 2px 0 0 0 #1abc9c, -13px 5px 20px 0 rgba(0, 0, 0, 0.1);
53 }
54
55 i.sui-icon-info,
56 i.sui-icon-loader {
57 font-family: "wpmudev-plugin-icons" !important;
58 font-style: normal;
59 font-size: 16px;
60 line-height: 50px;
61 color: #FECF2F;
62 }
63
64 i.sui-icon-info:before {
65 content: "I";
66 }
67
68 &.smush-toggle-success i.sui-icon-info {
69 color: #1abc9c;
70 &:before {
71 content: "_";
72 }
73 }
74
75 i.sui-icon-loader {
76 &:before {
77 display: block;
78 content: "N";
79 -webkit-animation: spin 1.3s linear infinite;
80 animation: spin 1.3s linear infinite;
81 }
82 }
83
84 @-webkit-keyframes spin {
85 0% {
86 -webkit-transform: rotate(0deg);
87 transform: rotate(0deg);
88 }
89 100% {
90 -webkit-transform: rotate(360deg);
91 transform: rotate(360deg);
92 }
93 }
94
95 @keyframes spin {
96 0% {
97 -webkit-transform: rotate(0deg);
98 transform: rotate(0deg);
99 }
100 100% {
101 -webkit-transform: rotate(360deg);
102 transform: rotate(360deg);
103 }
104 }
105 }
106
107 #smush-image-bar {
108 position: fixed;
109 top: 0;
110 right: 0;
111 width: 330px;
112 height: 100%;
113 background-color: #FFF;
114 box-shadow: 0 0 40px 0 rgba(0,0,0,0.1);
115 z-index: 999999;
116 padding: 0 0 20px;
117 overflow-y: auto;
118 overflow-x: hidden;
119 max-width: 330px;
120 transition-property: all;
121 transition-duration: .5s;
122 transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
123
124 &.closed {
125 max-width: 0;
126 overflow-y: hidden;
127 }
128
129 h3, p, strong, span {
130 font-family: 'Roboto', sans-serif;
131 letter-spacing: -0.25px;
132 }
133
134 h3 {
135 color: #333333;
136 font-size: 15px;
137 font-weight: bold;
138 line-height: 30px;
139 background-color: #FAFAFA;
140 padding: 15px 20px;
141 margin: 0;
142 }
143
144 p {
145 color: #888888;
146 font-size: 13px;
147 line-height: 22px;
148 padding: 0 20px;
149 }
150
151 strong {
152 color: #AAAAAA;
153 font-size: 12px;
154 font-weight: bold;
155 line-height: 22px;
156 padding: 0 20px;
157 }
158
159 .smush-resize-box {
160 background-color: #F8F8F8;
161
162 &:first-of-type {
163 border-top: 1px solid #E6E6E6;
164 margin-top: 5px;
165 }
166
167 &:last-of-type {
168 margin-bottom: 20px;
169 }
170
171 span:first-of-type {
172 color: #888;
173 height: 34px;
174 width: 40px;
175 font-size: 13px;
176 font-weight: bold;
177 line-height: 32px;
178 text-align: center;
179 border: 1px solid #DDDDDD;
180 border-radius: 50%;
181 margin-right: 10px;
182 }
183
184 .smush-image-info {
185 background-color: #FFF;
186 display: flex;
187 align-items: center;
188 align-content: center;
189 justify-content: space-between;
190 padding: 17px 20px;
191 border-bottom: 1px solid #E6E6E6;
192 cursor: pointer;
193 }
194
195 .smush-front-icons {
196 margin: 0 10px;
197 line-height: 5px;
198
199 &:before {
200 font-family: "wpmudev-plugin-icons" !important;
201 speak: none;
202 font-size: 12px;
203 font-style: normal;
204 font-weight: 400;
205 font-variant: normal;
206 text-transform: none;
207 text-rendering: auto;
208 color: #AAA;
209 -webkit-font-smoothing: antialiased;
210 -moz-osx-font-smoothing: grayscale;
211 }
212
213 &.smush-front-icon-arrows-in {
214 &:before {
215 content: '\2264';
216 }
217 }
218 }
219
220 .smush-tag {
221 background-color: #fecf2f;
222 color: #333;
223 border-radius: 13px;
224 height: 26px;
225 width: 116px;
226 font-size: 12px;
227 letter-spacing: -0.25px;
228 line-height: 16px;
229 font-weight: 500;
230 display: flex;
231 align-items: center;
232 justify-content: center;
233
234 &.smush-tag-success {
235 background-color: #1abc9c;
236 color: #fff;
237 }
238 }
239
240 &.smush-tooltip {
241 position: relative;
242
243 &:before,
244 &:after {
245 content: "";
246 opacity: 0;
247 backface-visibility: hidden;
248 pointer-events: none;
249 position: absolute;
250 z-index: 1;
251 transition: margin .2s, opacity .2s;
252 }
253
254 &:before {
255 border: 5px solid transparent;
256 bottom: 100%;
257 left: 50%;
258 border-top-color: #000000;
259 transform: translateX(-50%);
260 }
261
262 &:after {
263 content: attr(data-tooltip);
264 min-width: 40px;
265 padding: 8px 12px;
266 border-radius: 4px;
267 background: #000000;
268 box-sizing: border-box;
269 color: #FFFFFF;
270 font: 400 12px/18px "Roboto", Arial, sans-serif;
271 text-transform: none;
272 text-align: center;
273 white-space: nowrap;
274 bottom: 100%;
275 left: 50%;
276 margin: 0 0 10px;
277 transform: translateX(-50%);
278 }
279
280 &.smush-tooltip-constrained {
281 &:after {
282 min-width: 240px;
283 white-space: normal;
284 }
285 }
286
287 &:not(.show-description):hover {
288 &:before,
289 &:after {
290 opacity: 1;
291 }
292 }
293 }
294
295 &:not(.show-description):hover,
296 &.show-description {
297 .smush-image-info { background-color: #F8F8F8; }
298
299 span:first-of-type {
300 background-color: #E6E6E6;
301 color: transparent;
302 &:before {
303 font-family: "wpmudev-plugin-icons" !important;
304 font-weight: 400;
305 content: "";
306 color: #666;
307 margin-right: -7px;
308 }
309 }
310 }
311
312 .smush-image-description {
313 display: none;
314 border-radius: 4px;
315 background-color: #FFFFFF;
316 box-shadow: 0 2px 0 0 #DDDDDD;
317 margin: 0 20px 20px;
318 padding: 20px;
319 color: #888888;
320 font-family: 'Roboto', sans-serif;
321 font-size: 13px;
322 letter-spacing: -0.25px;
323 line-height: 22px;
324 }
325
326 &.show-description {
327 padding-bottom: 1px;
328 border-bottom: 1px solid #E6E6E6;
329
330 .smush-image-info { border-bottom: 0; }
331 .smush-image-description { display: block; }
332
333 span:first-of-type {
334 background-color: #FECF2F;
335 border-color: #FECF2F;
336 &:before { color: #333; }
337 }
338 }
339 }
340
341 #smush-image-bar-notice {
342 display: none;
343 margin: 20px;
344 border: 1px solid #E6E6E6;
345 border-left: 2px solid #1abc9c;
346 border-radius: 4px;
347 padding: 15px 20px 15px 25px;
348
349 p:before {
350 position: absolute;
351 left: 42px;
352 font-family: "wpmudev-plugin-icons" !important;
353 font-weight: 400;
354 content: "_";
355 color: #1abc9c;
356 margin-right: -7px;
357 }
358 }
359 }
360 }
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
1 @import url(https://fonts.googleapis.com/css?family=Roboto:400,500,700);
2 @font-face{font-family:"wpmudev-plugin-icons";src:url(../fonts/wpmudev-plugin-icons.eot);src:url(../fonts/wpmudev-plugin-icons.eot) format("embedded-opentype"),url(../fonts/wpmudev-plugin-icons.ttf) format("truetype"),url(../fonts/wpmudev-plugin-icons.woff) format("woff"),url(../fonts/wpmudev-plugin-icons.woff2) format("woff2"),url(../fonts/wpmudev-plugin-icons.svg) format("svg");font-weight:400;font-style:normal}@media screen and (max-width: 800px){#smush-image-bar-toggle,#smush-image-bar{display:none}}@media screen and (min-width: 800px){.smush-detected-img{border-radius:5px;transition:all 0.5s ease;box-shadow:0 0 0 5px #FECF2F}#smush-image-bar-toggle{position:fixed;top:60px;right:330px;height:50px;width:60px;z-index:9999999;border-radius:4px 0 0 4px;background-color:#FFF;box-shadow:inset 2px 0 0 0 #FECF2F,-13px 5px 20px 0 rgba(0,0,0,0.1);text-align:center;cursor:pointer;transition-property:all;transition-duration:.5s;transition-timing-function:cubic-bezier(0, 1, 0.5, 1)}#smush-image-bar-toggle.closed{right:0}#smush-image-bar-toggle.smush-toggle-success{box-shadow:inset 2px 0 0 0 #1abc9c,-13px 5px 20px 0 rgba(0,0,0,0.1)}#smush-image-bar-toggle i.sui-icon-info,#smush-image-bar-toggle i.sui-icon-loader{font-family:"wpmudev-plugin-icons" !important;font-style:normal;font-size:16px;line-height:50px;color:#FECF2F}#smush-image-bar-toggle i.sui-icon-info:before{content:"I"}#smush-image-bar-toggle.smush-toggle-success i.sui-icon-info{color:#1abc9c}#smush-image-bar-toggle.smush-toggle-success i.sui-icon-info:before{content:"_"}#smush-image-bar-toggle i.sui-icon-loader:before{display:block;content:"N";animation:spin 1.3s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}#smush-image-bar{position:fixed;top:0;right:0;width:330px;height:100%;background-color:#FFF;box-shadow:0 0 40px 0 rgba(0,0,0,0.1);z-index:999999;padding:0 0 20px;overflow-y:auto;overflow-x:hidden;max-width:330px;transition-property:all;transition-duration:.5s;transition-timing-function:cubic-bezier(0, 1, 0.5, 1)}#smush-image-bar.closed{max-width:0;overflow-y:hidden}#smush-image-bar h3,#smush-image-bar p,#smush-image-bar strong,#smush-image-bar span{font-family:'Roboto', sans-serif;letter-spacing:-0.25px}#smush-image-bar h3{color:#333333;font-size:15px;font-weight:bold;line-height:30px;background-color:#FAFAFA;padding:15px 20px;margin:0}#smush-image-bar p{color:#888888;font-size:13px;line-height:22px;padding:0 20px}#smush-image-bar strong{color:#AAAAAA;font-size:12px;font-weight:bold;line-height:22px;padding:0 20px}#smush-image-bar .smush-resize-box{background-color:#F8F8F8}#smush-image-bar .smush-resize-box:first-of-type{border-top:1px solid #E6E6E6;margin-top:5px}#smush-image-bar .smush-resize-box:last-of-type{margin-bottom:20px}#smush-image-bar .smush-resize-box span:first-of-type{color:#888;height:34px;width:40px;font-size:13px;font-weight:bold;line-height:32px;text-align:center;border:1px solid #DDDDDD;border-radius:50%;margin-right:10px}#smush-image-bar .smush-resize-box .smush-image-info{background-color:#FFF;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;-ms-flex-pack:justify;justify-content:space-between;padding:17px 20px;border-bottom:1px solid #E6E6E6;cursor:pointer}#smush-image-bar .smush-resize-box .smush-front-icons{margin:0 10px;line-height:5px}#smush-image-bar .smush-resize-box .smush-front-icons:before{font-family:"wpmudev-plugin-icons" !important;speak:none;font-size:12px;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;text-rendering:auto;color:#AAA;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#smush-image-bar .smush-resize-box .smush-front-icons.smush-front-icon-arrows-in:before{content:'\2264'}#smush-image-bar .smush-resize-box .smush-tag{background-color:#fecf2f;color:#333;border-radius:13px;height:26px;width:116px;font-size:12px;letter-spacing:-0.25px;line-height:16px;font-weight:500;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}#smush-image-bar .smush-resize-box .smush-tag.smush-tag-success{background-color:#1abc9c;color:#fff}#smush-image-bar .smush-resize-box.smush-tooltip{position:relative}#smush-image-bar .smush-resize-box.smush-tooltip:before,#smush-image-bar .smush-resize-box.smush-tooltip:after{content:"";opacity:0;-webkit-backface-visibility:hidden;backface-visibility:hidden;pointer-events:none;position:absolute;z-index:1;transition:margin .2s, opacity .2s}#smush-image-bar .smush-resize-box.smush-tooltip:before{border:5px solid transparent;bottom:100%;left:50%;border-top-color:#000000;transform:translateX(-50%)}#smush-image-bar .smush-resize-box.smush-tooltip:after{content:attr(data-tooltip);min-width:40px;padding:8px 12px;border-radius:4px;background:#000000;box-sizing:border-box;color:#FFFFFF;font:400 12px/18px "Roboto", Arial, sans-serif;text-transform:none;text-align:center;white-space:nowrap;bottom:100%;left:50%;margin:0 0 10px;transform:translateX(-50%)}#smush-image-bar .smush-resize-box.smush-tooltip.smush-tooltip-constrained:after{min-width:240px;white-space:normal}#smush-image-bar .smush-resize-box.smush-tooltip:not(.show-description):hover:before,#smush-image-bar .smush-resize-box.smush-tooltip:not(.show-description):hover:after{opacity:1}#smush-image-bar .smush-resize-box:not(.show-description):hover .smush-image-info,#smush-image-bar .smush-resize-box.show-description .smush-image-info{background-color:#F8F8F8}#smush-image-bar .smush-resize-box:not(.show-description):hover span:first-of-type,#smush-image-bar .smush-resize-box.show-description span:first-of-type{background-color:#E6E6E6;color:transparent}#smush-image-bar .smush-resize-box:not(.show-description):hover span:first-of-type:before,#smush-image-bar .smush-resize-box.show-description span:first-of-type:before{font-family:"wpmudev-plugin-icons" !important;font-weight:400;content:"";color:#666;margin-right:-7px}#smush-image-bar .smush-resize-box .smush-image-description{display:none;border-radius:4px;background-color:#FFFFFF;box-shadow:0 2px 0 0 #DDDDDD;margin:0 20px 20px;padding:20px;color:#888888;font-family:'Roboto', sans-serif;font-size:13px;letter-spacing:-0.25px;line-height:22px}#smush-image-bar .smush-resize-box.show-description{padding-bottom:1px;border-bottom:1px solid #E6E6E6}#smush-image-bar .smush-resize-box.show-description .smush-image-info{border-bottom:0}#smush-image-bar .smush-resize-box.show-description .smush-image-description{display:block}#smush-image-bar .smush-resize-box.show-description span:first-of-type{background-color:#FECF2F;border-color:#FECF2F}#smush-image-bar .smush-resize-box.show-description span:first-of-type:before{color:#333}#smush-image-bar #smush-image-bar-notice{display:none;margin:20px;border:1px solid #E6E6E6;border-left:2px solid #1abc9c;border-radius:4px;padding:15px 20px 15px 25px}#smush-image-bar #smush-image-bar-notice p:before{position:absolute;left:42px;font-family:"wpmudev-plugin-icons" !important;font-weight:400;content:"_";color:#1abc9c;margin-right:-7px}}
3
4
5 /*# sourceMappingURL=smush-rd.min.css.map*/
...\ No newline at end of file ...\ No newline at end of file
1 <?xml version="1.0" encoding="UTF-8"?>
2 <svg width="1160px" height="282px" viewBox="0 0 1160 282" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3 <!-- Generator: sketchtool 63.1 (101010) - https://sketch.com -->
4 <title>746CD678-3498-40DD-881C-3BDFFD316D52@3x</title>
5 <desc>Created with sketchtool.</desc>
6 <defs>
7 <path d="M45.2434076,1.09077368 L209.345,278.415757 L15.8088163,278.420668 C5.52850892,245.342422 0,210.243077 0,173.88174 C0,111.246765 16.4044688,52.3564314 45.2434076,1.09077368 Z M735,173.88174 C735,210.242944 729.471531,245.342166 719.191296,278.420305 L521.611,278.415757 L686.362,0.000757320254 L689.140534,0.000128493021 C718.361795,51.5204993 735,110.802121 735,173.88174 Z" id="path-1"></path>
8 </defs>
9 <g id="Bulk-Smush" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
10 <g id="Free-/-Bulk-Smush---Large-screen" transform="translate(-410.000000, -242.000000)">
11 <g id="Body" transform="translate(410.000000, 0.000000)">
12 <g id="Bulk-Smush-/-Advanced-Settings---Free-Upsell-widget-large-screen" transform="translate(0.000000, 183.000000)">
13 <g id="Graphic/widget-background-smush-icon" transform="translate(0.000000, 59.225748)">
14 <rect id="Rectangle" fill="#F6F6F6" x="0" y="1" width="1160" height="280.395286"></rect>
15 <rect id="Rectangle" fill="#FAFAFA" x="259" y="1" width="642" height="280.395286"></rect>
16 <rect id="Rectangle" fill="#FFFFFF" transform="translate(580.000000, 0.506346) scale(1, -1) translate(-580.000000, -0.506346) " x="0" y="0.0126926564" width="1160" height="1"></rect>
17 <g id="smush-icon-background" transform="translate(216.000000, 1.987307)">
18 <g id="Combined-Shape">
19 <mask id="mask-2" fill="white">
20 <use xlink:href="#path-1"></use>
21 </mask>
22 <use fill="#F6F6F6" xlink:href="#path-1"></use>
23 </g>
24 <polygon id="Rectangle" fill="#EAEAEA" opacity="0.364653088" points="244 80.3950266 491 80.3950266 367.5 279.407978"></polygon>
25 </g>
26 </g>
27 </g>
28 </g>
29 </g>
30 </g>
31 </svg>
...\ No newline at end of file ...\ No newline at end of file
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.