This commit is contained in:
2024-05-20 15:37:46 +03:00
commit 00b7dbd0b7
10404 changed files with 3285853 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 B

View File

@ -0,0 +1,62 @@
import '../scss/app.scss';
/**
* Admin modules
*/
const WP_Smush = WP_Smush || {};
window.WP_Smush = WP_Smush;
/**
* IE polyfill for includes.
*
* @since 3.1.0
* @param {string} search
* @param {number} start
* @return {boolean} Returns true if searchString appears as a substring of the result of converting this
* object to a String, at one or more positions that are
* greater than or equal to position; otherwise, returns false.
*/
if ( ! String.prototype.includes ) {
String.prototype.includes = function( search, start ) {
if ( typeof start !== 'number' ) {
start = 0;
}
if ( start + search.length > this.length ) {
return false;
}
return this.indexOf( search, start ) !== -1;
};
}
require( './modules/helpers' );
require( './modules/admin' );
require( './modules/admin-common' );
require( './modules/bulk-smush' );
require( './modules/nextgen-bulk' );
require( './modules/background-process' );
require( './common/media-library-scanner' );
require( './modules/media-library-scanner-on-bulk-smush' );
require( './modules/media-library-scanner-on-dashboard' );
require( './modules/onboarding' );
require( './modules/directory-smush' );
require( './smush/cdn' );
require( './smush/webp' );
require( './smush/lazy-load' );
require( './modules/bulk-restore' );
require( './smush/settings' );
require( './smush/product-analytics' );
/**
* Notice scripts.
*
* Notices are used in the following functions:
*
* @used-by \Smush\Core\Modules\Smush::smush_updated()
* @used-by \Smush\Core\Integrations\S3::3_support_required_notice()
* @used-by \Smush\App\Abstract_Page::installation_notice()
*
* TODO: should this be moved out in a separate file like common.scss?
*/
require( './modules/notice' );

View File

@ -0,0 +1,222 @@
/* global WP_Smush */
export const UpsellManger = ( () => {
return {
maybeShowCDNActivationNotice() {
if ( ! wp_smush_msgs.smush_cdn_activation_notice ) {
return;
}
WP_Smush.helpers.renderActivationCDNNotice( wp_smush_msgs.smush_cdn_activation_notice );
},
maybeShowCDNUpsellForPreSiteOnStart() {
const upsellCdn = document.querySelector( '.wp-smush-upsell-cdn' );
if ( upsellCdn ) {
upsellCdn.querySelector( 'p' ).innerHTML = wp_smush_msgs.processing_cdn_for_free;
upsellCdn.classList.remove( 'sui-hidden' );
}
},
maybeShowCDNUpsellForPreSiteOnCompleted() {
const upsellCdn = document.querySelector( '.wp-smush-upsell-cdn' );
if ( upsellCdn ) {
upsellCdn.querySelector( 'p' ).innerHTML = wp_smush_msgs.processed_cdn_for_free;
upsellCdn.classList.remove( 'sui-hidden' );
}
}
};
} )();
export const GlobalStats = ( () => {
const $ = document.querySelector.bind( document );
const summarySmush = $( '.sui-summary-smush-metabox' );
if ( ! summarySmush ) {
return {};
}
// Cache initial stats.
let boStats = window.wp_smushit_data.bo_stats;
let globalStats = {
count_images: 0,
count_total: 0,
count_resize: 0,
count_skipped: 0,
count_smushed: 0,
savings_bytes: 0,
savings_resize: 0,
size_after: 0,
size_before: 0,
savings_percent: 0,
percent_grade: 'sui-grade-dismissed',
percent_metric: 0,
percent_optimized: 0,
remaining_count: 0,
human_bytes: '',
savings_conversion_human: '',
savings_conversion: 0,
};
const imageScore = $( '#smush-image-score' );
const logBulk = $( '.smush-final-log .smush-bulk-errors' );
const bulkSmushCountContent = $( '#wp-smush-bulk-content' );
let allErrors = {};
const generateGlobalStatsFromSmushData = ( smushScriptData ) => {
window.wp_smushit_data = Object.assign( window.wp_smushit_data, smushScriptData || {} );
globalStats = Object.keys( globalStats ).reduce( function( newStats, key ) {
if ( key in window.wp_smushit_data ) {
newStats[ key ] = window.wp_smushit_data[ key ];
}
return newStats;
}, {} );
}
generateGlobalStatsFromSmushData( window.wp_smushit_data );
return {
isChangedStats( newBoStats ) {
const primaryKeys = [ 'total_items', 'processed_items', 'failed_items', 'is_cancelled', 'is_completed' ];
return primaryKeys.some( ( key ) => {
return newBoStats[ key ] !== boStats[ key ];
} );
},
setBoStats( newBoStats ) {
boStats = Object.assign( boStats, newBoStats || {} );
return this;
},
getBoStats() {
return boStats;
},
setGlobalStats( newGlobalStats ) {
globalStats = Object.assign( globalStats, newGlobalStats || {} );
return this;
},
getGlobalStats() {
return globalStats;
},
/**
* Circle progress bar.
*/
renderScoreProgress() {
imageScore.className = imageScore.className.replace( /(^|\s)sui-grade-\S+/g, '' );
imageScore.classList.add( globalStats.percent_grade );
imageScore.dataset.score = globalStats.percent_optimized;
imageScore.querySelector( '.sui-circle-score-label' ).innerHTML = globalStats.percent_optimized;
imageScore
.querySelector( 'circle:last-child' )
.setAttribute( 'style', '--metric-array:' + ( 2.63893782902 * globalStats.percent_metric ) + ' ' + ( 263.893782902 - globalStats.percent_metric ) );
},
/**
* Summary detail - center meta box.
*/
renderSummaryDetail() {
this.renderTotalStats();
this.renderResizedStats();
this.renderConversionSavings();
},
renderTotalStats() {
// Total savings.
summarySmush.querySelector( '.sui-summary-large.wp-smush-stats-human' ).innerHTML = globalStats.human_bytes;
// Update the savings percent.
summarySmush.querySelector( '.wp-smush-savings .wp-smush-stats-percent' ).innerHTML = globalStats.savings_percent;
// To total smushed images files.
summarySmush.querySelector( '.wp-smush-count-total .wp-smush-total-optimised' ).innerHTML = globalStats.count_images;
},
renderResizedStats() {
const resizeCountElement = summarySmush.querySelector( '.wp-smush-count-resize-total' );
if ( ! resizeCountElement ) {
return;
}
if ( globalStats.count_resize > 0 ) {
resizeCountElement.classList.remove( 'sui-hidden' );
} else {
resizeCountElement.classList.add( 'sui-hidden' );
}
resizeCountElement.querySelector( '.wp-smush-total-optimised' ).innerHTML = globalStats.count_resize;
},
renderConversionSavings() {
// PNG2JPG Savings.
const conversionSavingsElement = summarySmush.querySelector( '.smush-conversion-savings .wp-smush-stats' );
if ( ! conversionSavingsElement ) {
return;
}
conversionSavingsElement.innerHTML = globalStats.savings_conversion_human;
if ( globalStats.savings_conversion > 0 ) {
conversionSavingsElement.parentElement.classList.remove( 'sui-hidden' );
} else {
conversionSavingsElement.parentElement.classList.add( 'sui-hidden' );
}
},
renderBoxSummary() {
// Circle core progress.
this.renderScoreProgress();
// Summary detail.
this.renderSummaryDetail();
},
setErrors( newErrors ) {
allErrors = newErrors || {};
},
getErrors() {
return allErrors;
},
renderErrors() {
if ( ! Object.keys( allErrors ).length || ! boStats.is_completed ) {
return;
}
const errors = [];
const errorKeys = Object.keys( allErrors );
// Cache error code to avoid double upsell notice.
let showAnimatedUpsell = false;
errorKeys.map( ( image_id, index ) => {
const upsellErrorCode = allErrors[ image_id ].error_code;
if ( index < 5 && 'animated' === upsellErrorCode ) {
showAnimatedUpsell = true;
}
errors.push( WP_Smush.helpers.prepareBulkSmushErrorRow(
allErrors[ image_id ].error_message,
allErrors[ image_id ].file_name,
allErrors[ image_id ].thumbnail,
image_id,
'media',
allErrors[ image_id ].error_code,
) );
}
);
logBulk.innerHTML = errors.join( '' );
logBulk.parentElement.classList.remove( 'sui-hidden' );
logBulk.parentElement.style.display = null;
// Show view all.
if ( errorKeys.length > 1 ) {
$( '.smush-bulk-errors-actions' ).classList.remove( 'sui-hidden' );
}
// Show animated upsell CDN if user disabled CDN and found an animated error in first 5 errors.
if ( showAnimatedUpsell ) {
UpsellManger.maybeShowCDNActivationNotice();
}
},
resetAndHideBulkErrors() {
if ( ! logBulk ) {
return;
}
this.resetErrors();
logBulk.parentElement.classList.add( 'sui-hidden' );
logBulk.innerHTML = '';
},
resetErrors() {
allErrors = {};
},
renderStats() {
// Render Smush box summary.
this.renderBoxSummary();
// Render Errors.
this.renderErrors();
},
maybeUpdateBulkSmushCountContent( newContent ) {
if ( newContent && bulkSmushCountContent ) {
bulkSmushCountContent.innerHTML = newContent;
}
},
updateGlobalStatsFromSmushScriptData( smushScriptData ) {
this.maybeUpdateBulkSmushCountContent( smushScriptData?.content );
generateGlobalStatsFromSmushData( smushScriptData );
return this;
},
};
} )();

View File

@ -0,0 +1,267 @@
/* global WP_Smush */
/**
* Abstract Media Library Scanner.
*
*/
import Fetcher from '../utils/fetcher';
import { scanProgressBar } from './progressbar';
import { GlobalStats } from './globalStats';
const { __ } = wp.i18n;
export default class MediaLibraryScanner {
constructor() {
this.autoSyncDuration = 1500;
this.progressTimeoutId = 0;
this.scanProgress = scanProgressBar( this.autoSyncDuration );
}
startScan( optimizeOnScanCompleted = false ) {
this.onStart();
return Fetcher.scanMediaLibrary.start( optimizeOnScanCompleted ).then( ( response ) => {
if ( ! response?.success ) {
this.onStartFailure( response );
return;
}
this.showProgressBar().autoSyncStatus();
} );
}
onStart() {
// Do nothing at the moment.
}
onStartFailure( response ) {
WP_Smush.helpers.showNotice( response, {
showdismiss: true,
autoclose: false,
} );
}
showProgressBar() {
this.onShowProgressBar();
this.scanProgress.reset().setOnCancelCallback( this.showStopScanningModal.bind( this ) ).open();
return this;
}
onShowProgressBar() {
// Do nothing at the moment.
}
showStopScanningModal() {
if ( ! window.SUI ) {
return;
}
this.onShowStopScanningModal();
window.SUI.openModal(
'smush-stop-scanning-dialog',
'wpbody-content',
undefined,
false
);
}
onShowStopScanningModal() {
this.registerCancelProcessEvent();
}
registerCancelProcessEvent() {
const stopScanButton = document.querySelector( '.smush-stop-scanning-dialog-button' );
if ( ! stopScanButton ) {
return;
}
stopScanButton.addEventListener( 'click', this.cancelProgress.bind( this ) );
}
closeStopScanningModal() {
if ( ! window.SUI ) {
return;
}
const stopScanningElement = document.querySelector( '#smush-stop-scanning-dialog' );
const isModalClosed = ( ! stopScanningElement ) || ! stopScanningElement.classList.contains( 'sui-content-fade-in' );
if ( isModalClosed ) {
return;
}
window.SUI.closeModal( 'smush-stop-scanning-dialog' );
}
closeProgressBar() {
this.onCloseProgressBar();
this.scanProgress.close();
}
onCloseProgressBar() {
// Do nothing at the moment.
}
updateProgress( stats ) {
const totalItems = this.getTotalItems( stats );
const processedItems = this.getProcessedItems( stats );
return this.scanProgress.update( processedItems, totalItems );
}
getProcessedItems( stats ) {
return stats?.processed_items || 0;
}
getTotalItems( stats ) {
return stats?.total_items || 0;
}
cancelProgress() {
this.scanProgress.setCancelButtonOnCancelling();
return Fetcher.scanMediaLibrary.cancel().then( ( response ) => {
if ( ! response?.success ) {
this.onCancelFailure( response );
return;
}
this.onCancelled( response.data );
} );
}
onCancelFailure( response ) {
WP_Smush.helpers.showNotice( response, {
showdismiss: true,
autoclose: false,
} );
this.scanProgress.resetCancelButtonOnFailure();
}
getErrorProgressMessage() {
return __( 'Unfortunately the scan hit an error due to limited resources on your site, we have adjusted the scan to use fewer resources the next time.', 'wp-smushit' );
}
onDead( stats ) {
this.clearProgressTimeout();
this.closeProgressBar();
this.closeStopScanningModal();
this.showRetryScanModal();
}
showRetryScanModal() {
const retryScanModalElement = document.getElementById( 'smush-retry-scan-notice' );
if ( ! window.SUI || ! retryScanModalElement ) {
return;
}
retryScanModalElement.querySelector('.smush-retry-scan-notice-button').onclick = (e) => {
window.SUI.closeModal( 'smush-retry-scan-notice' );
const recheckImagesBtn = document.querySelector( '.wp-smush-scan' );
if ( ! recheckImagesBtn ) {
return;
}
e.preventDefault();
recheckImagesBtn.click();
}
window.SUI.openModal(
'smush-retry-scan-notice',
'wpbody-content',
undefined,
false
);
}
onCompleted( stats ) {
this.onFinish( stats );
}
onCancelled( stats ) {
this.onFinish( stats );
}
onFinish( stats ) {
this.clearProgressTimeout();
const globalStats = stats?.global_stats;
this.updateGlobalStatsAndBulkContent( globalStats );
this.closeProgressBar();
this.closeStopScanningModal();
}
clearProgressTimeout() {
if ( this.progressTimeoutId ) {
clearTimeout( this.progressTimeoutId );
}
}
updateGlobalStatsAndBulkContent( globalStats ) {
if ( ! globalStats ) {
return;
}
GlobalStats.updateGlobalStatsFromSmushScriptData( globalStats );
GlobalStats.renderStats();
}
getStatus() {
return Fetcher.scanMediaLibrary.getScanStatus();
}
autoSyncStatus() {
const startTime = new Date().getTime();
this.getStatus().then( ( response ) => {
if ( ! response?.success ) {
return;
}
const stats = response.data;
if ( stats.is_dead ) {
this.onDead( response.data );
return;
}
this.beforeUpdateStatus( stats );
this.updateProgress( stats ).then( () => {
this.scanProgress.increaseDurationToHaveChangeOnProgress( new Date().getTime() - startTime );
const isCompleted = stats?.is_completed;
if ( isCompleted ) {
this.onCompleted( stats );
return;
}
const isCancelled = stats?.is_cancelled;
if ( isCancelled ) {
this.onCancelled( stats );
return;
}
this.progressTimeoutId = setTimeout( () => this.autoSyncStatus(), this.autoSyncDuration );
} );
} );
}
beforeUpdateStatus() {
// Do nothing at the moment.
}
setInnerText( element, newText ) {
if ( ! element ) {
return;
}
element.dataset.originalText = element.dataset.originalText || element.innerText.trim();
element.innerText = newText;
}
revertInnerText( element ) {
if ( ! element || ! element.dataset.originalText ) {
return;
}
element.innerText = element.dataset.originalText.trim();
}
hideAnElement( element ) {
if ( element ) {
element.classList.add( 'sui-hidden' );
}
}
showAnElement( element ) {
if ( element ) {
element.classList.remove( 'sui-hidden' );
}
}
}

View File

@ -0,0 +1,302 @@
/* global WP_Smush */
/**
* SmushProgressBar
* TODO: Update progressbar for free version.
*
* @param autoSyncDuration
*/
export const scanProgressBar = ( autoSyncDuration ) => {
const { __, _n } = wp.i18n;
const scanProgressBar = document.querySelector( '.wp-smush-scan-progress-bar-wrapper' );
const percentElement = scanProgressBar.querySelector( '.wp-smush-progress-percent' );
const progressElement = scanProgressBar.querySelector( '.wp-smush-progress-inner' );
const remainingTimeElement = scanProgressBar.querySelector( '.wp-smush-remaining-time' );
const cancelBtn = scanProgressBar.querySelector( '.wp-smush-cancel-scan-progress-btn' );
const holdOnNoticeElement = scanProgressBar.querySelector( '.wp-smush-scan-hold-on-notice' );
let onCancelCallback = () => {};
let intervalProgressAnimation = 0;
// It should be smaller than autoSyncDuration.
const progressTransitionDuration = autoSyncDuration - 300;//1200
scanProgressBar.style.setProperty( '--progress-transition-duration', progressTransitionDuration / 1000 + 's' );
let prevProcessedItems = window.wp_smushit_data?.media_library_scan?.processed_items || 0;
const cacheProcessTimePerItem = [];
let durationToHaveChangeOnProgress = autoSyncDuration;
let timeLimitToShowNotice = autoSyncDuration * 10;// 15s.
return {
update( processedItems, totalItems ) {
this.updateRemainingTime( processedItems, totalItems );
let width = ( totalItems && Math.floor( processedItems / totalItems * 100 ) ) || 0;
width = Math.min( width, 100 );
let currentWidth = progressElement.style.width;
currentWidth = ( currentWidth && currentWidth.replace( '%', '' ) ) || 0;
progressElement.style.width = width + '%';
return this.animateProgressBar( currentWidth, width );
},
animateProgressBar( currentWidth, width ) {
if ( intervalProgressAnimation ) {
clearInterval( intervalProgressAnimation );
}
return new Promise( ( resolve ) => {
const delayTime = progressTransitionDuration / ( width - currentWidth );
intervalProgressAnimation = setInterval( () => {
// Progress bar label.
percentElement.innerHTML = currentWidth + '%';
currentWidth++;
if ( currentWidth > width ) {
resolve();
clearInterval( intervalProgressAnimation );
}
}, delayTime );
} );
},
updateRemainingTime( processedItems, totalItems ) {
if ( ! remainingTimeElement ) {
return;
}
const processTimePerItem = this.calcProcessTimePerItem( processedItems ) || 500;
const remainingTime = processTimePerItem * ( totalItems - processedItems );
remainingTimeElement.innerText = this.formatTime( remainingTime );
},
calcProcessTimePerItem( processedItems ) {
if ( ! processedItems ) {
return;
}
prevProcessedItems = prevProcessedItems <= processedItems ? prevProcessedItems : 0;
if ( prevProcessedItems != processedItems ) {
const processTimePerItem = Math.floor( durationToHaveChangeOnProgress / ( processedItems - prevProcessedItems ) );
prevProcessedItems = processedItems;
cacheProcessTimePerItem.push( processTimePerItem );
this.resetDurationToHaveChangeOnProgress();
} else {
this.increaseDurationToHaveChangeOnProgress( autoSyncDuration );
}
if ( ! cacheProcessTimePerItem.length ) {
return;
}
return cacheProcessTimePerItem.reduce(
( accumulator, processTime ) => accumulator + processTime, 0
) / cacheProcessTimePerItem.length;
},
increaseDurationToHaveChangeOnProgress( increaseTime ) {
durationToHaveChangeOnProgress += increaseTime;
if ( durationToHaveChangeOnProgress > timeLimitToShowNotice ) {
this.showHoldOnNotice();
}
},
showHoldOnNotice() {
holdOnNoticeElement.classList.remove( 'sui-hidden' );
timeLimitToShowNotice = 100000000;
},
resetHoldOnNoticeVisibility() {
holdOnNoticeElement.classList.add( 'sui-hidden' );
},
resetDurationToHaveChangeOnProgress() {
durationToHaveChangeOnProgress = autoSyncDuration;
},
formatTime( totalMilliSeconds ) {
const totalSeconds = Math.floor( ( totalMilliSeconds + progressTransitionDuration ) / 1000 );
const seconds = totalSeconds % 60;
const minutes = Math.floor( totalSeconds / 60 );
let timeString = '';
if ( minutes ) {
timeString += minutes + ' ' + _n( 'minute', 'minutes', minutes, 'wp-smushit' );
}
timeString += ' ' + seconds + ' ' + _n( 'second', 'seconds', seconds, 'wp-smushit' );
return timeString.trim();
},
reset() {
progressElement.style.width = '0%';
percentElement.innerHTML = '0%';
this.resetCancelButton();
this.resetHoldOnNoticeVisibility();
return this;
},
open() {
cancelBtn.onclick = onCancelCallback;
scanProgressBar.classList.remove( 'sui-hidden' );
},
close() {
scanProgressBar.classList.add( 'sui-hidden' );
this.reset();
},
setOnCancelCallback( callBack ) {
if ( 'function' !== typeof callBack ) {
return;
}
onCancelCallback = callBack;
return this;
},
setCancelButtonLabel( textContent ) {
cancelBtn.textContent = textContent;
return this;
},
setCancelButtonOnCancelling() {
this.setCancelButtonLabel( wp_smush_msgs.cancelling );
this.setOnCancelCallback( () => false );
cancelBtn.setAttribute( 'disabled', true );
},
resetCancelButton() {
this.setOnCancelCallback( () => {} );
this.resetCancelButtonLabel();
cancelBtn.removeAttribute( 'disabled' );
},
resetCancelButtonLabel() {
this.setCancelButtonLabel( __( 'Cancel Scan', 'wp-smushit' ) );
},
resetCancelButtonOnFailure() {
this.resetCancelButtonLabel();
cancelBtn.removeAttribute( 'disabled' );
}
};
};
const SmushProgressBar = () => {
'use strict';
const progressBar = document.querySelector( '.wp-smush-bulk-progress-bar-wrapper' );
if ( ! progressBar ) {
return {
isEmptyObject: true,
};
}
const cancelBtn = progressBar.querySelector( '.wp-smush-cancel-btn' );
const bulkSmushDescription = document.querySelector( '.wp-smush-bulk-wrapper' );
const bulkRunningNotice = progressBar.querySelector( '#wp-smush-running-notice' );
const bulkSmushAllDone = document.querySelector( '.wp-smush-all-done' );
let isStateHidden = false;
let onCancelCallback = () => {};
return {
/**
* Update progress bar.
*
* @param {number} processedItems
* @param {number} totalItems
*/
update( processedItems, totalItems ) {
let width = totalItems && Math.floor( processedItems / totalItems * 100 ) || 0;
width = Math.min( width, 100 );
// Progress bar label.
progressBar.querySelector( '.wp-smush-images-percent' ).innerHTML = width + '%';
// Progress bar.
progressBar.querySelector( '.wp-smush-progress-inner' ).style.width = width + '%';
// Update processed/total.
const processStateStats = progressBar.querySelector( '.sui-progress-state-text' );
processStateStats.firstElementChild.innerHTML = processedItems;
processStateStats.lastElementChild.innerHTML = totalItems;
return this;
},
close() {
progressBar.classList.add( 'sui-hidden' );
this.setCancelButtonLabel( window.wp_smush_msgs.cancel )
.setOnCancelCallback( () => {} )
.update( 0, 0 );
this.resetOriginalNotice();
return this;
},
show() {
// Show progress bar.
cancelBtn.onclick = onCancelCallback;
progressBar.classList.remove( 'sui-hidden' );
this.hideBulkSmushDescription();
this.hideBulkSmushAllDone();
this.hideRecheckImagesNotice();
},
setCancelButtonLabel( textContent ) {
cancelBtn.textContent = textContent;
return this;
},
showBulkSmushDescription() {
bulkSmushDescription.classList.remove( 'sui-hidden' );
},
hideBulkSmushDescription() {
bulkSmushDescription.classList.add( 'sui-hidden' );
},
showBulkSmushAllDone() {
bulkSmushAllDone.classList.remove( 'sui-hidden' );
},
hideBulkSmushAllDone() {
bulkSmushAllDone.classList.add( 'sui-hidden' );
},
hideState() {
if ( isStateHidden ) {
return this;
}
isStateHidden = true;
progressBar.querySelector( '.sui-progress-state' ).classList.add( 'sui-hidden' );
return this;
},
showState() {
if ( ! isStateHidden ) {
return this;
}
isStateHidden = false;
progressBar.querySelector( '.sui-progress-state' ).classList.remove( 'sui-hidden' );
return this;
},
setNotice( inProcessNotice ) {
const progressMessage = bulkRunningNotice.querySelector( '.sui-notice-message p' );
this.cacheOriginalNotice( progressMessage );
progressMessage.innerHTML = inProcessNotice;
return this;
},
cacheOriginalNotice( progressMessage ) {
if ( bulkRunningNotice.dataset.progressMessage ) {
return;
}
bulkRunningNotice.dataset.progressMessage = progressMessage.innerHTML;
},
resetOriginalNotice() {
if ( ! bulkRunningNotice.dataset.progressMessage ) {
return;
}
const progressMessage = bulkRunningNotice.querySelector( '.sui-notice-message p' );
progressMessage.innerHTML = bulkRunningNotice.dataset.progressMessage;
},
hideBulkProcessingNotice() {
bulkRunningNotice.classList.add( 'sui-hidden' );
return this;
},
showBulkProcessingNotice() {
bulkRunningNotice.classList.remove( 'sui-hidden' );
return this;
},
setCountUnitText( unitText ) {
const progressUnit = progressBar.querySelector( '.sui-progress-state-unit' );
progressUnit.innerHTML = unitText;
},
setOnCancelCallback( callBack ) {
if ( 'function' !== typeof callBack ) {
return;
}
onCancelCallback = callBack;
return this;
},
disableExceedLimitMode() {
progressBar.classList.remove( 'wp-smush-exceed-limit' );
progressBar.querySelector( '#bulk-smush-resume-button' ).classList.add( 'sui-hidden' );
},
hideRecheckImagesNotice() {
const recheckImagesNoticeElement = document.querySelector( '.wp-smush-recheck-images-notice-box' );
if ( recheckImagesNoticeElement ) {
recheckImagesNoticeElement.classList.add( 'sui-hidden' );
}
}
};
};
export default new SmushProgressBar();

View File

@ -0,0 +1,11 @@
import lazySizes from 'lazysizes';
import 'lazysizes/plugins/native-loading/ls.native-loading';
lazySizes.cfg.nativeLoading = {
setLoadingAttribute: true,
disableListeners: {
scroll: true,
},
};
lazySizes.init();

View File

@ -0,0 +1,3 @@
import lazySizes from 'lazysizes';
lazySizes.init();

View File

@ -0,0 +1,362 @@
import '../../scss/resize-detection.scss';
/**
* Image resize detection (IRD).
*
* Show all wrongly scaled images with a highlighted border and resize box.
*
* Made in pure JS.
* DO NOT ADD JQUERY SUPPORT!!!
*
* @since 2.9
*/
( function() {
'use strict';
const SmushIRS = {
bar: document.getElementById( 'smush-image-bar' ),
toggle: document.getElementById( 'smush-image-bar-toggle' ),
images: {
bigger: [],
smaller: [],
},
strings: window.wp_smush_resize_vars,
/**
* Init scripts.
*/
init() {
/**
* Make sure these are set, before we proceed.
*/
if ( ! this.bar ) {
this.bar = document.getElementById( 'smush-image-bar' );
}
if ( ! this.toggle ) {
this.toggle = document.getElementById(
'smush-image-bar-toggle'
);
}
this.process();
// Register the event handler after everything is done.
this.toggle.addEventListener(
'click',
this.handleToggleClick.bind( this )
);
},
/**
* Do image processing.
*/
process() {
const icon = this.toggle.querySelector( 'i' );
icon.classList.add( 'sui-icon-loader' );
icon.classList.remove( 'sui-icon-info' );
this.detectImages();
if ( ! this.images.bigger.length && ! this.images.smaller.length ) {
this.toggle.classList.add( 'smush-toggle-success' );
document.getElementById(
'smush-image-bar-notice'
).style.display = 'block';
document.getElementById(
'smush-image-bar-notice-desc'
).style.display = 'none';
} else {
this.toggle.classList.remove( 'smush-toggle-success' );
document.getElementById(
'smush-image-bar-notice'
).style.display = 'none';
document.getElementById(
'smush-image-bar-notice-desc'
).style.display = 'block';
this.generateMarkup( 'bigger' );
this.generateMarkup( 'smaller' );
}
this.toggleDivs();
icon.classList.remove( 'sui-icon-loader' );
icon.classList.add( 'sui-icon-info' );
},
/**
* Various checks to see if the image should be processed.
*
* @param {Object} image
* @return {boolean} Should skip image or not.
*/
shouldSkipImage( image ) {
// Skip avatars.
if ( image.classList.contains( 'avatar' ) ) {
return true;
}
// Skip images from Smush CDN with auto-resize feature.
if (
'string' === typeof image.getAttribute( 'no-resize-detection' )
) {
return true;
}
// Skip 1x1px images.
if (
image.clientWidth === image.clientHeight &&
1 === image.clientWidth
) {
return true;
}
// Skip 1x1px placeholders.
if (
image.naturalWidth === image.naturalHeight &&
1 === image.naturalWidth
) {
return true;
}
// If width attribute is not set, do not continue.
return null === image.clientWidth || null === image.clientHeight;
},
/**
* Get tooltip text.
*
* @param {Object} props
* @return {string} Tooltip.
*/
getTooltipText( props ) {
let tooltipText = '';
if ( props.bigger_width || props.bigger_height ) {
/** @param {string} strings.large_image */
tooltipText = this.strings.large_image;
} else if ( props.smaller_width || props.smaller_height ) {
/** @param {string} strings.small_image */
tooltipText = this.strings.small_image;
}
return tooltipText
.replace( 'width', props.real_width )
.replace( 'height', props.real_height );
},
/**
* Generate markup.
*
* @param {string} type Accepts: 'bigger' or 'smaller'.
*/
generateMarkup( type ) {
this.images[ type ].forEach( ( image, index ) => {
const item = document.createElement( 'div' ),
tooltip = this.getTooltipText( image.props );
item.setAttribute(
'class',
'smush-resize-box smush-tooltip smush-tooltip-constrained'
);
item.setAttribute( 'data-tooltip', tooltip );
item.setAttribute( 'data-image', image.class );
item.addEventListener( 'click', ( e ) =>
this.highlightImage( e )
);
item.innerHTML = `
<div class="smush-image-info">
<span>${ index + 1 }</span>
<span class="smush-tag">${ image.props.computed_width } x ${ image.props.computed_height }px</span>
<i class="smush-front-icons smush-front-icon-arrows-in" aria-hidden="true">&nbsp;</i>
<span class="smush-tag smush-tag-success">${ image.props.real_width } × ${ image.props.real_height }px</span>
</div>
<div class="smush-image-description">${ tooltip }</div>
`;
document
.getElementById( 'smush-image-bar-items-' + type )
.appendChild( item );
} );
},
/**
* Show/hide sections based on images.
*/
toggleDivs() {
const types = [ 'bigger', 'smaller' ];
types.forEach( ( type ) => {
const div = document.getElementById(
'smush-image-bar-items-' + type
);
if ( 0 === this.images[ type ].length ) {
div.style.display = 'none';
} else {
div.style.display = 'block';
}
} );
},
/**
* Scroll the selected image into view and highlight it.
*
* @param {Object} e
*/
highlightImage( e ) {
this.removeSelection();
const el = document.getElementsByClassName(
e.currentTarget.dataset.image
);
if ( 'undefined' !== typeof el[ 0 ] ) {
// Display description box.
e.currentTarget.classList.toggle( 'show-description' );
// Scroll and flash image.
el[ 0 ].scrollIntoView( {
behavior: 'smooth',
block: 'center',
inline: 'nearest',
} );
el[ 0 ].style.opacity = '0.5';
setTimeout( () => {
el[ 0 ].style.opacity = '1';
}, 1000 );
}
},
/**
* Handle click on the toggle item.
*/
handleToggleClick() {
this.bar.classList.toggle( 'closed' );
this.toggle.classList.toggle( 'closed' );
this.removeSelection();
},
/**
* Remove selected items.
*/
removeSelection() {
const items = document.getElementsByClassName( 'show-description' );
if ( items.length > 0 ) {
Array.from( items ).forEach( ( div ) =>
div.classList.remove( 'show-description' )
);
}
},
/**
* Function to highlight all scaled images.
*
* Add yellow border and then show one small box to
* resize the images as per the required size, on fly.
*/
detectImages() {
const images = document.getElementsByTagName( 'img' );
for ( const image of images ) {
if ( this.shouldSkipImage( image ) ) {
continue;
}
// Get defined width and height.
const props = {
real_width: image.clientWidth,
real_height: image.clientHeight,
computed_width: image.naturalWidth,
computed_height: image.naturalHeight,
bigger_width: image.clientWidth * 1.5 < image.naturalWidth,
bigger_height:
image.clientHeight * 1.5 < image.naturalHeight,
smaller_width: image.clientWidth > image.naturalWidth,
smaller_height: image.clientHeight > image.naturalHeight,
};
// In case image is in correct size, do not continue.
if (
! props.bigger_width &&
! props.bigger_height &&
! props.smaller_width &&
! props.smaller_height
) {
continue;
}
const imgType =
props.bigger_width || props.bigger_height
? 'bigger'
: 'smaller',
imageClass =
'smush-image-' + ( this.images[ imgType ].length + 1 );
// Fill the images arrays.
this.images[ imgType ].push( {
src: image,
props,
class: imageClass,
} );
/**
* Add class to original image.
* Can't add two classes in single add(), because no support in IE11.
* image.classList.add('smush-detected-img', imageClass);
*/
image.classList.add( 'smush-detected-img' );
image.classList.add( imageClass );
}
}, // End detectImages()
/**
* Allows refreshing the list. A good way is to refresh on lazyload actions.
*
* @since 3.6.0
*/
refresh() {
// Clear out classes on DOM.
for ( let id in this.images.bigger ) {
if ( this.images.bigger.hasOwnProperty( id ) ) {
this.images.bigger[ id ].src.classList.remove(
'smush-detected-img'
);
this.images.bigger[ id ].src.classList.remove(
'smush-image-' + ++id
);
}
}
for ( let id in this.images.smaller ) {
if ( this.images.smaller.hasOwnProperty( id ) ) {
this.images.smaller[ id ].src.classList.remove(
'smush-detected-img'
);
this.images.smaller[ id ].src.classList.remove(
'smush-image-' + ++id
);
}
}
this.images = {
bigger: [],
smaller: [],
};
// This might be overkill - there will probably never be a situation when there are less images than on
// initial page load.
const elements = document.getElementsByClassName(
'smush-resize-box'
);
while ( elements.length > 0 ) {
elements[ 0 ].remove();
}
this.process();
},
}; // End WP_Smush_IRS
/**
* After page load, initialize toggle event.
*/
window.addEventListener( 'DOMContentLoaded', () => SmushIRS.init() );
window.addEventListener( 'lazyloaded', () => SmushIRS.refresh() );
}() );

View File

@ -0,0 +1,83 @@
import '../scss/common.scss';
/* global ajaxurl */
document.addEventListener('DOMContentLoaded', function () {
const dismissNoticeButton = document.querySelectorAll(
'.smush-dismissible-notice .smush-dismiss-notice-button'
);
dismissNoticeButton.forEach((button) => {
button.addEventListener('click', handleDismissNotice);
});
function handleDismissNotice(event) {
event.preventDefault();
const button = event.target;
const notice = button.closest('.smush-dismissible-notice');
const key = notice.getAttribute('data-key');
dismissNotice( key, notice );
}
function dismissNotice( key, notice ) {
const xhr = new XMLHttpRequest();
xhr.open(
'POST',
ajaxurl + '?action=smush_dismiss_notice&key=' + key + '&_ajax_nonce=' + smush_global.nonce,
true
);
xhr.onload = () => {
if (notice) {
notice.querySelector('button.notice-dismiss').dispatchEvent(new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true
}));
}
};
xhr.send();
}
// Show header notices.
const handleHeaderNotice = () => {
const headerNotice = document.querySelector('.wp-smush-dismissible-header-notice');
if ( ! headerNotice || ! headerNotice.id ) {
return;
}
const { dismissKey, message } = headerNotice.dataset;
if ( ! dismissKey || ! message ) {
return;
}
headerNotice.onclick = (e) => {
const classList = e.target.classList;
const isDismissButton = classList && ( classList.contains('sui-icon-check') || classList.contains('sui-button-icon') );
if ( ! isDismissButton ) {
return;
}
dismissNotice( dismissKey );
}
const noticeOptions = {
type: 'warning',
icon: 'info',
dismiss: {
show: true,
label: wp_smush_msgs.noticeDismiss,
tooltip: wp_smush_msgs.noticeDismissTooltip,
},
};
window.SUI.openNotice(
headerNotice.id,
message,
noticeOptions
);
}
handleHeaderNotice();
});

View File

@ -0,0 +1,75 @@
/* global wp_smush_mixpanel */
import mixpanel from "mixpanel-browser";
class MixPanel {
constructor() {
// Opt out event is not triggered via js so only initial mixpanel when it's enabled.
this.mixpanelInstance = this.allowToTrack() && mixpanel.init(wp_smush_mixpanel.token, {
opt_out_tracking_by_default: ! this.allowToTrack(),
loaded: (mixpanel) => {
mixpanel.identify(wp_smush_mixpanel.unique_id);
mixpanel.register(wp_smush_mixpanel.super_properties);
if (mixpanel.has_opted_in_tracking() !== this.allowToTrack()) {
// The status cached by MixPanel in the local storage is different from the settings. Clear the cache.
mixpanel.clear_opt_in_out_tracking();
}
}
}, 'smush');
}
allowToTrack() {
return !! wp_smush_mixpanel.opt_in;
}
track(event, properties = {}) {
if ( wp_smush_mixpanel.debug ) {
console.log( 'Event:', event, properties );
}
this.mixpanelInstance && this.mixpanelInstance.track(event, properties);
}
trackBulkSmushCompleted( globalStats ) {
const {
savings_bytes,
count_images,
percent_optimized,
savings_percent,
count_resize,
savings_resize
} = globalStats;
this.track('Bulk Smush Completed', {
'Total Savings': this.convertToMegabytes( savings_bytes ),
'Total Images': count_images,
'Media Optimization Percentage': parseFloat( percent_optimized ),
'Percentage of Savings': parseFloat( savings_percent ),
'Images Resized': count_resize,
'Resize Savings': this.convertToMegabytes( savings_resize )
});
}
trackBulkSmushCancel() {
this.track('Bulk Smush Cancelled');
}
convertToMegabytes( sizeInBytes ) {
const unitMB = Math.pow( 1024, 2 );
const sizeInMegabytes = sizeInBytes/unitMB;
return sizeInMegabytes && parseFloat(sizeInMegabytes.toFixed(2)) || 0;
}
}
const mixPanelInstance = () => {
let instance;
return {
getInstance: () => {
if ( ! instance ) {
instance = new MixPanel();
}
return instance;
}
}
}
export default mixPanelInstance();

View File

@ -0,0 +1,20 @@
jQuery(function ($) {
'use strict';
/**
* Handle the Smush Stats link click
*/
$('body').on('click', 'a.smush-stats-details', function (e) {
//If disabled
if ($(this).prop('disabled')) {
return false;
}
// prevent the default action
e.preventDefault();
//Replace the `+` with a `-`
const slide_symbol = $(this).find('.stats-toggle');
$(this).parents().eq(1).find('.smush-stats-wrapper').slideToggle();
slide_symbol.text(slide_symbol.text() == '+' ? '-' : '+');
});
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,395 @@
/* global WP_Smush */
/**
* Bulk Smush Background Optimization.
*
* @since 3.12.0
*/
import Fetcher from '../utils/fetcher';
import MixPanel from "../mixpanel";
import SmushProgress from '../common/progressbar';
import {GlobalStats, UpsellManger} from "../common/globalStats";
(function () {
'use strict';
if (!window.wp_smush_msgs) {
return;
}
const $ = document.querySelector.bind(document);
const NO_FREE_LIMITED = 50;
/**
* Handle Background Process.
* @returns
*/
const BackgroundProcess = () => {
return {
handle(action) {
return Fetcher.background[action]();
},
initState() {
return Fetcher.background.initState();
},
}
}
/**
* Background Optimization.
*/
const BackgroundSmush = (() => {
const startBtn = window.wp_smushit_data && window.wp_smushit_data.bo_stats && $('.wp-smush-bo-start');
if (!startBtn) {
return;
}
const mixPanel = MixPanel.getInstance();
const BO = new BackgroundProcess();
const bulkWrapper = $('.bulk-smush-wrapper');
const reScanImagesButton = $('.wp-smush-scan');
let intervalHandle = 0;
let cancellationInProgress = false;
return {
hookStatusChecks() {
if (intervalHandle) {
// Already done
return;
}
let count = 0;
let statusSyncInProgress = false;
let statSyncInProgress = false;
intervalHandle = setInterval(() => {
if (statusSyncInProgress) {
return;
}
statusSyncInProgress = true;
count++;
const statusSynced = this.syncBackgroundStatus();
if (count % 3 === 0) {
// Do a full update every nth time
statusSynced.then(() => {
if (!statSyncInProgress) {
this.syncStats().then(() => {
statSyncInProgress = false;
});
statSyncInProgress = true;
}
});
}
statusSynced.finally(() => {
statusSyncInProgress = false;
});
}, 3 * 1000);
},
resetBOStatsOnStart() {
GlobalStats.setBoStats( {
is_cancelled: false,
is_completed: false,
processed_items: 0,
failed_items: 0,
} );
},
start() {
// Reset boStats.
this.resetBOStatsOnStart();
// Disable UI.
this.onStart();
// Start BO.
BO.handle('start').then((res) => {
if (res.success) {
// Update stats.
const updatedStats = this.updateStats(res.data, false);
// Show progress bar.
this.showProgressBar();
this.hookStatusChecks();
if ( updatedStats ) {
// Render stats.
GlobalStats.renderStats();
}
} else {
WP_Smush.helpers.showNotice( res, {
'showdismiss': true,
'autoclose' : false,
} );
this.cancelBulk();
}
});
},
/**
* Initial state when load the Bulk Smush page while BO is running.
*/
initState() {
if (!GlobalStats.getBoStats().in_processing) {
return;
}
// Disable UI.
this.onStart();
// Start BO.
BO.initState().then((res) => {
if (res.success) {
// Update stats.
this.updateStats(res.data, false);
// Show progress bar.
this.showProgressBar();
this.hookStatusChecks();
// Maybe update errors.
if ( res.data.errors && ! Object.keys( GlobalStats.getErrors() ).length ) {
GlobalStats.setErrors( res.data.errors );
}
// Render stats.
GlobalStats.renderStats();
} else {
WP_Smush.helpers.showNotice( res );
}
});
},
/**
* Cancel.
*/
cancel() {
cancellationInProgress = true;
this.setCancelButtonStateToStarted();
BO.handle('cancel').then((res) => {
if (res.success) {
this.cancelBulk();
} else {
WP_Smush.helpers.showNotice( res );
}
});
},
hideProgressBar() {
// Hide progress bar.
SmushProgress.close().update(0, GlobalStats.getBoStats().total_items);
},
showProgressBar() {
// Reset progress bar.
SmushProgress.update(GlobalStats.getBoStats().processed_items, GlobalStats.getBoStats().total_items);
// Show progress bar.
SmushProgress.show();
},
/**
* Update stats.
* @param {Object} newStats Included increment stats and new BO stats.
* @param updateGlobal
*/
updateStats(newStats, updateGlobal) {
// Make sure we have processed_stats/errors property (not added by default when start).
newStats.global_stats = newStats.global_stats || {};
newStats.errors = newStats.errors || {};
const {
global_stats,
errors,
...newBoStats
} = newStats;
if ( ! GlobalStats.isChangedStats( newBoStats ) ) {
return false;
}
// Update BO stats.
GlobalStats.setBoStats(newBoStats);
if (updateGlobal) {
// Update global stats.
GlobalStats.setGlobalStats(global_stats);
}
// Update Errors.
GlobalStats.setErrors( errors );
return true;
},
cancelBulk() {
// Sync Stats.
this.syncStats(() => {
if (100 === GlobalStats.getGlobalStats().percent_optimized) {
// If the last item was getting processed when the user cancelled then the process might have completed
GlobalStats.setBoStats( {
is_completed: true
} );
this.onCompletedBulk();
} else {
// Update status of boStats.
GlobalStats.setBoStats( {
is_cancelled: true
} );
// Reset and hide progress bar.
this.onFinish();
// Bulk is cancelled, show bulk desc.
SmushProgress.showBulkSmushDescription();
}
mixPanel.trackBulkSmushCancel();
cancellationInProgress = false;
});
},
showCompletedMessage() {
// Render completed message.
// Show completed message.
const processedWrapper = bulkWrapper.querySelector('.wp-smush-all-done');
if ( GlobalStats.getBoStats().failed_items ) {
let completeMessage = wp_smush_msgs.all_failed;
if ( ! this.isFailedAllItems() ) {
completeMessage = wp_smush_msgs.error_in_bulk.replace( '{{smushed}}', GlobalStats.getBoStats().total_items - GlobalStats.getBoStats().failed_items )
.replace('{{total}}', GlobalStats.getBoStats().total_items )
.replace('{{errors}}', GlobalStats.getBoStats().failed_items );
}
processedWrapper.querySelector('p').innerHTML = completeMessage;
processedWrapper.classList.remove('sui-notice-success', 'sui-notice-warning');
const noticeType = this.getNoticeType();
const noticeIcon = 'warning' === noticeType ? 'info' : 'check-tick';
const iconElement = processedWrapper.querySelector('.sui-notice-icon');
processedWrapper.classList.add( 'sui-notice-' + noticeType );
iconElement.classList.remove('sui-icon-check-tick', 'sui-icon-info');
iconElement.classList.add( 'sui-icon-' + noticeIcon );
} else {
processedWrapper.querySelector('p').innerHTML = wp_smush_msgs.all_smushed;
}
processedWrapper.classList.remove('sui-hidden');
},
isFailedAllItems() {
return GlobalStats.getBoStats().failed_items === GlobalStats.getBoStats().total_items;
},
getNoticeType() {
return this.isFailedAllItems() ? 'warning' : 'success';
},
onCompletedBulk() {
// Reset and hide progress bar.
this.onFinish();
// Bulk is completed, hide bulk desc.
SmushProgress.hideBulkSmushDescription();
// Show completed message.
this.showCompletedMessage();
// Reset the progress when we finish so the next smushing starts from zero.
SmushProgress.update(0, GlobalStats.getBoStats().total_items);
},
completeBulk() {
// Sync Stats.
this.syncStats(() => this.onCompletedBulk());
},
syncStats(onComplete = () => false) {
return BO.handle('getStats').then((res) => {
if ( res.success ) {
const responseErrors = res.data.errors || {};
this.updateStats( { global_stats: res.data, errors: responseErrors }, true );
GlobalStats.renderStats();
if ( res.data.content ) {
$('#wp-smush-bulk-content').innerHTML = res.data.content;
}
onComplete();
} else {
WP_Smush.helpers.showNotice( res );
}
}).catch( (error) => console.log('error', error));
},
syncBackgroundStatus() {
return BO.handle('getStatus').then((res) => {
if ((res.data || {}).in_process_notice) {
SmushProgress.setNotice( res.data.in_process_notice );
}
if (res.success) {
// Update stats.
if ( this.updateStats(res.data, false) ) {
// Update progress bar.
SmushProgress.update(GlobalStats.getBoStats().processed_items, GlobalStats.getBoStats().total_items);
if (! GlobalStats.getBoStats().is_cancelled && ! GlobalStats.getBoStats().is_completed) {
// Render stats.
GlobalStats.renderStats();
}
}
if (GlobalStats.getBoStats().is_cancelled && !cancellationInProgress) {
// Cancelled on server side
this.cancelBulk();
} else if (GlobalStats.getBoStats().is_completed) {
this.completeBulk();
}
} else {
WP_Smush.helpers.showNotice( res );
}
});
},
onStart() {
// Disable btn.
startBtn.setAttribute('disabled', '');
// Disable re-check images.
reScanImagesButton && reScanImagesButton.setAttribute('disabled', '');
$('.wp-smush-restore').setAttribute('disabled', '');
// Show upsell cdn.
UpsellManger.maybeShowCDNUpsellForPreSiteOnStart();
this.setCancelButtonStateToInitial();
},
onFinish() {
// Clear interval.
if (intervalHandle) {
clearInterval(intervalHandle);
intervalHandle = 0;
}
// Disable btn.
startBtn.removeAttribute('disabled');
// Reset and hide Progress Bar.
this.hideProgressBar();
// Disable re-check images.
reScanImagesButton && reScanImagesButton.removeAttribute('disabled', '');
$('.wp-smush-restore').removeAttribute('disabled', '');
// Show upsell cdn.
UpsellManger.maybeShowCDNUpsellForPreSiteOnCompleted();
},
init() {
if (!startBtn) {
return;
}
// Start BO.
startBtn.onclick = () => {
const requiredScanMedia = startBtn.classList.contains('wp-smush-scan-and-bulk-smush');
if ( requiredScanMedia ) {
return;
}
this.start();
}
// If BO is running, initial new state.
this.initState();
},
setCancelButtonStateToInitial() {
SmushProgress.setCancelButtonLabel( wp_smush_msgs.cancel );
SmushProgress.setOnCancelCallback( this.cancel.bind(this) );
},
setCancelButtonStateToStarted() {
SmushProgress.setCancelButtonLabel( wp_smush_msgs.cancelling );
SmushProgress.setOnCancelCallback( () => false );
},
}
})();
// Run.
BackgroundSmush && BackgroundSmush.init();
/**
* For globalStats, we will need to update it on reload and after re-checking images,
* and on finish the BO.
* 1. On finish, we handled via BackgroundSmush.syncStats -> BackgroundSmush.updateStats
* 2. On reload or after re-checking images, we need to update globalStats from global variable:
* window.wp_smushit_data
*/
// Update global stats after re-checking images.
document.addEventListener('wpSmushAfterRecheckImages', function(){
GlobalStats.updateGlobalStatsFromSmushScriptData();
});
document.addEventListener('backgroundBulkSmushOnScanCompleted', function(){
if ( ! BackgroundSmush ) {
return;
}
GlobalStats.setBoStats({
in_processing: true,
});
BackgroundSmush.initState();
});
})();

View File

@ -0,0 +1,276 @@
/* global WP_Smush */
/* global ajaxurl */
/* global _ */
import MixPanel from "../mixpanel";
/**
* Bulk restore JavaScript code.
*
* @since 3.2.2
*/
(function () {
'use strict';
/**
* Bulk restore modal.
*
* @since 3.2.2
*/
WP_Smush.restore = {
modal: document.getElementById('smush-restore-images-dialog'),
contentContainer: document.getElementById('smush-bulk-restore-content'),
settings: {
slide: 'start', // start, progress or finish.
success: 0,
errors: [],
},
items: [], // total items, 1 item = 1 step.
success: [], // successful items restored.
errors: [], // failed items.
currentStep: 0,
totalSteps: 0,
/**
* Init module.
*/
init() {
if (!this.modal) {
return;
}
this.settings = {
slide: 'start',
success: 0,
errors: [],
};
this.mixPanel = MixPanel.getInstance();
this.resetModalWidth();
this.renderTemplate();
// Show the modal.
window.SUI.openModal(
'smush-restore-images-dialog',
'wpbody-content',
undefined,
false
);
},
/**
* Update the template, register new listeners.
*/
renderTemplate() {
const template = WP_Smush.onboarding.template('smush-bulk-restore');
const content = template(this.settings);
if (content) {
this.contentContainer.innerHTML = content;
}
this.bindSubmit();
},
/**
* Reset modal width.
*
* @since 3.6.0
*/
resetModalWidth() {
this.modal.style.maxWidth = '460px';
this.modal.querySelector('.sui-box').style.maxWidth = '460px';
},
/**
* Catch "Finish setup wizard" button click.
*/
bindSubmit() {
const confirmButton = this.modal.querySelector(
'button[id="smush-bulk-restore-button"]'
);
const self = this;
if (confirmButton) {
confirmButton.addEventListener('click', function (e) {
e.preventDefault();
self.resetModalWidth();
self.settings = { slide: 'progress' };
self.errors = [];
self.renderTemplate();
self.initScan();
self.mixPanel.track('Bulk Restore Triggered');
});
}
},
/**
* Cancel the bulk restore.
*/
cancel() {
if (
'start' === this.settings.slide ||
'finish' === this.settings.slide
) {
// Hide the modal.
window.SUI.closeModal();
} else {
this.updateProgressBar(true);
window.location.reload();
}
},
/**
* Update progress bar during directory smush.
*
* @param {boolean} cancel Cancel status.
*/
updateProgressBar(cancel = false) {
let progress = 0;
if (0 < this.currentStep) {
progress = Math.min(
Math.round((this.currentStep * 100) / this.totalSteps),
99
);
}
if (progress > 100) {
progress = 100;
}
// Update progress bar
this.modal.querySelector('.sui-progress-text span').innerHTML =
progress + '%';
this.modal.querySelector('.sui-progress-bar span').style.width =
progress + '%';
const statusDiv = this.modal.querySelector(
'.sui-progress-state-text'
);
if (progress >= 90) {
statusDiv.innerHTML = 'Finalizing...';
} else if (cancel) {
statusDiv.innerHTML = 'Cancelling...';
} else {
statusDiv.innerHTML =
this.currentStep +
'/' +
this.totalSteps +
' ' +
'images restored';
}
},
/**
* First step in bulk restore - get the bulk attachment count.
*/
initScan() {
const self = this;
const _nonce = document.getElementById('_wpnonce');
const xhr = new XMLHttpRequest();
xhr.open('POST', ajaxurl + '?action=get_image_count', true);
xhr.setRequestHeader(
'Content-type',
'application/x-www-form-urlencoded'
);
xhr.onload = () => {
if (200 === xhr.status) {
const res = JSON.parse(xhr.response);
if ('undefined' !== typeof res.data.items) {
self.items = res.data.items;
self.totalSteps = res.data.items.length;
self.step();
}
} else {
window.console.log(
'Request failed. Returned status of ' + xhr.status
);
}
};
xhr.send('_ajax_nonce=' + _nonce.value);
},
/**
* Execute a scan step recursively
*/
step() {
const self = this;
const _nonce = document.getElementById('_wpnonce');
if (0 < this.items.length) {
const item = this.items.pop();
const xhr = new XMLHttpRequest();
xhr.open('POST', ajaxurl + '?action=restore_step', true);
xhr.setRequestHeader(
'Content-type',
'application/x-www-form-urlencoded'
);
xhr.onload = () => {
this.currentStep++;
if (200 === xhr.status) {
const res = JSON.parse(xhr.response);
const data = ((res || {}).data || {});
if (data.success) {
self.success.push(item);
} else {
self.errors.push({
id: item,
src: data.src || "Error",
thumb: data.thumb,
link: data.link,
});
}
}
self.updateProgressBar();
self.step();
};
xhr.send('item=' + item + '&_ajax_nonce=' + _nonce.value);
} else {
// Finish.
this.settings = {
slide: 'finish',
success: this.success.length,
errors: this.errors,
total: this.totalSteps,
};
self.renderTemplate();
if (0 < this.errors.length) {
this.modal.style.maxWidth = '660px';
this.modal.querySelector('.sui-box').style.maxWidth =
'660px';
}
}
},
};
/**
* Template function (underscores based).
*
* @type {Function}
*/
WP_Smush.restore.template = _.memoize((id) => {
let compiled;
const options = {
evaluate: /<#([\s\S]+?)#>/g,
interpolate: /{{{([\s\S]+?)}}}/g,
escape: /{{([^}]+?)}}(?!})/g,
variable: 'data',
};
return (data) => {
_.templateSettings = options;
compiled =
compiled || _.template(document.getElementById(id).innerHTML);
return compiled(data);
};
});
})();

View File

@ -0,0 +1,149 @@
/* global WP_Smush */
/* global ajaxurl */
/**
* Bulk Smush functionality.
*
* @since 2.9.0 Moved from admin.js
*/
import Smush from '../smush/smush';
import Fetcher from '../utils/fetcher';
import SmushProcess from '../common/progressbar';
( function( $ ) {
'use strict';
WP_Smush.bulk = {
init() {
this.onClickBulkSmushNow();
this.onClickIgnoreImage();
this.onClickIgnoreAllImages();
this.onScanCompleted();
},
onClickBulkSmushNow() {
/**
* Handle the Bulk Smush/Bulk re-Smush button click.
*/
$( '.wp-smush-all' ).on( 'click', function( e ) {
const bulkSmushButton = $(this);
if ( bulkSmushButton.hasClass('wp-smush-scan-and-bulk-smush') ) {
return;
}
e.preventDefault();
WP_Smush.bulk.ajaxBulkSmushStart( bulkSmushButton );
} );
},
ajaxBulkSmushStart( bulkSmushButton ) {
bulkSmushButton = bulkSmushButton || $( '#wp-smush-bulk-content .wp-smush-all' );
// Check for IDs, if there is none (unsmushed or lossless), don't call Smush function.
/** @param {Array} wp_smushit_data.unsmushed */
if (
'undefined' === typeof window.wp_smushit_data ||
( 0 === window.wp_smushit_data.unsmushed.length &&
0 === window.wp_smushit_data.resmush.length )
) {
return false;
}
// Disable re-Smush and scan button.
// TODO: refine what is disabled.
$(
'.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'
).prop( 'disabled', true );
if ( bulkSmushButton.hasClass('wp-smush-resume-bulk-smush') && this.bulkSmush ) {
this.resumeBulkSmush();
return;
}
this.bulkSmush = new Smush( bulkSmushButton, true );
SmushProcess.setOnCancelCallback( () => {
this.bulkSmush.cancelAjax()
}).update( 0, this.bulkSmush.ids.length ).show();
// Show upsell cdn.
this.maybeShowCDNUpsellForPreSiteOnStart();
// Run bulk Smush.
this.bulkSmush.run();
},
resumeBulkSmush() {
SmushProcess.disableExceedLimitMode();
SmushProcess.hideBulkSmushDescription();
this.bulkSmush.onStart();
this.bulkSmush.callAjax();
},
onClickIgnoreImage() {
/**
* Ignore file from bulk Smush.
*
* @since 2.9.0
*/
$( 'body' ).on( 'click', '.smush-ignore-image', function( e ) {
e.preventDefault();
const self = $( this );
self.prop( 'disabled', true );
self.attr( 'data-tooltip' );
self.removeClass( 'sui-tooltip' );
$.post( ajaxurl, {
action: 'ignore_bulk_image',
id: self.attr( 'data-id' ),
_ajax_nonce: wp_smush_msgs.nonce,
} ).done( ( response ) => {
if ( self.is( 'a' ) && response.success && 'undefined' !== typeof response.data.html ) {
if( self.closest('.smush-status-links') ) {
self.closest('.smush-status-links').parent().html( response.data.html );
} else if (e.target.closest( '.smush-bulk-error-row' ) ){
self.addClass('disabled');
e.target.closest( '.smush-bulk-error-row' ).style.opacity = 0.5;
}
}
} );
} );
},
onClickIgnoreAllImages() {
/**
* Ignore file from bulk Smush.
*
* @since 3.12.0
*/
const ignoreAll = document.querySelector('.wp_smush_ignore_all_failed_items');
if ( ignoreAll ) {
ignoreAll.onclick = (e) => {
e.preventDefault();
e.target.setAttribute('disabled','');
e.target.style.cursor = 'progress';
const type = e.target.dataset.type || null;
e.target.classList.remove('sui-tooltip');
Fetcher.smush.ignoreAll(type).then((res) => {
if ( res.success ) {
window.location.reload();
} else {
e.target.style.cursor = 'pointer';
e.target.removeAttribute('disabled');
WP_Smush.helpers.showNotice( res );
}
});
}
}
},
onScanCompleted() {
document.addEventListener('ajaxBulkSmushOnScanCompleted', (e) => {
this.ajaxBulkSmushStart();
});
},
maybeShowCDNUpsellForPreSiteOnStart: () => {
// Show upsell cdn.
const upsell_cdn = document.querySelector('.wp-smush-upsell-cdn');
if ( upsell_cdn ) {
upsell_cdn.classList.remove('sui-hidden');
}
}
};
WP_Smush.bulk.init();
} )( jQuery );

View File

@ -0,0 +1,279 @@
/* global WP_Smush */
/* global ajaxurl */
/**
* Directory Smush module JavaScript code.
*
* @since 2.8.1 Separated from admin.js into dedicated file.
*/
import { createTree } from 'jquery.fancytree';
import Scanner from '../smush/directory-scanner';
( function( $ ) {
'use strict';
WP_Smush.directory = {
selected: [],
tree: [],
wp_smush_msgs: [],
triggered: false,
init() {
const self = this,
progressDialog = $( '#wp-smush-progress-dialog' );
let totalSteps = 0,
currentScanStep = 0;
// Make sure directory smush vars are set.
if ( typeof window.wp_smushit_data.dir_smush !== 'undefined' ) {
totalSteps = window.wp_smushit_data.dir_smush.totalSteps;
currentScanStep =
window.wp_smushit_data.dir_smush.currentScanStep;
}
// Init image scanner.
this.scanner = new Scanner( totalSteps, currentScanStep );
/**
* Smush translation strings.
*
* @param {Array} wp_smush_msgs
*/
this.wp_smush_msgs = window.wp_smush_msgs || {};
/**
* Open the "Select Smush directory" modal.
*/
$( 'button.wp-smush-browse, a#smush-directory-open-modal' ).on(
'click',
function( e ) {
e.preventDefault();
if ( $( e.currentTarget ).hasClass( 'wp-smush-browse' ) ) {
// Hide all the notices.
$( 'div.wp-smush-scan-result div.wp-smush-notice' ).hide();
// Remove notice.
$( 'div.wp-smush-info' ).remove();
}
window.SUI.openModal(
'wp-smush-list-dialog',
e.currentTarget,
$(
'#wp-smush-list-dialog .sui-box-header [data-modal-close]'
)[0],
true
);
//Display File tree for Directory Smush
self.initFileTree();
}
);
/**
* Smush images: Smush in Choose Directory modal clicked
*/
$( '#wp-smush-select-dir' ).on( 'click', function( e ) {
e.preventDefault();
$( 'div.wp-smush-list-dialog div.sui-box-body' ).css( {
opacity: '0.8',
} );
$( 'div.wp-smush-list-dialog div.sui-box-body a' ).off(
'click'
);
const button = $( this );
// Display the spinner.
button.addClass('sui-button-onload');
const selectedFolders = self.tree.getSelectedNodes();
const paths = [];
selectedFolders.forEach( function( folder ) {
paths.push( folder.key );
} );
// Send a ajax request to get a list of all the image files
const param = {
action: 'image_list',
smush_path: paths,
image_list_nonce: $(
'input[name="image_list_nonce"]'
).val(),
};
$.post( ajaxurl, param, function( response ) {
if ( response.success ) {
// Close the modal.
window.SUI.closeModal();
self.scanner = new Scanner( response.data, 0 );
self.showProgressDialog( response.data );
self.scanner.scan();
} else {
// Remove the spinner.
button.removeClass('sui-button-onload');
window.SUI.openNotice(
'wp-smush-ajax-notice',
response.data.message,
{ type: 'warning' }
);
}
} );
} );
/**
* Cancel scan.
*/
progressDialog.on(
'click',
'#cancel-directory-smush, #dialog-close-div, .wp-smush-cancel-dir',
function( e ) {
e.preventDefault();
// Display the spinner
$( '.wp-smush-cancel-dir' ).addClass( 'sui-button-onload' );
self.scanner
.cancel()
.done(
() =>
( window.location.href =
self.wp_smush_msgs.directory_url )
);
}
);
/**
* Continue scan.
*/
progressDialog.on(
'click',
'.sui-icon-play, .wp-smush-resume-scan',
function( e ) {
e.preventDefault();
self.scanner.resume();
}
);
/**
* Check to see if we should open the directory module.
* Used to redirect from dashboard page.
*
* @since 3.8.6
*/
const queryString = window.location.search;
const urlParams = new URLSearchParams( queryString );
if ( urlParams.has( 'start' ) && ! this.triggered ) {
this.triggered = true;
$( 'button.wp-smush-browse' ).trigger( 'click' );
}
},
/**
* Init fileTree.
*/
initFileTree() {
const self = this,
smushButton = $( 'button#wp-smush-select-dir' ),
ajaxSettings = {
type: 'GET',
url: ajaxurl,
data: {
action: 'smush_get_directory_list',
list_nonce: $( 'input[name="list_nonce"]' ).val(),
},
cache: false,
};
// Object already defined.
if ( Object.entries( self.tree ).length > 0 ) {
return;
}
self.tree = createTree( '.wp-smush-list-dialog .content', {
autoCollapse: true, // Automatically collapse all siblings, when a node is expanded
clickFolderMode: 3, // 1:activate, 2:expand, 3:activate and expand, 4:activate (dblclick expands)
checkbox: true, // Show checkboxes
debugLevel: 0, // 0:quiet, 1:errors, 2:warnings, 3:infos, 4:debug
selectMode: 3, // 1:single, 2:multi, 3:multi-hier
tabindex: '0', // Whole tree behaves as one single control
keyboard: true, // Support keyboard navigation
quicksearch: true, // Navigate to next node by typing the first letters
source: ajaxSettings,
lazyLoad: ( event, data ) => {
data.result = new Promise( function( resolve, reject ) {
ajaxSettings.data.dir = data.node.key;
$.ajax( ajaxSettings )
.done( ( response ) => resolve( response ) )
.fail( reject );
} );
},
loadChildren: ( event, data ) =>
data.node.fixSelection3AfterClick(), // Apply parent's state to new child nodes:
select: () =>
smushButton.prop(
'disabled',
! +self.tree.getSelectedNodes().length
),
init: () => smushButton.prop( 'disabled', true ),
} );
},
/**
* Show progress dialog.
*
* @param {number} items Number of items in the scan.
*/
showProgressDialog( items ) {
// Update items status and show the progress dialog..
$( '.wp-smush-progress-dialog .sui-progress-state-text' ).html(
'0/' + items + ' ' + self.wp_smush_msgs.progress_smushed
);
window.SUI.openModal(
'wp-smush-progress-dialog',
'dialog-close-div',
undefined,
false
);
},
/**
* Update progress bar during directory smush.
*
* @param {number} progress Current progress in percent.
* @param {boolean} cancel Cancel status.
*/
updateProgressBar( progress, cancel = false ) {
if ( progress > 100 ) {
progress = 100;
}
// Update progress bar
$( '.sui-progress-block .sui-progress-text span' ).text(
progress + '%'
);
$( '.sui-progress-block .sui-progress-bar span' ).width(
progress + '%'
);
if ( progress >= 90 ) {
$( '.sui-progress-state .sui-progress-state-text' ).text(
'Finalizing...'
);
}
if ( cancel ) {
$( '.sui-progress-state .sui-progress-state-text' ).text(
'Cancelling...'
);
}
},
};
WP_Smush.directory.init();
}( jQuery ) );

View File

@ -0,0 +1,298 @@
/* global WP_Smush */
/* global ajaxurl */
/* global wp_smush_msgs */
/**
* Helpers functions.
*
* @since 2.9.0 Moved from admin.js
*/
( function() {
'use strict';
WP_Smush.helpers = {
init: () => {},
cacheUpsellErrorCodes: [],
/**
* Convert bytes to human-readable form.
*
* @param {number} a Bytes
* @param {number} b Number of digits
* @return {*} Formatted Bytes
*/
formatBytes: ( a, b ) => {
const thresh = 1024,
units = [ 'KB', 'MB', 'GB', 'TB', 'PB' ];
if ( Math.abs( a ) < thresh ) {
return a + ' B';
}
let u = -1;
do {
a /= thresh;
++u;
} while ( Math.abs( a ) >= thresh && u < units.length - 1 );
return a.toFixed( b ) + ' ' + units[ u ];
},
/**
* Get size from a string.
*
* @param {string} formattedSize Formatter string
* @return {*} Formatted Bytes
*/
getSizeFromString: ( formattedSize ) => {
return formattedSize.replace( /[a-zA-Z]/g, '' ).trim();
},
/**
* Get type from formatted string.
*
* @param {string} formattedSize Formatted string
* @return {*} Formatted Bytes
*/
getFormatFromString: ( formattedSize ) => {
return formattedSize.replace( /[0-9.]/g, '' ).trim();
},
/**
* Stackoverflow: http://stackoverflow.com/questions/1726630/formatting-a-number-with-exactly-two-decimals-in-javascript
*
* @param {number} num
* @param {number} decimals
* @return {number} Number
*/
precise_round: ( num, decimals ) => {
const sign = num >= 0 ? 1 : -1;
// Keep the percentage below 100.
num = num > 100 ? 100 : num;
return (
Math.round( num * Math.pow( 10, decimals ) + sign * 0.001 ) /
Math.pow( 10, decimals )
);
},
/**
* Displays a floating error message using the #wp-smush-ajax-notice container.
*
* @since 3.8.0
*
* @param {string} message
*/
showErrorNotice: ( message ) => {
if ( 'undefined' === typeof message ) {
return;
}
const noticeMessage = `<p>${ message }</p>`,
noticeOptions = {
type: 'error',
icon: 'info',
};
SUI.openNotice( 'wp-smush-ajax-notice', noticeMessage, noticeOptions );
const loadingButton = document.querySelector( '.sui-button-onload' );
if ( loadingButton ) {
loadingButton.classList.remove( 'sui-button-onload' );
}
},
/**
* Reset settings.
*
* @since 3.2.0
*/
resetSettings: () => {
const _nonce = document.getElementById( 'wp_smush_reset' );
const xhr = new XMLHttpRequest();
xhr.open( 'POST', ajaxurl + '?action=reset_settings', true );
xhr.setRequestHeader(
'Content-type',
'application/x-www-form-urlencoded'
);
xhr.onload = () => {
if ( 200 === xhr.status ) {
const res = JSON.parse( xhr.response );
if ( 'undefined' !== typeof res.success && res.success ) {
window.location.href = wp_smush_msgs.smush_url;
}
} else {
window.console.log(
'Request failed. Returned status of ' + xhr.status
);
}
};
xhr.send( '_ajax_nonce=' + _nonce.value );
},
/**
* Prepare error row. Will only allow to hide errors for WP media attachments (not nextgen).
*
* @since 1.9.0
* @since 3.12.0 Moved from Smush.
*
* @param {string} errorMsg Error message.
* @param {string} fileName File name.
* @param {string} thumbnail Thumbnail for image (if available).
* @param {number} id Image ID.
* @param {string} type Smush type: media or netxgen.
* @param {string} errorCode Error code.
*
* @return {string} Row with error.
*/
prepareBulkSmushErrorRow: (errorMsg, fileName, thumbnail, id, type, errorCode) => {
const thumbDiv =
thumbnail && 'undefined' !== typeof thumbnail ?
`<img class="attachment-thumbnail" src="${thumbnail}" />` :
'<i class="sui-icon-photo-picture" aria-hidden="true"></i>';
const editLink = window.wp_smush_msgs.edit_link.replace('{{id}}', id);
fileName =
'undefined' === fileName || 'undefined' === typeof fileName ?
'undefined' :
fileName;
let tableDiv =
`<div class="smush-bulk-error-row" data-error-code="${errorCode}">
<div class="smush-bulk-image-data">
<div class="smush-bulk-image-title">
${ thumbDiv }
<span class="smush-image-name">
<a href="${editLink}">${fileName}</a>
</span>
</div>
<div class="smush-image-error">
${errorMsg}
</div>
</div>`;
if ('media' === type) {
tableDiv +=
`<div class="smush-bulk-image-actions">
<a href="javascript:void(0)" class="sui-tooltip sui-tooltip-constrained sui-tooltip-left smush-ignore-image" data-tooltip="${window.wp_smush_msgs.error_ignore}" data-id="${id}">
${window.wp_smush_msgs.btn_ignore}
</a>
<a class="smush-link-detail" href="${editLink}">
${window.wp_smush_msgs.view_detail}
</a>
</div>`;
}
tableDiv += '</div>';
tableDiv += WP_Smush.helpers.upsellWithError( errorCode );
return tableDiv;
},
cacheUpsellErrorCode( errorCode ) {
this.cacheUpsellErrorCodes.push( errorCode );
},
/**
* Get upsell base on error code.
* @param {string} errorCode Error code.
* @returns {string}
*/
upsellWithError(errorCode) {
if (
!errorCode
|| !window.wp_smush_msgs['error_' + errorCode]
|| this.isUpsellRendered( errorCode )
) {
return '';
}
this.cacheRenderedUpsell( errorCode );
return '<div class="smush-bulk-error-row smush-error-upsell">' +
'<div class="smush-bulk-image-title">' +
'<span class="smush-image-error">' +
window.wp_smush_msgs['error_' + errorCode] +
'</span>' +
'</div></div>';
},
// Do not use arrow function to use `this`.
isUpsellRendered( errorCode ) {
return this.cacheUpsellErrorCodes.includes( errorCode );
},
// Do not use arrow function to use `this`.
cacheRenderedUpsell( errorCode ) {
this.cacheUpsellErrorCodes.push( errorCode );
},
/**
* Get error message from Ajax response or Error.
* @param {Object} resp
*/
getErrorMessage: ( resp ) => {
return resp.message || resp.data && resp.data.message ||
resp.responseJSON && resp.responseJSON.data && resp.responseJSON.data.message ||
window.wp_smush_msgs.generic_ajax_error ||
resp.status && 'Request failed. Returned status of ' + resp.status
},
/**
* Displays a floating message from response,
* using the #wp-smush-ajax-notice container.
*
* @param {Object|string} notice
* @param {Object} noticeOptions
*/
showNotice: function( notice, noticeOptions ) {
let message;
if ( 'object' === typeof notice ) {
message = this.getErrorMessage( notice );
} else {
message = notice;
}
if ( ! message ) {
return;
}
noticeOptions = noticeOptions || {};
noticeOptions = Object.assign({
showdismiss: false,
autoclose: true,
},noticeOptions);
noticeOptions = {
type: noticeOptions.type || 'error',
icon: noticeOptions.icon || ( 'success' === noticeOptions.type ? 'check-tick' : 'info' ),
dismiss: {
show: noticeOptions.showdismiss,
label: window.wp_smush_msgs.noticeDismiss,
tooltip: window.wp_smush_msgs.noticeDismissTooltip,
},
autoclose: {
show: noticeOptions.autoclose
}
};
const noticeMessage = `<p>${ message }</p>`;
SUI.openNotice( 'wp-smush-ajax-notice', noticeMessage, noticeOptions );
return Promise.resolve( '#wp-smush-ajax-notice' );
},
closeNotice() {
window.SUI.closeNotice( 'wp-smush-ajax-notice' );
},
renderActivationCDNNotice: function( noticeMessage ) {
const animatedNotice = document.getElementById('wp-smush-animated-upsell-notice');
if ( animatedNotice ) {
return;
}
const upsellHtml = `<div class="sui-notice sui-notice-info sui-margin-top" id="wp-smush-animated-upsell-notice">
<div class="sui-notice-content">
<div class="sui-notice-message">
<i class="sui-notice-icon sui-icon-info" aria-hidden="true"></i>
<p>${noticeMessage}</p>
</div>
</div>
</div>`;
document.querySelector( '#smush-box-bulk .wp-smush-bulk-wrapper' ).outerHTML += upsellHtml;
}
};
WP_Smush.helpers.init();
}() );

View File

@ -0,0 +1,385 @@
/* global WP_Smush */
/**
* Scan Media Library.
*
*/
import SmushProgress from '../common/progressbar';
import MediaLibraryScanner from '../common/media-library-scanner';
import { GlobalStats } from '../common/globalStats';
( function() {
'use strict';
if ( ! window.wp_smush_msgs ) {
return;
}
const $ = document.querySelector.bind( document );
const existScanProgressBar = $( '.wp-smush-scan-progress-bar-wrapper' );
if ( ! existScanProgressBar ) {
return;
}
const recheckImagesBtn = $( '.wp-smush-scan' );
if ( ! recheckImagesBtn ) {
return;
}
const bulkSmushButton = $( '.wp-smush-bo-start' ) || $( '.wp-smush-bulk-wrapper .wp-smush-all' );
const { __ } = wp.i18n;
class MediaLibraryScannerOnBulkSmush extends MediaLibraryScanner {
constructor() {
super();
this.runBulkSmushOnComplete = false;
this.restoreButton = $( '.wp-smush-restore' );
this.autoBulkSmushNotification = $( '.wp-smush-auto-bulk-smush-notification' );
}
startScanThenBulkSmushOnComplete() {
this.runBulkSmushOnComplete = true;
return this.startScan( true );
}
onStart() {
this.hideRecheckNotice();
this.disableRelatedButtons();
this.setRecheckImagesButtonOnLoad();
this.toggleBulkSmushBoxContent();
return this;
}
onStartFailure( response ) {
super.onStartFailure( response );
this.revertRelatedButtons();
}
onCloseProgressBar() {
this.maybeHideAutoBulkSmushNotification();
}
disableRelatedButtons() {
this.restoreButton.setAttribute( 'disabled', true );
if ( bulkSmushButton ) {
bulkSmushButton.setAttribute( 'disabled', true );
this.setInnerText( bulkSmushButton, __( 'Waiting for Re-check to finish', 'wp-smushit' ) );
}
}
revertRelatedButtons() {
if ( bulkSmushButton ) {
bulkSmushButton.removeAttribute( 'disabled' );
this.revertInnerText( bulkSmushButton );
}
this.restoreButton.removeAttribute( 'disabled' );
this.revertRecheckImagesButton();
return this;
}
setRecheckImagesButtonOnLoad() {
// recheckImagesBtn.classList.add( 'sui-button-onload' );
this.disableRecheckImagesButton();
this.setInnerText( recheckImagesBtn.querySelector( '.wp-smush-inner-text' ), __( 'Checking Images', 'wp-smushit' ) );
}
disableRecheckImagesButton() {
recheckImagesBtn.setAttribute( 'disabled', true );
}
revertRecheckImagesButton() {
// recheckImagesBtn.classList.remove( 'sui-button-onload' );
recheckImagesBtn.removeAttribute( 'disabled' );
this.revertInnerText( recheckImagesBtn.querySelector( '.wp-smush-inner-text' ) );
}
beforeUpdateStatus( stats ) {
this.runBulkSmushOnComplete = stats?.optimize_on_scan_completed;
this.maybeShowAutoBulkSmushNotification();
}
onDead( stats ) {
super.onDead( stats );
this.revertRelatedButtons();
this.setRequiredScanForBulkSmushButton();
}
onFinish( stats ) {
const globalStats = stats.global_stats;
super.onFinish( stats );
this.revertRelatedButtons();
this.toggleBulkSmushDescription( globalStats );
if ( globalStats.is_outdated ) {
this.setRequiredScanForBulkSmushButton();
} else {
this.removeScanEventFromBulkSmushButton();
}
this.revertRecheckWarning();
}
onCompleted( stats ) {
const requiredReloadPage = ! bulkSmushButton;
if ( requiredReloadPage ) {
window.location.reload();
return;
}
this.onFinish( stats );
const globalStats = stats.global_stats;
const allImagesSmushed = globalStats.remaining_count < 1;
if ( allImagesSmushed ) {
return;
}
if ( ! this.runBulkSmushOnComplete ) {
this.showRecheckNoticeSuccess();
return;
}
this.runBulkSmushOnComplete = false;
this.triggerBulkSmushEvent( stats );
}
showNotice( stats ) {
if ( ! stats.notice ) {
return;
}
let type = 'success';
if ( 'undefined' !== typeof stats.noticeType ) {
type = stats.noticeType;
}
window.SUI.openNotice(
'wp-smush-ajax-notice',
'<p>' + stats.notice + '</p>',
{ type, icon: 'check-tick' }
);
}
showRecheckNoticeSuccess() {
const recheckNotice = $( '.wp-smush-recheck-images-notice-box' );
if ( ! recheckNotice ) {
return;
}
this.showAnElement( recheckNotice );
this.hideAnElement( recheckNotice.querySelector( '.wp-smush-recheck-images-notice-warning' ) );
this.showAnElement( recheckNotice.querySelector( '.wp-smush-recheck-images-notice-success' ) );
}
showRecheckNoticeWarning() {
const recheckNotice = $( '.wp-smush-recheck-images-notice-box' );
if ( ! recheckNotice ) {
return;
}
this.showAnElement( recheckNotice );
this.hideAnElement( recheckNotice.querySelector( '.wp-smush-recheck-images-notice-success' ) );
this.showAnElement( recheckNotice.querySelector( '.wp-smush-recheck-images-notice-warning' ) );
}
hideRecheckNotice() {
this.hideAnElement( $( '.wp-smush-recheck-images-notice-box' ) );
}
showProgressErrorNoticeOnRecheckNotice() {
const recheckWarningElement = $( '.wp-smush-recheck-images-notice-box .wp-smush-recheck-images-notice-warning' );
if ( ! recheckWarningElement ) {
return;
}
recheckWarningElement.classList.add( 'sui-notice-error' );
recheckWarningElement.classList.remove( 'sui-notice-warning' );
this.setInnerText( recheckWarningElement.querySelector( 'span' ), this.getErrorProgressMessage() );
this.showRecheckNoticeWarning();
}
revertRecheckWarning() {
const recheckWarningElement = $( '.wp-smush-recheck-images-notice-box .wp-smush-recheck-images-notice-warning' );
if ( ! recheckWarningElement ) {
return;
}
recheckWarningElement.classList.add( 'sui-notice-warning' );
recheckWarningElement.classList.remove( 'sui-notice-error' );
this.revertInnerText( recheckWarningElement.querySelector( 'span' ) );
}
triggerBulkSmushEvent( stats ) {
this.disableRecheckImagesButton();
if ( stats.enabled_background_process ) {
this.triggerBackgroundBulkSmushEvent( stats.global_stats );
} else {
this.triggerAjaxBulkSmushEvent( stats.global_stats );
}
}
toggleBulkSmushDescription( globalStats ) {
if ( SmushProgress.isEmptyObject ) {
return;
}
if ( globalStats.remaining_count < 1 ) {
SmushProgress.hideBulkSmushDescription();
SmushProgress.showBulkSmushAllDone();
} else {
SmushProgress.showBulkSmushDescription();
SmushProgress.hideBulkSmushAllDone();
}
}
setRequiredScanForBulkSmushButton() {
bulkSmushButton && bulkSmushButton.classList.add( 'wp-smush-scan-and-bulk-smush' );
}
removeScanEventFromBulkSmushButton() {
bulkSmushButton && bulkSmushButton.classList.remove( 'wp-smush-scan-and-bulk-smush' );
}
triggerBackgroundBulkSmushEvent( globalStats ) {
document.dispatchEvent(
new CustomEvent( 'backgroundBulkSmushOnScanCompleted', {
detail: globalStats
} )
);
}
triggerAjaxBulkSmushEvent( globalStats ) {
document.dispatchEvent(
new CustomEvent( 'ajaxBulkSmushOnScanCompleted', {
detail: globalStats
} )
);
}
onCancelled( stats ) {
this.onFinish( stats );
this.runBulkSmushOnComplete = false;
this.setRequiredScanForBulkSmushButton();
}
maybeShowAutoBulkSmushNotification() {
if (
! this.runBulkSmushOnComplete
) {
return;
}
this.showAnElement( this.autoBulkSmushNotification );
}
maybeHideAutoBulkSmushNotification() {
if (
! this.runBulkSmushOnComplete
) {
return;
}
this.hideAnElement( this.autoBulkSmushNotification );
}
toggleBulkSmushBoxContent() {
GlobalStats.resetAndHideBulkErrors();
this.toggleBulkSmushDescription( GlobalStats.getGlobalStats() );
}
}
const mediaLibScanner = new MediaLibraryScannerOnBulkSmush();
/**
* Event Listeners.
*/
// Background Scan Media Library.
const registerScanMediaLibraryEvent = () => {
if ( ! recheckImagesBtn ) {
return;
}
const canScanInBackground = recheckImagesBtn.classList.contains( 'wp-smush-background-scan' );
if ( ! canScanInBackground ) {
return;
}
recheckImagesBtn.addEventListener( 'click', () => mediaLibScanner.startScan() );
//Check scan is running.
if ( window.wp_smushit_data.media_library_scan?.in_processing ) {
mediaLibScanner.onStart().showProgressBar().autoSyncStatus();
return;
}
if ( window.location.search.includes( 'smush-action=start-scan-media' ) ) {
recheckImagesBtn.click();
const removeScanActionFromURLAddress = () => {
const cleanedURL = window.location.href.replace( '&smush-action=start-scan-media', '' );
window.history.pushState( null, null, cleanedURL );
};
removeScanActionFromURLAddress();
}
};
registerScanMediaLibraryEvent();
/**
* Recheck Images Notice events.
*/
const registerEventsRelatedRecheckImagesNotice = () => {
const recheckImagesNotice = $( '.wp-smush-recheck-images-notice-box' );
if ( ! recheckImagesNotice || ! recheckImagesBtn ) {
return;
}
const triggerBackgroundScanImagesLink = recheckImagesNotice.querySelector( '.wp-smush-trigger-background-scan' );
if ( triggerBackgroundScanImagesLink ) {
triggerBackgroundScanImagesLink.onclick = ( e ) => {
e.preventDefault();
recheckImagesBtn.click();
};
if ( window.wp_smushit_data.media_library_scan?.is_dead ) {
mediaLibScanner.showProgressErrorNoticeOnRecheckNotice();
} else if( window.wp_smushit_data.is_outdated ) {
mediaLibScanner.showRecheckNoticeWarning();
}
}
const triggerBulkSmush = recheckImagesNotice.querySelector( '.wp-smush-trigger-bulk-smush' );
if ( triggerBulkSmush && bulkSmushButton ) {
triggerBulkSmush.onclick = ( e ) => {
e.preventDefault();
recheckImagesNotice.classList.add( 'sui-hidden' );
bulkSmushButton.click();
};
}
const dismissNotices = recheckImagesNotice.querySelectorAll( 'button.sui-button-icon' );
if ( dismissNotices ) {
dismissNotices.forEach( ( dismissNotice ) => {
dismissNotice.onclick = ( e ) => {
dismissNotice.closest( '.sui-recheck-images-notice' ).classList.add( 'sui-hidden' );
};
} );
}
document.addEventListener( 'onSavedSmushSettings', function( e ) {
if ( ! e?.detail?.is_outdated_stats ) {
return;
}
mediaLibScanner.setRequiredScanForBulkSmushButton();
recheckImagesNotice.classList.remove( 'sui-hidden' );
recheckImagesNotice.querySelector( '.wp-smush-recheck-images-notice-success' ).classList.add( 'sui-hidden' );
recheckImagesNotice.querySelector( '.wp-smush-recheck-images-notice-warning' ).classList.remove( 'sui-hidden' );
} );
};
registerEventsRelatedRecheckImagesNotice();
// Scan and Bulk Smush.
const registerScanAndBulkSmushEvent = () => {
if ( ! bulkSmushButton ) {
return;
}
const handleScanAndBulkSmush = ( e ) => {
const shouldRunScan = bulkSmushButton.classList.contains( 'wp-smush-scan-and-bulk-smush' );
if ( ! shouldRunScan ) {
return;
}
e.preventDefault();
mediaLibScanner.startScanThenBulkSmushOnComplete();
};
bulkSmushButton.addEventListener( 'click', handleScanAndBulkSmush );
};
registerScanAndBulkSmushEvent();
}() );

View File

@ -0,0 +1,63 @@
/* global WP_Smush */
/**
* Scan Media Library.
*
*/
import MediaLibraryScanner from '../common/media-library-scanner';
( function() {
'use strict';
if ( ! window.wp_smush_msgs ) {
return;
}
const $ = document.querySelector.bind( document );
const existScanProgressBar = $( '.wp-smush-scan-progress-bar-wrapper' );
if ( ! existScanProgressBar ) {
return;
}
const recheckImagesBtn = $( '.wp-smush-scan' );
if ( recheckImagesBtn ) {
return;
}
//Check scan is running.
const is_scan_running = window.wp_smushit_data.media_library_scan?.in_processing;
if ( ! is_scan_running ) {
return;
}
const { __ } = wp.i18n;
class mediaLibraryScannerOnDashboard extends MediaLibraryScanner {
constructor() {
super();
this.bulkSmushLink = $( '.wp-smush-bulk-smush-link' );
}
onShowProgressBar() {
this.disableBulkSmushLink();
}
onCloseProgressBar() {
this.revertBulkSmushLink();
}
disableBulkSmushLink() {
if ( ! this.bulkSmushLink ) {
return;
}
this.bulkSmushLink.setAttribute( 'disabled', true );
this.setInnerText( this.bulkSmushLink, __( 'Waiting for Re-check to finish', 'wp-smushit' ) );
}
revertBulkSmushLink() {
if ( ! this.bulkSmushLink ) {
return;
}
this.bulkSmushLink.removeAttribute( 'disabled' );
this.revertInnerText( this.bulkSmushLink );
}
}
( new mediaLibraryScannerOnDashboard() ).showProgressBar().autoSyncStatus();
}() );

View File

@ -0,0 +1,52 @@
import Smush from '../smush/smush';
import SmushProcess from '../common/progressbar';
(function($) {
$(function() {
/** Handle NextGen Gallery smush button click **/
$('body').on('click', '.wp-smush-nextgen-send', function (e) {
// prevent the default action
e.preventDefault();
new Smush($(this), false, 'nextgen');
});
/** Handle NextGen Gallery Bulk smush button click **/
$('body').on('click', '.wp-smush-nextgen-bulk', function (e) {
// prevent the default action
e.preventDefault();
// Remove existing Re-Smush notices.
// TODO: REMOVE re-smush-notice since no longer used.
$('.wp-smush-resmush-notice').remove();
//Check for ids, if there is none (Unsmushed or lossless), don't call smush function
if (
'undefined' === typeof wp_smushit_data ||
(wp_smushit_data.unsmushed.length === 0 &&
wp_smushit_data.resmush.length === 0)
) {
return false;
}
const bulkSmush = new Smush( $(this), true, 'nextgen' );
SmushProcess.setOnCancelCallback( () => {
bulkSmush.cancelAjax();
}).update( 0, bulkSmush.ids.length ).show();
jQuery('.wp-smush-all, .wp-smush-scan').prop('disabled', true);
$('.wp-smush-notice.wp-smush-remaining').hide();
// Run bulk Smush.
bulkSmush.run();
})
.on('click', '.wp-smush-trigger-nextgen-bulk', function(e){
e.preventDefault();
const bulkSmushButton = $('.wp-smush-nextgen-bulk');
if ( bulkSmushButton.length ) {
bulkSmushButton.trigger('click');
SUI.closeNotice( 'wp-smush-ajax-notice' );
}
});
});
}(window.jQuery));

View File

@ -0,0 +1,81 @@
/* global ajaxurl */
/* global wp_smush_msgs */
( function( $ ) {
'use strict';
const s3alert = $( '#wp-smush-s3support-alert' );
/**
* S3 support alert.
*
* @since 3.6.2 Moved from class-s3.php
*/
if ( s3alert.length ) {
const noticeOptions = {
type: 'warning',
icon: 'info',
dismiss: {
show: true,
label: wp_smush_msgs.noticeDismiss,
tooltip: wp_smush_msgs.noticeDismissTooltip,
},
};
window.SUI.openNotice(
'wp-smush-s3support-alert',
s3alert.data( 'message' ),
noticeOptions
);
}
// Dismiss S3 support alert.
s3alert.on( 'click', 'button', () => {
$.post( ajaxurl,
{
action: 'dismiss_s3support_alert',
_ajax_nonce: window.wp_smush_msgs.nonce,
}
);
} );
// Remove API message.
$( '#wp-smush-api-message button.sui-button-icon' ).on( 'click', function( e ) {
e.preventDefault();
const notice = $( '#wp-smush-api-message' );
notice.slideUp( 'slow', function() {
notice.remove();
} );
$.post( ajaxurl,
{
action: 'hide_api_message',
_ajax_nonce: window.wp_smush_msgs.nonce,
}
);
} );
// Hide the notice after a CTA button was clicked
function removeNotice( e ) {
const $notice = $( e.currentTarget ).closest( '.smush-notice' );
$notice.fadeTo( 100, 0, () =>
$notice.slideUp( 100, () => $notice.remove() )
);
}
// Only used for the Dashboard notification for now.
$( '.smush-notice .smush-notice-act' ).on( 'click', ( e ) => {
removeNotice( e );
} );
// Dismiss the update notice.
$( '.wp-smush-update-info' ).on( 'click', '.notice-dismiss', ( e ) => {
e.preventDefault();
removeNotice( e );
$.post( ajaxurl,
{
action: 'dismiss_update_info',
_ajax_nonce: window.wp_smush_msgs.nonce,
}
);
} );
}( jQuery ) );

View File

@ -0,0 +1,378 @@
/* global WP_Smush */
/* global ajaxurl */
/**
* Modals JavaScript code.
*/
( function() {
'use strict';
/**
* Onboarding modal.
*
* @since 3.1
*/
WP_Smush.onboarding = {
membership: 'free', // Assume free by default.
onboardingModal: document.getElementById( 'smush-onboarding-dialog' ),
first_slide: 'usage',
settings: {
first: true,
last: false,
slide: 'usage',
value: false,
},
selection: {
usage: false,
auto: true,
lossy: true,
strip_exif: true,
original: false,
lazy_load: true,
},
contentContainer: document.getElementById( 'smush-onboarding-content' ),
onboardingSlides: [
'usage',
'auto',
'lossy',
'strip_exif',
'original',
'lazy_load',
],
touchX: null,
touchY: null,
recheckImagesLink: '',
/**
* Init module.
*/
init() {
if ( ! this.onboardingModal ) {
return;
}
const dialog = document.getElementById( 'smush-onboarding' );
this.membership = dialog.dataset.type;
this.recheckImagesLink = dialog.dataset.ctaUrl;
if ( 'pro' !== this.membership ) {
this.onboardingSlides = [
'usage',
'auto',
'lossy',
'strip_exif',
'lazy_load',
];
}
if ( 'false' === dialog.dataset.tracking ) {
this.onboardingSlides.pop();
}
this.renderTemplate();
// Skip setup.
const skipButton = this.onboardingModal.querySelector(
'.smush-onboarding-skip-link'
);
if ( skipButton ) {
skipButton.addEventListener( 'click', this.skipSetup.bind( this ) );
}
// Show the modal.
window.SUI.openModal(
'smush-onboarding-dialog',
'wpcontent',
undefined,
false
);
},
/**
* Get swipe coordinates.
*
* @param {Object} e
*/
handleTouchStart( e ) {
const firstTouch = e.touches[ 0 ];
this.touchX = firstTouch.clientX;
this.touchY = firstTouch.clientY;
},
/**
* Process swipe left/right.
*
* @param {Object} e
*/
handleTouchMove( e ) {
if ( ! this.touchX || ! this.touchY ) {
return;
}
const xUp = e.touches[ 0 ].clientX,
yUp = e.touches[ 0 ].clientY,
xDiff = this.touchX - xUp,
yDiff = this.touchY - yUp;
if ( Math.abs( xDiff ) > Math.abs( yDiff ) ) {
if ( xDiff > 0 ) {
if ( false === WP_Smush.onboarding.settings.last ) {
WP_Smush.onboarding.next( null, 'next' );
}
} else if ( false === WP_Smush.onboarding.settings.first ) {
WP_Smush.onboarding.next( null, 'prev' );
}
}
this.touchX = null;
this.touchY = null;
},
/**
* Update the template, register new listeners.
*
* @param {string} directionClass Accepts: fadeInRight, fadeInLeft, none.
*/
renderTemplate( directionClass = 'none' ) {
// Grab the selected value.
const input = this.onboardingModal.querySelector(
'input[type="checkbox"]'
);
if ( input ) {
this.selection[ input.id ] = input.checked;
}
const template = WP_Smush.onboarding.template( 'smush-onboarding' );
const content = template( this.settings );
if ( content ) {
this.contentContainer.innerHTML = content;
if ( 'none' === directionClass ) {
this.contentContainer.classList.add( 'loaded' );
} else {
this.contentContainer.classList.remove( 'loaded' );
this.contentContainer.classList.add( directionClass );
setTimeout( () => {
this.contentContainer.classList.add( 'loaded' );
this.contentContainer.classList.remove(
directionClass
);
}, 600 );
}
}
this.onboardingModal.addEventListener(
'touchstart',
this.handleTouchStart,
false
);
this.onboardingModal.addEventListener(
'touchmove',
this.handleTouchMove,
false
);
this.bindSubmit();
},
/**
* Catch "Finish setup wizard" button click.
*/
bindSubmit() {
const submitButton = this.onboardingModal.querySelector(
'button[type="submit"]'
);
const self = this;
if ( submitButton ) {
submitButton.addEventListener( 'click', function( e ) {
e.preventDefault();
// Because we are not rendering the template, we need to update the last element value.
const input = self.onboardingModal.querySelector(
'input[type="checkbox"]'
);
if ( input ) {
self.selection[ input.id ] = input.checked;
}
const _nonce = document.getElementById(
'smush_quick_setup_nonce'
);
const xhr = new XMLHttpRequest();
xhr.open( 'POST', ajaxurl + '?action=smush_setup', true );
xhr.setRequestHeader(
'Content-type',
'application/x-www-form-urlencoded'
);
xhr.onload = () => {
if ( 200 === xhr.status ) {
self.onFinishingSetup();
} else {
window.console.log(
'Request failed. Returned status of ' +
xhr.status
);
}
};
xhr.send(
'smush_settings=' +
JSON.stringify( self.selection ) +
'&_ajax_nonce=' +
_nonce.value
);
} );
}
},
onFinishingSetup() {
this.onFinish();
this.startRecheckImages();
},
onFinish() {
window.SUI.closeModal();
},
startRecheckImages() {
if ( ! this.recheckImagesLink ) {
return;
}
window.location.href = this.recheckImagesLink;
},
/**
* Handle navigation.
*
* @param {Object} e
* @param {null|string} whereTo
*/
next( e, whereTo = null ) {
const index = this.onboardingSlides.indexOf( this.settings.slide );
let newIndex = 0;
if ( ! whereTo ) {
newIndex =
null !== e && e.classList.contains( 'next' )
? index + 1
: index - 1;
} else {
newIndex = 'next' === whereTo ? index + 1 : index - 1;
}
const directionClass =
null !== e && e.classList.contains( 'next' )
? 'fadeInRight'
: 'fadeInLeft';
this.settings = {
first: 0 === newIndex,
last: newIndex + 1 === this.onboardingSlides.length, // length !== index
slide: this.onboardingSlides[ newIndex ],
value: this.selection[ this.onboardingSlides[ newIndex ] ],
};
this.renderTemplate( directionClass );
},
/**
* Handle circle navigation.
*
* @param {string} target
*/
goTo( target ) {
const newIndex = this.onboardingSlides.indexOf( target );
this.settings = {
first: 0 === newIndex,
last: newIndex + 1 === this.onboardingSlides.length, // length !== index
slide: target,
value: this.selection[ target ],
};
this.renderTemplate();
},
/**
* Skip onboarding experience.
*/
skipSetup() {
const _nonce = document.getElementById( 'smush_quick_setup_nonce' );
const xhr = new XMLHttpRequest();
xhr.open(
'POST',
ajaxurl + '?action=skip_smush_setup&_ajax_nonce=' + _nonce.value
);
xhr.onload = () => {
if ( 200 === xhr.status ) {
this.onSkipSetup();
} else {
window.console.log(
'Request failed. Returned status of ' + xhr.status
);
}
};
xhr.send();
},
onSkipSetup() {
this.onFinish();
},
/**
* Hide new features modal.
* @since 3.7.0
* @since 3.12.2 Add a new parameter redirectUrl
*/
hideUpgradeModal: ( e, button ) => {
e.preventDefault();
button.classList.add( 'wp-smush-link-in-progress' );
const redirectUrl = button?.href;
const xhr = new XMLHttpRequest();
xhr.open( 'POST', ajaxurl + '?action=hide_new_features&_ajax_nonce=' + window.wp_smush_msgs.nonce );
xhr.onload = () => {
window.SUI.closeModal();
button.classList.remove( 'wp-smush-link-in-progress' );
if ( 200 === xhr.status ) {
if ( redirectUrl ) {
window.location.href = redirectUrl;
}
} else {
window.console.log(
'Request failed. Returned status of ' + xhr.status
);
}
};
xhr.send();
},
};
/**
* Template function (underscores based).
*
* @type {Function}
*/
WP_Smush.onboarding.template = _.memoize( ( id ) => {
let compiled;
const options = {
evaluate: /<#([\s\S]+?)#>/g,
interpolate: /{{{([\s\S]+?)}}}/g,
escape: /{{([^}]+?)}}(?!})/g,
variable: 'data',
};
return ( data ) => {
_.templateSettings = options;
compiled =
compiled ||
_.template( document.getElementById( id ).innerHTML );
data.first_slide = WP_Smush.onboarding.first_slide;
return compiled( data );
};
} );
window.addEventListener( 'load', () => WP_Smush.onboarding.init() );
}() );

View File

@ -0,0 +1,13 @@
/**
* Shared UI JS libraries. Use only what we need to keep the vendor file size smaller.
*
* @package
*/
require( '@wpmudev/shared-ui/dist/js/_src/code-snippet' );
require( '@wpmudev/shared-ui/dist/js/_src/modal-dialog' );
require( '@wpmudev/shared-ui/dist/js/_src/notifications' );
require( '@wpmudev/shared-ui/dist/js/_src/select2.full' );
require( '@wpmudev/shared-ui/dist/js/_src/select2' );
require( '@wpmudev/shared-ui/dist/js/_src/tabs' );
require( '@wpmudev/shared-ui/dist/js/_src/upload' ); // Used on lazy load page (since 3.2.2).
require( '@wpmudev/shared-ui/dist/js/_src/reviews' );

View File

@ -0,0 +1,147 @@
/**
* BLOCK: extend image block
*/
const { createHigherOrderComponent } = wp.compose,
{ Fragment } = wp.element,
{ InspectorControls } = wp.blockEditor,
{ PanelBody } = wp.components;
/**
* Transform bytes to human readable format.
*
* @param {number} bytes
* @return {string} Readable size string.
*/
function humanFileSize( bytes ) {
const thresh = 1024,
units = [ 'kB', 'MB', 'GB', 'TB' ];
if ( Math.abs( bytes ) < thresh ) {
return bytes + ' B';
}
let u = -1;
do {
bytes /= thresh;
++u;
} while ( Math.abs( bytes ) >= thresh && u < units.length - 1 );
return bytes.toFixed( 1 ) + ' ' + units[ u ];
}
/**
* Generate Smush stats table.
*
* @param {number} id
* @param {Object} stats
* @return {*} Smush stats.
*/
export function smushStats( id, stats ) {
if ( 'undefined' === typeof stats ) {
return window.smush_vars.strings.gb.select_image;
} else if ( 'string' === typeof stats ) {
return stats;
}
return (
<div
id="smush-stats"
className="sui-smush-media smush-stats-wrapper hidden"
style={ { display: 'block' } }
>
<table className="wp-smush-stats-holder">
<thead>
<tr>
<th className="smush-stats-header">
{ window.smush_vars.strings.gb.size }
</th>
<th className="smush-stats-header">
{ window.smush_vars.strings.gb.savings }
</th>
</tr>
</thead>
<tbody>
{ Object.keys( stats.sizes )
.filter( ( item ) => 0 < stats.sizes[ item ].percent )
.map( ( item, i ) => (
<tr key={ i }>
<td>{ item.toUpperCase() }</td>
<td>
{ humanFileSize(
stats.sizes[ item ].bytes
) }{ ' ' }
( { stats.sizes[ item ].percent }% )
</td>
</tr>
) ) }
</tbody>
</table>
</div>
);
}
/**
* Fetch image data. If image is Smushing, update in 3 seconds.
*
* TODO: this could be optimized not to query so much.
*
* @param {Object} props
*/
export function fetchProps( props ) {
const image = new wp.api.models.Media( { id: props.attributes.id } ),
smushData = props.attributes.smush;
image.fetch( { attribute: 'smush' } ).done( function( img ) {
if ( 'string' === typeof img.smush ) {
props.setAttributes( { smush: img.smush } );
//setTimeout( () => fetch( props ), 3000 );
} else if (
'undefined' !== typeof img.smush &&
( 'undefined' === typeof smushData ||
JSON.stringify( smushData ) !== JSON.stringify( img.smush ) )
) {
props.setAttributes( { smush: img.smush } );
}
} );
}
/**
* Modify the blocks edit component.
* Receives the original block BlockEdit component and returns a new wrapped component.
*/
const smushStatsControl = createHigherOrderComponent( ( BlockEdit ) => {
return ( props ) => {
// If not image block or not selected, return unmodified block.
if (
'core/image' !== props.name ||
! props.isSelected ||
'undefined' === typeof props.attributes.id
) {
return (
<Fragment>
<BlockEdit { ...props } />
</Fragment>
);
}
const smushData = props.attributes.smush;
fetchProps( props );
return (
<Fragment>
<BlockEdit { ...props } />
<InspectorControls>
<PanelBody title={ window.smush_vars.strings.gb.stats }>
{ smushStats( props.attributes.id, smushData ) }
</PanelBody>
</InspectorControls>
</Fragment>
);
};
}, 'withInspectorControl' );
wp.hooks.addFilter(
'editor.BlockEdit',
'wp-smush/smush-data-control',
smushStatsControl
);

View File

@ -0,0 +1,138 @@
/* global WP_Smush */
/* global ajaxurl */
/**
* CDN functionality.
*
* @since 3.0
*/
( function() {
'use strict';
WP_Smush.CDN = {
cdnEnableButton: document.getElementById( 'smush-enable-cdn' ),
cdnDisableButton: document.getElementById( 'smush-cancel-cdn' ),
cdnStatsBox: document.querySelector( '.smush-cdn-stats' ),
init() {
/**
* Handle "Get Started" button click on disabled CDN page.
*/
if ( this.cdnEnableButton ) {
this.cdnEnableButton.addEventListener( 'click', ( e ) => {
e.preventDefault();
e.currentTarget.classList.add( 'sui-button-onload' );
this.toggle_cdn( true );
} );
}
/**
* Handle "Deactivate' button click on CDN page.
*/
if ( this.cdnDisableButton ) {
this.cdnDisableButton.addEventListener( 'click', ( e ) => {
e.preventDefault();
e.currentTarget.classList.add( 'sui-button-onload' );
this.toggle_cdn( false );
} );
}
this.updateStatsBox();
},
/**
* Toggle CDN.
*
* @since 3.0
*
* @param {boolean} enable
*/
toggle_cdn( enable ) {
const nonceField = document.getElementsByName(
'wp_smush_options_nonce'
);
const xhr = new XMLHttpRequest();
xhr.open( 'POST', ajaxurl + '?action=smush_toggle_cdn', true );
xhr.setRequestHeader(
'Content-type',
'application/x-www-form-urlencoded'
);
xhr.onload = () => {
if ( 200 === xhr.status ) {
const res = JSON.parse( xhr.response );
if ( 'undefined' !== typeof res.success && res.success ) {
window.location.search = 'page=smush-cdn';
} else if ( 'undefined' !== typeof res.data.message ) {
WP_Smush.helpers.showErrorNotice( res.data.message );
}
} else {
WP_Smush.helpers.showErrorNotice( 'Request failed. Returned status of ' + xhr.status );
}
};
xhr.send(
'param=' + enable + '&_ajax_nonce=' + nonceField[ 0 ].value
);
},
/**
* Update the CDN stats box in summary meta box. Only fetch new data when on CDN page.
*
* @since 3.0
*/
updateStatsBox() {
if (
'undefined' === typeof this.cdnStatsBox ||
! this.cdnStatsBox
) {
return;
}
// Only fetch the new stats, when user is on CDN page.
if ( ! window.location.search.includes( 'page=smush-cdn' ) ) {
return;
}
this.toggleElements();
const xhr = new XMLHttpRequest();
xhr.open( 'POST', ajaxurl + '?action=get_cdn_stats', true );
xhr.onload = () => {
if ( 200 === xhr.status ) {
const res = JSON.parse( xhr.response );
if ( 'undefined' !== typeof res.success && res.success ) {
this.toggleElements();
} else if ( 'undefined' !== typeof res.data.message ) {
WP_Smush.helpers.showErrorNotice( res.data.message );
}
} else {
WP_Smush.helpers.showErrorNotice( 'Request failed. Returned status of ' + xhr.status );
}
};
xhr.send();
},
/**
* Show/hide elements during status update in the updateStatsBox()
*
* @since 3.1 Moved out from updateStatsBox()
*/
toggleElements() {
const spinner = this.cdnStatsBox.querySelector(
'.sui-icon-loader'
);
const elements = this.cdnStatsBox.querySelectorAll(
'.wp-smush-stats > :not(.sui-icon-loader)'
);
for ( let i = 0; i < elements.length; i++ ) {
elements[ i ].classList.toggle( 'sui-hidden' );
}
spinner.classList.toggle( 'sui-hidden' );
},
};
WP_Smush.CDN.init();
} )();

View File

@ -0,0 +1,208 @@
/* global WP_Smush */
/* global ajaxurl */
/**
* Directory scanner module that will Smush images in the Directory Smush modal.
*
* @since 2.8.1
*
* @param {string|number} totalSteps
* @param {string|number} currentStep
* @return {Object} Scan object.
* @class
*/
const DirectoryScanner = ( totalSteps, currentStep ) => {
totalSteps = parseInt( totalSteps );
currentStep = parseInt( currentStep );
let cancelling = false,
failedItems = 0,
skippedItems = 0;
const obj = {
scan() {
const remainingSteps = totalSteps - currentStep;
if ( currentStep !== 0 ) {
// Scan started on a previous page load.
step( remainingSteps ).fail( this.showScanError );
} else {
jQuery
.post( ajaxurl, {
action: 'directory_smush_start',
_ajax_nonce: window.wp_smush_msgs.nonce
}, () =>
step( remainingSteps ).fail( this.showScanError )
)
.fail( this.showScanError );
}
},
cancel() {
cancelling = true;
return jQuery.post( ajaxurl, {
action: 'directory_smush_cancel',
_ajax_nonce: window.wp_smush_msgs.nonce
} );
},
getProgress() {
if ( cancelling ) {
return 0;
}
// O M G ... Logic at it's finest!
const remainingSteps = totalSteps - currentStep;
return Math.min(
Math.round(
( parseInt( totalSteps - remainingSteps ) * 100 ) /
totalSteps
),
99
);
},
onFinishStep( progress ) {
jQuery( '.wp-smush-progress-dialog .sui-progress-state-text' ).html(
currentStep -
failedItems +
'/' +
totalSteps +
' ' +
window.wp_smush_msgs.progress_smushed
);
WP_Smush.directory.updateProgressBar( progress );
},
onFinish() {
WP_Smush.directory.updateProgressBar( 100 );
window.location.href =
window.wp_smush_msgs.directory_url + '&scan=done';
},
/**
* Displays an error when the scan request fails.
*
* @param {Object} res XHR object.
*/
showScanError( res ) {
const dialog = jQuery( '#wp-smush-progress-dialog' );
// Add the error class to show/hide elements in the dialog.
dialog
.removeClass( 'wp-smush-exceed-limit' )
.addClass( 'wp-smush-scan-error' );
// Add the error status and description to the error message.
dialog
.find( '#smush-scan-error' )
.text( `${ res.status } ${ res.statusText }` );
// Show/hide the 403 error specific instructions.
const forbiddenMessage = dialog.find( '.smush-403-error-message' );
if ( 403 !== res.status ) {
forbiddenMessage.addClass( 'sui-hidden' );
} else {
forbiddenMessage.removeClass( 'sui-hidden' );
}
},
limitReached() {
const dialog = jQuery( '#wp-smush-progress-dialog' );
dialog.addClass( 'wp-smush-exceed-limit' );
dialog
.find( '#cancel-directory-smush' )
.attr( 'data-tooltip', window.wp_smush_msgs.bulk_resume );
dialog
.find( '.sui-box-body .sui-icon-close' )
.removeClass( 'sui-icon-close' )
.addClass( 'sui-icon-play' );
dialog
.find( '#cancel-directory-smush' )
.attr( 'id', 'cancel-directory-smush-disabled' );
},
resume() {
const dialog = jQuery( '#wp-smush-progress-dialog' );
const resume = dialog.find( '#cancel-directory-smush-disabled' );
dialog.removeClass( 'wp-smush-exceed-limit' );
dialog
.find( '.sui-box-body .sui-icon-play' )
.removeClass( 'sui-icon-play' )
.addClass( 'sui-icon-close' );
resume.attr( 'data-tooltip', 'Cancel' );
resume.attr( 'id', 'cancel-directory-smush' );
obj.scan();
},
};
/**
* Execute a scan step recursively
*
* Private to avoid overriding
*
* @param {number} remainingSteps
*/
const step = function( remainingSteps ) {
if ( remainingSteps >= 0 ) {
currentStep = totalSteps - remainingSteps;
return jQuery.post(
ajaxurl,
{
action: 'directory_smush_check_step',
_ajax_nonce: window.wp_smush_msgs.nonce,
step: currentStep,
},
( response ) => {
// We're good - continue on.
if (
'undefined' !== typeof response.success &&
response.success
) {
if (
'undefined' !== typeof response.data &&
'undefined' !== typeof response.data.skipped &&
true === response.data.skipped
) {
skippedItems++;
}
currentStep++;
remainingSteps = remainingSteps - 1;
obj.onFinishStep( obj.getProgress() );
step( remainingSteps ).fail( obj.showScanError );
} else if (
'undefined' !== typeof response.data.error &&
'dir_smush_limit_exceeded' === response.data.error
) {
// Limit reached. Stop.
obj.limitReached();
} else {
// Error? never mind, continue, but count them.
failedItems++;
currentStep++;
remainingSteps = remainingSteps - 1;
obj.onFinishStep( obj.getProgress() );
step( remainingSteps ).fail( obj.showScanError );
}
}
);
}
return jQuery.post(
ajaxurl,
{
action: 'directory_smush_finish',
_ajax_nonce: window.wp_smush_msgs.nonce,
items: totalSteps - ( failedItems + skippedItems ),
failed: failedItems,
skipped: skippedItems,
},
( response ) => obj.onFinish( response )
);
};
return obj;
};
export default DirectoryScanner;

View File

@ -0,0 +1,287 @@
/* global WP_Smush */
/* global ajaxurl */
/**
* Lazy loading functionality.
*
* @since 3.0
*/
( function() {
'use strict';
WP_Smush.Lazyload = {
lazyloadEnableButton: document.getElementById(
'smush-enable-lazyload'
),
lazyloadDisableButton: document.getElementById(
'smush-cancel-lazyload'
),
init() {
const self = this;
/**
* Handle "Activate" button click on disabled Lazy load page.
*/
if ( this.lazyloadEnableButton ) {
this.lazyloadEnableButton.addEventListener( 'click', ( e ) => {
e.preventDefault();
e.currentTarget.classList.add( 'sui-button-onload' );
this.toggle_lazy_load( true );
} );
}
/**
* Handle "Deactivate' button click on Lazy load page.
*/
if ( this.lazyloadDisableButton ) {
this.lazyloadDisableButton.addEventListener( 'click', ( e ) => {
e.preventDefault();
e.currentTarget.classList.add( 'sui-button-onload' );
this.toggle_lazy_load( false );
} );
}
/**
* Handle "Remove icon" button click on Lazy load page.
*
* This removes the image from the upload placeholder.
*
* @since 3.2.2
*/
const removeSpinner = document.getElementById(
'smush-remove-spinner'
);
if ( removeSpinner ) {
removeSpinner.addEventListener( 'click', ( e ) => {
e.preventDefault();
this.removeLoaderIcon();
} );
}
const removePlaceholder = document.getElementById(
'smush-remove-placeholder'
);
if ( removePlaceholder ) {
removePlaceholder.addEventListener( 'click', ( e ) => {
e.preventDefault();
this.removeLoaderIcon( 'placeholder' );
} );
}
/**
* Handle "Remove" icon click.
*
* This removes the select icon from the list (not same as above functions).
*
* @since 3.2.2
*/
const items = document.querySelectorAll( '.smush-ll-remove' );
if ( items && 0 < items.length ) {
items.forEach( function( el ) {
el.addEventListener( 'click', ( e ) => {
e.preventDefault();
e.target.closest( 'li' ).style.display = 'none';
self.remove(
e.target.dataset.id,
e.target.dataset.type
);
} );
} );
}
this.handlePredefinedPlaceholders();
},
/**
* Handle background color changes for the two predefined placeholders.
*
* @since 3.7.1
*/
handlePredefinedPlaceholders() {
const pl1 = document.getElementById( 'placeholder-icon-1' );
if ( pl1 ) {
pl1.addEventListener( 'click', () => this.changeColor( '#F3F3F3' ) );
}
const pl2 = document.getElementById( 'placeholder-icon-2' );
if ( pl2 ) {
pl2.addEventListener( 'click', () => this.changeColor( '#333333' ) );
}
},
/**
* Set color.
*
* @since 3.7.1
* @param {string} color
*/
changeColor( color ) {
document.getElementById( 'smush-color-picker' ).value = color;
document.querySelector( '.sui-colorpicker-hex .sui-colorpicker-value > span > span' ).style.backgroundColor = color;
document.querySelector( '.sui-colorpicker-hex .sui-colorpicker-value > input' ).value = color;
},
/**
* Toggle lazy loading.
*
* @since 3.2.0
*
* @param {string} enable
*/
toggle_lazy_load( enable ) {
const nonceField = document.getElementsByName(
'wp_smush_options_nonce'
);
const xhr = new XMLHttpRequest();
xhr.open(
'POST',
ajaxurl + '?action=smush_toggle_lazy_load',
true
);
xhr.setRequestHeader(
'Content-type',
'application/x-www-form-urlencoded'
);
xhr.onload = () => {
if ( 200 === xhr.status ) {
const res = JSON.parse( xhr.response );
if ( 'undefined' !== typeof res.success && res.success ) {
window.location.search = 'page=smush-lazy-load';
} else if ( 'undefined' !== typeof res.data.message ) {
WP_Smush.helpers.showErrorNotice( res.data.message );
document.querySelector( '.sui-button-onload' ).classList.remove( 'sui-button-onload' );
}
} else {
WP_Smush.helpers.showErrorNotice( 'Request failed. Returned status of ' + xhr.status );
document.querySelector( '.sui-button-onload' ).classList.remove( 'sui-button-onload' );
}
};
xhr.send(
'param=' + enable + '&_ajax_nonce=' + nonceField[ 0 ].value
);
},
/**
* Add lazy load spinner icon.
*
* @since 3.2.2
* @param {string} type Accepts: spinner, placeholder.
*/
addLoaderIcon( type = 'spinner' ) {
let frame;
// If the media frame already exists, reopen it.
if ( frame ) {
frame.open();
return;
}
// Create a new media frame
frame = wp.media( {
title: 'Select or upload an icon',
button: {
text: 'Select icon',
},
multiple: false, // Set to true to allow multiple files to be selected
} );
// When an image is selected in the media frame...
frame.on( 'select', function() {
// Get media attachment details from the frame state
const attachment = frame
.state()
.get( 'selection' )
.first()
.toJSON();
// Send the attachment URL to our custom image input field.
const imageIcon = document.getElementById(
'smush-' + type + '-icon-preview'
);
imageIcon.style.backgroundImage =
'url("' + attachment.url + '")';
imageIcon.style.display = 'block';
// Send the attachment id to our hidden input
document
.getElementById( 'smush-' + type + '-icon-file' )
.setAttribute( 'value', attachment.id );
// Hide the add image link
document.getElementById(
'smush-upload-' + type
).style.display = 'none';
// Unhide the remove image link
const removeDiv = document.getElementById(
'smush-remove-' + type
);
removeDiv.querySelector( 'span' ).innerHTML =
attachment.filename;
removeDiv.style.display = 'block';
} );
// Finally, open the modal on click
frame.open();
},
/**
* Remove lazy load spinner icon.
*
* @since 3.2.2
* @param {string} type Accepts: spinner, placeholder.
*/
removeLoaderIcon: ( type = 'spinner' ) => {
// Clear out the preview image
const imageIcon = document.getElementById(
'smush-' + type + '-icon-preview'
);
imageIcon.style.backgroundImage = '';
imageIcon.style.display = 'none';
// Un-hide the add image link
document.getElementById( 'smush-upload-' + type ).style.display =
'block';
// Hide the delete image link
document.getElementById( 'smush-remove-' + type ).style.display =
'none';
// Delete the image id from the hidden input
document
.getElementById( 'smush-' + type + '-icon-file' )
.setAttribute( 'value', '' );
},
/**
* Remove item.
*
* @param {number} id Image ID.
* @param {string} type Accepts: spinner, placeholder.
*/
remove: ( id, type = 'spinner' ) => {
const nonceField = document.getElementsByName(
'wp_smush_options_nonce'
);
const xhr = new XMLHttpRequest();
xhr.open( 'POST', ajaxurl + '?action=smush_remove_icon', true );
xhr.setRequestHeader(
'Content-type',
'application/x-www-form-urlencoded'
);
xhr.send(
'id=' +
id +
'&type=' +
type +
'&_ajax_nonce=' +
nonceField[ 0 ].value
);
},
};
WP_Smush.Lazyload.init();
} )();

View File

@ -0,0 +1,180 @@
/* global smush_vars */
/* global _ */
/**
* Adds a Smush Now button and displays stats in Media Attachment Details Screen
*/
(function ($, _) {
'use strict';
// Local reference to the WordPress media namespace.
const smushMedia = wp.media,
sharedTemplate =
"<span class='setting smush-stats' data-setting='smush'>" +
"<span class='name'><%= label %></span>" +
"<span class='value'><%= value %></span>" +
'</span>',
template = _.template(sharedTemplate);
/**
* Create the template.
*
* @param {string} smushHTML
* @return {Object} Template object
*/
const prepareTemplate = function (smushHTML) {
/**
* @param {Array} smush_vars.strings Localization strings.
* @param {Object} smush_vars Object from wp_localize_script()
*/
return template({
label: smush_vars.strings.stats_label,
value: smushHTML,
});
};
if (
'undefined' !== typeof smushMedia.view &&
'undefined' !== typeof smushMedia.view.Attachment.Details.TwoColumn
) {
// Local instance of the Attachment Details TwoColumn used in the edit attachment modal view
const smushMediaTwoColumn =
smushMedia.view.Attachment.Details.TwoColumn;
/**
* Add Smush details to attachment.
*
* A similar view to media.view.Attachment.Details
* for use in the Edit Attachment modal.
*
* @see wp-includes/js/media-grid.js
*/
smushMedia.view.Attachment.Details.TwoColumn = smushMediaTwoColumn.extend(
{
initialize() {
smushMediaTwoColumn.prototype.initialize.apply(this, arguments);
this.listenTo(this.model, 'change:smush', this.render);
},
render() {
// Ensure that the main attachment fields are rendered.
smushMedia.view.Attachment.prototype.render.apply(
this,
arguments
);
const smushHTML = this.model.get('smush');
if (typeof smushHTML === 'undefined') {
return this;
}
this.model.fetch();
/**
* Detach the views, append our custom fields, make sure that our data is fully updated
* and re-render the updated view.
*/
this.views.detach();
this.$el
.find('.settings')
.append(prepareTemplate(smushHTML));
this.views.render();
return this;
},
}
);
}
// Local instance of the Attachment Details TwoColumn used in the edit attachment modal view
const smushAttachmentDetails = smushMedia.view.Attachment.Details;
/**
* Add Smush details to attachment.
*/
smushMedia.view.Attachment.Details = smushAttachmentDetails.extend({
initialize() {
smushAttachmentDetails.prototype.initialize.apply(this, arguments);
this.listenTo(this.model, 'change:smush', this.render);
},
render() {
// Ensure that the main attachment fields are rendered.
smushMedia.view.Attachment.prototype.render.apply(this, arguments);
const smushHTML = this.model.get('smush');
if (typeof smushHTML === 'undefined') {
return this;
}
this.model.fetch();
/**
* Detach the views, append our custom fields, make sure that our data is fully updated
* and re-render the updated view.
*/
this.views.detach();
this.$el.append(prepareTemplate(smushHTML));
return this;
},
});
/**
* Create a new MediaLibraryTaxonomyFilter we later will instantiate
*
* @since 3.0
*/
const MediaLibraryTaxonomyFilter = wp.media.view.AttachmentFilters.extend({
id: 'media-attachment-smush-filter',
createFilters() {
this.filters = {
all: {
text: smush_vars.strings.filter_all,
props: { stats: 'all' },
priority: 10,
},
unsmushed: {
text: smush_vars.strings.filter_not_processed,
props: { stats: 'unsmushed' },
priority: 20,
},
excluded: {
text: smush_vars.strings.filter_excl,
props: { stats: 'excluded' },
priority: 30,
},
failed: {
text: smush_vars.strings.filter_failed,
props: { stats: 'failed_processing' },
priority: 40,
},
};
},
});
/**
* Extend and override wp.media.view.AttachmentsBrowser to include our new filter.
*
* @since 3.0
*/
const AttachmentsBrowser = wp.media.view.AttachmentsBrowser;
wp.media.view.AttachmentsBrowser = wp.media.view.AttachmentsBrowser.extend({
createToolbar() {
// Make sure to load the original toolbar
AttachmentsBrowser.prototype.createToolbar.call(this);
this.toolbar.set(
'MediaLibraryTaxonomyFilter',
new MediaLibraryTaxonomyFilter({
controller: this.controller,
model: this.collection.props,
priority: -75,
}).render()
);
},
});
})(jQuery, _);

View File

@ -0,0 +1,45 @@
import MixPanel from "../mixpanel";
class ProductAnalytics {
init() {
this.trackUltraLinks();
}
trackUltraLinks() {
const ultraUpsellLinks = document.querySelectorAll( '.wp-smush-upsell-ultra-compression' );
if ( ! ultraUpsellLinks ) {
return;
}
const getLocation = ( ultraLink ) => {
const locations = {
'settings': 'bulksmush_settings',
'dashboard': 'dash_summary',
'bulk': 'bulksmush_summary',
'directory': 'directory_summary',
'lazy-load': 'lazy_summary',
'cdn': 'cdn_summary',
'webp': 'webp_summary',
};
const locationId = ultraLink.classList.contains( 'wp-smush-ultra-compression-link' ) ? 'settings' : this.getCurrentPageSlug();
return locations[locationId] || 'bulksmush_settings';
}
ultraUpsellLinks.forEach( ( ultraLink ) => {
const eventName = 'ultra_upsell_modal';
ultraLink.addEventListener( 'click', (e) => {
MixPanel.getInstance().track( eventName, {
'Location': getLocation( e.target ),
'Modal Action': 'direct_cta',
});
});
});
}
getCurrentPageSlug(){
const searchParams = new URLSearchParams(document.location.search);
const pageSlug = searchParams.get("page");
return 'smush' === pageSlug ? 'dashboard' : pageSlug.replace( 'smush-', '' );
}
}
( new ProductAnalytics() ).init();

View File

@ -0,0 +1,173 @@
/* global ajaxurl */
/* global wp_smush_msgs */
/* global WP_Smush */
/* global SUI */
( function( $ ) {
'use strict';
/**
* Bulk compress page.
*/
$( 'form#smush-bulk-form' ).on( 'submit', function( e ) {
e.preventDefault();
$( '#save-settings-button' ).addClass( 'sui-button-onload' );
saveSettings( $( this ).serialize(), 'bulk' );
// runReCheck();
} );
/**
* Lazy load page.
*/
$( 'form#smush-lazy-load-form' ).on( 'submit', function( e ) {
e.preventDefault();
$( '#save-settings-button' ).addClass( 'sui-button-onload-text' );
saveSettings( $( this ).serialize(), 'lazy-load' );
} );
/**
* CDN page.
*/
$( 'form#smush-cdn-form' ).on( 'submit', function( e ) {
e.preventDefault();
$( '#save-settings-button' ).addClass( 'sui-button-onload-text' );
saveSettings( $( this ).serialize(), 'cdn' );
} );
/**
* Integrations page.
*/
$( 'form#smush-integrations-form' ).on( 'submit', function( e ) {
e.preventDefault();
$( '#save-settings-button' ).addClass( 'sui-button-onload-text' );
saveSettings( $( this ).serialize(), 'integrations' );
} );
/**
* Settings page.
*/
$( 'form#smush-settings-form' ).on( 'submit', function( e ) {
e.preventDefault();
$( '#save-settings-button' ).addClass( 'sui-button-onload-text' );
saveSettings( $( this ).serialize(), 'settings' );
} );
/**
* Save settings.
*
* @param {string} settings JSON string of settings.
* @param {string} page Settings page.
*/
function saveSettings( settings, page ) {
const xhr = new XMLHttpRequest();
xhr.open( 'POST', ajaxurl + '?action=smush_save_settings', true );
xhr.setRequestHeader(
'Content-type',
'application/x-www-form-urlencoded'
);
xhr.onload = () => {
$( '#save-settings-button' ).removeClass(
'sui-button-onload-text sui-button-onload'
);
if ( 200 === xhr.status ) {
const res = JSON.parse( xhr.response );
if ( 'undefined' !== typeof res.success && res.success ) {
showSuccessNotice( wp_smush_msgs.settingsUpdated );
triggerSavedSmushSettingsEvent( res.data );
} else if ( res.data && res.data.message ) {
WP_Smush.helpers.showErrorNotice( res.data.message );
} else {
WP_Smush.helpers.showErrorNotice( 'Request failed.' );
}
} else {
WP_Smush.helpers.showErrorNotice( 'Request failed. Returned status of ' + xhr.status );
}
};
xhr.send( 'page=' + page + '&' + settings + '&_ajax_nonce=' + wp_smush_msgs.nonce );
}
function triggerSavedSmushSettingsEvent( status ) {
document.dispatchEvent(
new CustomEvent( 'onSavedSmushSettings', {
detail: status
} )
);
}
/**
* Show successful update notice.
*
* @param {string} msg Notice message.
*/
function showSuccessNotice( msg ) {
const noticeMessage = `<p>${ msg }</p>`,
noticeOptions = {
type: 'success',
icon: 'check',
};
SUI.openNotice( 'wp-smush-ajax-notice', noticeMessage, noticeOptions );
const loadingButton = document.querySelector( '.sui-button-onload' );
if ( loadingButton ) {
loadingButton.classList.remove( 'sui-button-onload' );
}
}
/**
* Re-check images from bulk smush and integrations pages.
*/
function runReCheck() {
$( '#save-settings-button' ).addClass( 'sui-button-onload' );
const param = {
action: 'scan_for_resmush',
wp_smush_options_nonce: $( '#wp_smush_options_nonce' ).val(),
type: 'media',
};
// Send ajax, Update Settings, And Check For resmush.
$.post( ajaxurl, $.param( param ) ).done( function() {
$( '#save-settings-button' ).removeClass( 'sui-button-onload' );
} );
}
/**
* Parse remove data change.
*/
$( 'input[name=keep_data]' ).on( 'change', function( e ) {
const otherClass =
'keep_data-true' === e.target.id
? 'keep_data-false'
: 'keep_data-true';
e.target.parentNode.classList.add( 'active' );
document
.getElementById( otherClass )
.parentNode.classList.remove( 'active' );
} );
/**
* Handle auto-detect checkbox toggle, to show/hide highlighting notice.
*/
$( 'input#detection' ).on( 'click', function() {
const noticeDiv = $( '.smush-highlighting-notice' );
const warningDiv = $( '.smush-highlighting-warning' );
// Setting enabled.
if ( $( this ).is( ':checked' ) ) {
// Highlighting is already active and setting not saved.
if ( noticeDiv.length > 0 ) {
noticeDiv.show();
} else {
warningDiv.show();
}
} else {
noticeDiv.hide();
warningDiv.hide();
}
} );
}( jQuery ) );

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,311 @@
/* global WP_Smush */
/* global ajaxurl */
/**
* WebP functionality.
*
* @since 3.8.0
*/
(function () {
'use strict';
WP_Smush.WebP = {
nonceField: document.getElementsByName('wp_smush_options_nonce'),
toggleModuleButton: document.getElementById('smush-toggle-webp-button'),
recheckStatusButton: document.getElementById('smush-webp-recheck'),
recheckStatusLink: document.getElementById('smush-webp-recheck-link'),
showWizardButton: document.getElementById('smush-webp-toggle-wizard'),
init() {
this.maybeShowDeleteAllSuccessNotice();
/**
* Handles the "Deactivate" and "Get Started" buttons on the WebP page.
*/
if (this.toggleModuleButton) {
this.toggleModuleButton.addEventListener('click', (e) =>
this.toggleWebp(e)
);
}
/**
* Handle "RE-CHECK STATUS' button click on WebP page.
*/
if (this.recheckStatusButton) {
this.recheckStatusButton.addEventListener('click', (e) => {
e.preventDefault();
this.recheckStatus();
});
}
/**
* Handle "RE-CHECK STATUS' link click on WebP page.
*/
if (this.recheckStatusLink) {
this.recheckStatusLink.addEventListener('click', (e) => {
e.preventDefault();
this.recheckStatus();
});
}
/**
* Handles the "Delete WebP images" button.
*/
if (document.getElementById('wp-smush-webp-delete-all')) {
document
.getElementById('wp-smush-webp-delete-all')
.addEventListener('click', (e) => this.deleteAll(e));
}
if (this.showWizardButton) {
this.showWizardButton.addEventListener(
'click',
this.toggleWizard
);
}
},
/**
* Toggle WebP module.
*
* @param {Event} e
*/
toggleWebp(e) {
e.preventDefault();
const button = e.currentTarget,
doEnable = 'enable' === button.dataset.action;
button.classList.add('sui-button-onload');
const xhr = new XMLHttpRequest();
xhr.open('POST', ajaxurl + '?action=smush_webp_toggle', true);
xhr.setRequestHeader(
'Content-type',
'application/x-www-form-urlencoded'
);
xhr.onload = () => {
const res = JSON.parse(xhr.response);
if (200 === xhr.status) {
if ('undefined' !== typeof res.success && res.success) {
const scanPromise = this.runScan();
scanPromise.onload = () => {
window.location.href =
window.wp_smush_msgs.localWebpURL;
};
} else if ('undefined' !== typeof res.data.message) {
this.showNotice(res.data.message);
button.classList.remove('sui-button-onload');
}
} else {
let message = window.wp_smush_msgs.generic_ajax_error;
if (res && 'undefined' !== typeof res.data.message) {
message = res.data.message;
}
this.showNotice(message);
button.classList.remove('sui-button-onload');
}
};
xhr.send(
'param=' + doEnable + '&_ajax_nonce=' + this.nonceField[0].value
);
},
/**
* re-check server configuration for WebP.
*/
recheckStatus() {
this.recheckStatusButton.classList.add('sui-button-onload');
const xhr = new XMLHttpRequest();
xhr.open('POST', ajaxurl + '?action=smush_webp_get_status', true);
xhr.setRequestHeader(
'Content-type',
'application/x-www-form-urlencoded'
);
xhr.onload = () => {
this.recheckStatusButton.classList.remove('sui-button-onload');
let message = false;
const res = JSON.parse(xhr.response);
if (200 === xhr.status) {
const isConfigured = res.success ? '1' : '0';
if (
isConfigured !==
this.recheckStatusButton.dataset.isConfigured
) {
// Reload the page when the configuration status changed.
location.reload();
}
} else {
message = window.wp_smush_msgs.generic_ajax_error;
}
if (res && res.data) {
message = res.data;
}
if (message) {
this.showNotice(message);
}
};
xhr.send('_ajax_nonce=' + window.wp_smush_msgs.webp_nonce);
},
deleteAll(e) {
const button = e.currentTarget;
button.classList.add('sui-button-onload');
let message = false;
const xhr = new XMLHttpRequest();
xhr.open('POST', ajaxurl + '?action=smush_webp_delete_all', true);
xhr.setRequestHeader(
'Content-type',
'application/x-www-form-urlencoded'
);
xhr.onload = () => {
const res = JSON.parse(xhr.response);
if (200 === xhr.status) {
if ('undefined' !== typeof res.success && res.success) {
const scanPromise = this.runScan();
scanPromise.onload = () => {
location.search =
location.search + '&notice=webp-deleted';
};
} else {
message = window.wp_smush_msgs.generic_ajax_error;
}
} else {
message = window.wp_smush_msgs.generic_ajax_error;
}
if (res && res.data && res.data.message) {
message = res.data.message;
}
if (message) {
button.classList.remove('sui-button-onload');
const noticeMessage = `<p style="text-align: left;">${message}</p>`;
const noticeOptions = {
type: 'error',
icon: 'info',
autoclose: {
show: false,
},
};
window.SUI.openNotice(
'wp-smush-webp-delete-all-error-notice',
noticeMessage,
noticeOptions
);
}
};
xhr.send('_ajax_nonce=' + this.nonceField[0].value);
},
toggleWizard(e) {
e.currentTarget.classList.add('sui-button-onload');
const xhr = new XMLHttpRequest();
xhr.open(
'GET',
ajaxurl +
'?action=smush_toggle_webp_wizard&_ajax_nonce=' +
window.wp_smush_msgs.webp_nonce,
true
);
xhr.onload = () => location.reload();
xhr.send();
},
/**
* Triggers the scanning of images for updating the images to re-smush.
*
* @since 3.8.0
*/
runScan() {
const xhr = new XMLHttpRequest(),
nonceField = document.getElementsByName(
'wp_smush_options_nonce'
);
xhr.open('POST', ajaxurl + '?action=scan_for_resmush', true);
xhr.setRequestHeader(
'Content-type',
'application/x-www-form-urlencoded'
);
xhr.send('_ajax_nonce=' + nonceField[0].value);
return xhr;
},
/**
* Show message (notice).
*
* @param {string} message
* @param {string} type
*/
showNotice(message, type) {
if ('undefined' === typeof message) {
return;
}
const noticeMessage = `<p>${message}</p>`;
const noticeOptions = {
type: type || 'error',
icon: 'info',
dismiss: {
show: true,
label: window.wp_smush_msgs.noticeDismiss,
tooltip: window.wp_smush_msgs.noticeDismissTooltip,
},
autoclose: {
show: false,
},
};
window.SUI.openNotice(
'wp-smush-ajax-notice',
noticeMessage,
noticeOptions
);
},
/**
* Show delete all webp success notice.
*/
maybeShowDeleteAllSuccessNotice() {
if (!document.getElementById('wp-smush-webp-delete-all-notice')) {
return;
}
const noticeMessage = `<p>${
document.getElementById('wp-smush-webp-delete-all-notice')
.dataset.message
}</p>`;
const noticeOptions = {
type: 'success',
icon: 'check-tick',
dismiss: {
show: true,
},
};
window.SUI.openNotice(
'wp-smush-webp-delete-all-notice',
noticeMessage,
noticeOptions
);
},
};
WP_Smush.WebP.init();
})();

View File

@ -0,0 +1,96 @@
/* global ajaxurl */
/**
* External dependencies
*/
import React from 'react';
import ReactDOM from 'react-dom';
/**
* WordPress dependencies
*/
import domReady from '@wordpress/dom-ready';
/**
* SUI dependencies
*/
import { TutorialsList, TutorialsSlider } from '@wpmudev/shared-tutorials';
import MixPanel from "./mixpanel"
function hideTutorials() {
const xhr = new XMLHttpRequest();
xhr.open( 'POST', ajaxurl + '?action=smush_hide_tutorials', true );
xhr.setRequestHeader( 'Content-type', 'application/x-www-form-urlencoded' );
xhr.onload = () => {
if ( 200 === xhr.status ) {
const noticeMessage = `<p>${ window.wp_smush_msgs.tutorialsRemoved }</p>`,
noticeOptions = {
type: 'success',
icon: 'check',
};
window.SUI.openNotice(
'wp-smush-ajax-notice',
noticeMessage,
noticeOptions
);
}
};
xhr.send( '_ajax_nonce=' + window.wp_smush_msgs.nonce );
}
/**
* Render the "Tutorials List" component.
*
* @since 2.8.5
*/
domReady( function() {
// Tutorials section on Dashboard page.
const tutorialsDiv = document.getElementById( 'smush-dash-tutorials' );
if ( tutorialsDiv ) {
ReactDOM.render(
<TutorialsSlider
category="11228"
title={ window.smush_tutorials.tutorials }
viewAll={ window.smush_tutorials.tutorials_link }
onCloseClick={ hideTutorials }
/>,
tutorialsDiv
);
}
// Tutorials page.
const tutorialsPageBox = document.getElementById( 'smush-box-tutorials' );
if ( tutorialsPageBox ) {
ReactDOM.render(
<TutorialsList
category="11228"
title={ window.smush_tutorials.tutorials }
translate={ window.smush_tutorials.tutorials_strings }
/>,
tutorialsPageBox
);
}
} );
jQuery(function ($) {
$(document).on('click', '#smush-box-tutorials li > [role="link"], #smush-dash-tutorials li > [role="link"]', function () {
const $tutorial = $(this);
const isDashPage = !!$tutorial.closest('#smush-dash-tutorials').length;
const decodeHtml = (html) => {
const txt = document.createElement("textarea");
txt.innerHTML = html;
return txt.value;
};
const title = decodeHtml($tutorial.attr('title'));
MixPanel.getInstance().track('Tutorial Opened', {
'Tutorial Name': title,
'Triggered From': isDashPage ? 'Dashboard' : 'Tutorials Tab'
});
});
});

View File

@ -0,0 +1,170 @@
/* global ajaxurl */
/**
* External dependencies
*/
import assign from 'lodash/assign';
/**
* Wrapper function for ajax calls to WordPress.
*
* @since 3.12.0
*/
function SmushFetcher() {
/**
* Request ajax with a promise.
* Use FormData Object as data if you need to upload file
*
* @param {string} action
* @param {Object|FormData} data
* @param {string} method
* @return {Promise<any>} Request results.
*/
function request(action, data = {}, method = 'POST') {
const args = {
url: ajaxurl,
method,
cache: false
};
if (data instanceof FormData) {
data.append('action', action);
data.append('_ajax_nonce', window.wp_smush_msgs.nonce);
args.contentType = false;
args.processData = false;
} else {
data._ajax_nonce = data._ajax_nonce || window.wp_smush_msgs.nonce;
data.action = action;
}
args.data = data;
return new Promise((resolve, reject) => {
jQuery.ajax(args).done(resolve).fail(reject);
}).then((response) => {
if (typeof response !== 'object') {
response = JSON.parse(response);
}
return response;
}).catch((error) => {
console.error('Error:', error);
});
}
const methods = {
/**
* Manage ajax for background.
*/
background: {
/**
* Start background process.
*/
start: () => {
return request('bulk_smush_start');
},
/**
* Cancel background process.
*/
cancel: () => {
return request('bulk_smush_cancel');
},
/**
* Initial State - Get stats on the first time.
*/
initState: () => {
return request('bulk_smush_get_status');
},
/**
* Get stats.
*/
getStatus: () => {
return request('bulk_smush_get_status');
},
getStats: () => {
return request('bulk_smush_get_global_stats');
}
},
smush: {
/**
* Sync stats.
*/
syncStats: ( data ) => {
data = data || {};
return request('get_stats', data);
},
/**
* Ignore All.
*/
ignoreAll: ( type ) => {
return request('wp_smush_ignore_all_failed_items', {
type: type,
});
},
},
/**
* Manage ajax for other requests
*/
common: {
/**
* Dismiss Notice.
*
* @param {string} dismissId Notification id.
*/
dismissNotice: (dismissId) => {
return request('smush_dismiss_notice', {
key: dismissId
});
},
/**
* Hide the new features modal.
*
* @param {string} modalID Notification id.
*/
hideModal: (modalID) => request('hide_modal', {
modal_id: modalID,
}),
/**
* Custom request.
*
* @param {Object} data
*/
request: (data) => data.action && request(data.action, data),
},
scanMediaLibrary: {
start: ( optimize_on_scan_completed = false ) => {
optimize_on_scan_completed = optimize_on_scan_completed ? 1 : 0;
const _ajax_nonce = window.wp_smushit_data.media_library_scan.nonce;
return request( 'wp_smush_start_background_scan', {
optimize_on_scan_completed,
_ajax_nonce,
} );
},
cancel: () => {
const _ajax_nonce = window.wp_smushit_data.media_library_scan.nonce;
return request( 'wp_smush_cancel_background_scan', {
_ajax_nonce,
} );
},
getScanStatus: () => {
const _ajax_nonce = window.wp_smushit_data.media_library_scan.nonce;
return request( 'wp_smush_get_background_scan_status', {
_ajax_nonce,
} );
},
},
};
assign(this, methods);
}
const SmushAjax = new SmushFetcher();
export default SmushAjax;

View File

@ -0,0 +1,91 @@
import React, {useEffect, useRef, useState} from "react";
import {post} from "../utils/request";
import MediaLibraryScannerModal from "./media-library-scanner-modal";
export default function AjaxMediaLibraryScannerModal(
{
nonce = '',
onScanCompleted = () => false,
onClose = () => false,
focusAfterClose = ''
}
) {
const [inProgress, setInProgress] = useState(false);
const [progress, setProgress] = useState(0);
const [cancelled, setCancelled] = useState(false);
const cancelledRef = useRef();
useEffect(() => {
cancelledRef.current = cancelled;
}, [cancelled]);
function start() {
setInProgress(true);
post('wp_smush_before_scan_library', nonce).then((response) => {
const sliceCount = response?.slice_count;
const slicesList = range(sliceCount, 1);
const parallelRequests = response?.parallel_requests;
handleBatch(slicesList, sliceCount, parallelRequests)
.then(() => {
setTimeout(() => {
onScanCompleted();
}, 1000);
});
});
}
function handleBatch(remainingSlicesList, sliceCount, parallelRequests) {
const batchPromises = [];
const completedSliceCount = Math.max(sliceCount - remainingSlicesList.length, 0);
const batch = remainingSlicesList.splice(0, parallelRequests);
updateProgress(completedSliceCount, sliceCount);
batch.forEach((sliceNumber) => {
batchPromises.push(
post('wp_smush_scan_library_slice', nonce, {slice: sliceNumber})
);
});
return new Promise((resolve) => {
Promise.all(batchPromises)
.then(() => {
if (!cancelledRef.current) {
if (remainingSlicesList.length) {
handleBatch(remainingSlicesList, sliceCount, parallelRequests)
.then(resolve);
} else {
updateProgress(sliceCount, sliceCount);
resolve();
}
}
});
});
}
function range(size, startAt = 0) {
return [...Array(size).keys()].map(i => i + startAt);
}
function cancelScan() {
setCancelled(true);
setInProgress(false);
setProgress(0);
}
function updateProgress(completedSlices, totalSlices) {
const progress = (completedSlices / totalSlices) * 100;
setProgress(progress);
}
return <MediaLibraryScannerModal
inProgress={inProgress}
progress={progress}
onCancel={cancelScan}
focusAfterClose={focusAfterClose}
onClose={onClose}
onStart={start}
/>;
};

View File

@ -0,0 +1,76 @@
import React, {useRef, useState} from "react";
import {post} from "../utils/request";
import MediaLibraryScannerModal from "./media-library-scanner-modal";
export default function BackgroundMediaLibraryScannerModal(
{
nonce = '',
onScanCompleted = () => false,
onClose = () => false,
focusAfterClose = ''
}
) {
const [inProgress, setInProgress] = useState(false);
const [progress, setProgress] = useState(0);
const [cancelled, setCancelled] = useState(false);
const progressTimeoutId = useRef(0);
function start() {
post('wp_smush_start_background_scan', nonce).then(() => {
setInProgress(true);
progressTimeoutId.current = setTimeout(updateProgress, 2000);
});
}
function clearProgressTimeout() {
if (progressTimeoutId.current) {
clearTimeout(progressTimeoutId.current);
}
}
function updateProgress() {
post('wp_smush_get_background_scan_status', nonce).then(response => {
const isCompleted = response?.is_completed;
if (isCompleted) {
clearProgressTimeout();
onScanCompleted();
return;
}
const isCancelled = response?.is_cancelled;
if (isCancelled) {
clearProgressTimeout();
changeStateToCancelled();
return;
}
const totalItems = response?.total_items;
const processedItems = response?.processed_items;
const progress = (processedItems / totalItems) * 100;
setProgress(progress);
progressTimeoutId.current = setTimeout(updateProgress, 1000);
});
}
function cancelScan() {
clearProgressTimeout();
post('wp_smush_cancel_background_scan', nonce)
.then(changeStateToCancelled);
}
function changeStateToCancelled() {
setCancelled(true);
setProgress(0);
setInProgress(false);
}
return <MediaLibraryScannerModal
inProgress={inProgress}
progress={progress}
onCancel={cancelScan}
focusAfterClose={focusAfterClose}
onClose={onClose}
onStart={start}
/>;
};

View File

@ -0,0 +1,49 @@
import React, {useEffect, useRef, useState} from "react";
import Modal from "../common/modal";
import {post} from "../utils/request";
import Button from "../common/button";
import ProgressBar from "../common/progress-bar";
const {__} = wp.i18n;
export default function MediaLibraryScannerModal(
{
inProgress = false,
progress = 0,
onClose = () => false,
onStart = () => false,
onCancel = () => false,
focusAfterClose = ''
}
) {
function content() {
if (inProgress) {
return <>
<ProgressBar progress={progress}/>
<Button id="wp-smush-cancel-media-library-scan"
icon="sui-icon-close"
text={__('Cancel', 'wp-smushit')}
ghost={true}
onClick={onCancel}
/>
</>;
} else {
return <>
<Button id="wp-smush-start-media-library-scan"
icon="sui-icon-play"
text={__('Start', 'wp-smushit')}
onClick={onStart}
/>
</>;
}
}
return <Modal id="wp-smush-media-library-scanner-modal"
title={__('Scan Media Library', 'wp-smushit')}
description={__('Scans the media library to detect items to Smush.', 'wp-smushit')}
onClose={onClose}
focusAfterClose={focusAfterClose}
disableCloseButton={inProgress}>
{content()}
</Modal>;
};

View File

@ -0,0 +1,52 @@
import React, {useState} from "react";
import domReady from '@wordpress/dom-ready';
import ReactDOM from "react-dom";
import Button from "../common/button";
import FloatingNoticePlaceholder from "../common/floating-notice-placeholder";
import {showSuccessNotice} from "../utils/notices";
import AjaxMediaLibraryScannerModal from "./ajax-media-library-scanner-modal";
import BackgroundMediaLibraryScannerModal from "./background-media-library-scanner-modal";
const {__} = wp.i18n;
function MediaLibraryScanner({}) {
const [modalOpen, setModalOpen] = useState(false);
return <>
<FloatingNoticePlaceholder id="wp-smush-media-library-scanner-notice"/>
{modalOpen &&
<BackgroundMediaLibraryScannerModal
focusAfterClose="wp-smush-open-media-library-scanner"
nonce={mediaLibraryScan.nonce}
onScanCompleted={() => {
showSuccessNotice(
'wp-smush-media-library-scanner-notice',
__('Scan completed successfully!', 'wp-smushit'),
true
);
setModalOpen(false);
window.location.reload();
}}
onClose={() => setModalOpen(false)}
/>
}
<Button id="wp-smush-open-media-library-scanner" text={__('Re-Check Images', 'wp-smushit')}
className="wp-smush-scan"
icon="sui-icon-update"
disabled={modalOpen}
onClick={() => setModalOpen(true)}
/>
</>;
}
domReady(function () {
const scannerContainer = document.getElementById('wp-smush-media-library-scanner');
if (scannerContainer) {
ReactDOM.render(
<MediaLibraryScanner/>,
scannerContainer
);
}
});

View File

@ -0,0 +1,70 @@
import React from "react";
import classnames from "classnames";
export default function Button(
{
id = "",
text = "",
color = "",
dashed = false,
icon = '',
loading = false,
ghost = false,
disabled = false,
href = "",
target = "",
className = "",
onClick = () => false,
}
) {
function handleClick(e) {
e.preventDefault();
onClick();
}
function textTag() {
const iconTag = icon ? <span className={icon} aria-hidden="true"/> : "";
return (
<span className={classnames({"sui-loading-text": loading})}>
{iconTag} {text}
</span>
);
}
function loadingIcon() {
return loading
? <span className="sui-icon-loader sui-loading" aria-hidden="true"/>
: "";
}
let HtmlTag, props;
if (href) {
HtmlTag = 'a';
props = {href: href, target: target};
} else {
HtmlTag = 'button';
props = {
disabled: disabled,
onClick: e => handleClick(e)
};
}
const hasText = text && text.trim();
return (
<HtmlTag
{...props}
className={classnames(className, "sui-button-" + color, {
"sui-button-onload": loading,
"sui-button-ghost": ghost,
"sui-button-icon": !hasText,
"sui-button-dashed": dashed,
"sui-button": hasText
})}
id={id}
>
{textTag()}
{loadingIcon()}
</HtmlTag>
);
}

View File

@ -0,0 +1,11 @@
import React from "react";
export default function FloatingNoticePlaceholder({id = ''}) {
return <div className="sui-floating-notices">
<div role="alert"
id={id}
className="sui-notice"
aria-live="assertive">
</div>
</div>;
}

View File

@ -0,0 +1,133 @@
import React, {useEffect} from 'react';
import classnames from 'classnames';
import SUI from 'SUI';
import $ from 'jquery';
const {__} = wp.i18n;
export default function Modal(
{
id = '',
title = '',
description = '',
small = false,
headerActions = false,
focusAfterOpen = '',
focusAfterClose = 'container',
dialogClasses = [],
disableCloseButton = false,
enterDisabled = false,
beforeTitle = false,
onEnter = () => false,
onClose = () => false,
footer,
children
}
) {
useEffect(() => {
SUI.openModal(
id,
focusAfterClose,
focusAfterOpen ? focusAfterOpen : getTitleId(),
false,
false
);
return () => SUI.closeModal();
}, []);
const handleKeyDown = (event) => {
const isTargetInput = $(event.target).is('.sui-modal.sui-active input');
if (isTargetInput && event.keyCode === 13) {
event.preventDefault();
event.stopPropagation();
if (!enterDisabled && onEnter) {
onEnter(event);
}
}
}
function getTitleId() {
return id + '-modal-title';
}
function getHeaderActions() {
const closeButton = getCloseButton();
if (small) {
return closeButton;
} else if (headerActions) {
return headerActions;
} else {
return <div className="sui-actions-right">{closeButton}</div>
}
}
function getCloseButton() {
return <button id={id + '-close-button'}
type="button"
onClick={() => onClose()}
disabled={disableCloseButton}
className={classnames("sui-button-icon", {
'sui-button-float--right': small
})}>
<span className="sui-icon-close sui-md" aria-hidden="true"/>
<span className="sui-screen-reader-text">
{__('Close this dialog window', 'wds')}
</span>
</button>
}
function getDialogClasses() {
return Object.assign({}, {
'sui-modal-sm': small,
'sui-modal-lg': !small
}, dialogClasses);
}
return <div className={classnames('sui-modal', getDialogClasses())}
onKeyDown={e => handleKeyDown(e)}>
<div role="dialog"
id={id}
className={classnames('sui-modal-content', id + '-modal')}
aria-modal="true"
aria-labelledby={id + '-modal-title'}
aria-describedby={id + '-modal-description'}>
<div className="sui-box" role="document">
<div className={classnames('sui-box-header', {
'sui-flatten sui-content-center sui-spacing-top--40': small
})}>
{beforeTitle}
<h3 id={getTitleId()}
className={classnames('sui-box-title', {
'sui-lg': small
})}>
{title}
</h3>
{getHeaderActions()}
</div>
<div className={classnames('sui-box-body', {
'sui-content-center': small
})}>
{description &&
<p className="sui-description"
id={id + '-modal-description'}>
{description}
</p>}
{children}
</div>
{footer && <div className="sui-box-footer">
{footer}
</div>}
</div>
</div>
</div>;
}

View File

@ -0,0 +1,36 @@
import React from "react";
export default function ProgressBar(
{
progress = 0,
stateMessage = ''
}
) {
progress = Math.ceil(progress);
const progressPercentage = progress + "%";
return (
<React.Fragment>
<div className="sui-progress-block">
<div className="sui-progress">
<span className="sui-progress-icon" aria-hidden="true">
<span className="sui-icon-loader sui-loading"/>
</span>
<div className="sui-progress-text">{progressPercentage}</div>
<div className="sui-progress-bar">
<span
style={{
transition: progress === 0 ? false : "transform 0.4s linear 0s",
transformOrigin: "left center",
transform: `translateX(${progress - 100}%)`,
}}
/>
</div>
</div>
</div>
<div className="sui-progress-state">{stateMessage}</div>
</React.Fragment>
);
}

View File

@ -0,0 +1,176 @@
/**
* External dependencies
*/
import React from 'react';
import ReactDOM from 'react-dom';
/**
* WordPress dependencies
*/
import domReady from '@wordpress/dom-ready';
const { __, sprintf } = wp.i18n;
/**
* SUI dependencies
*/
import { Presets } from '@wpmudev/shared-presets';
export const Configs = ({ isWidget }) => {
// TODO: Handle the html interpolation and translation better.
const proDescription = (
<>
{__(
'You can easily apply configs to multiple sites at once via ',
'wp-smushit'
)}
<a
href={window.smushReact.links.hubConfigs}
target="_blank"
rel="noreferrer"
>
{__('the Hub.')}
</a>
</>
);
const closeIcon = __('Close this dialog window', 'wp-smushit'),
cancelButton = __('Cancel', 'wp-smushit');
const lang = {
title: __('Preset Configs', 'wp-smushit'),
upload: __('Upload', 'wp-smushit'),
save: __('Save config', 'wp-smushit'),
loading: __('Updating the config list…', 'wp-smushit'),
emptyNotice: __(
'You dont have any available config. Save preset configurations of Smushs settings, then upload and apply them to your other sites in just a few clicks!',
'wp-smushit'
),
baseDescription: __(
'Use configs to save preset configurations of Smushs settings, then upload and apply them to your other sites in just a few clicks!',
'wp-smushit'
),
proDescription,
syncWithHubText: __(
'Created or updated configs via the Hub?',
'wp-smushit'
),
syncWithHubButton: __('Check again', 'wp-smushit'),
apply: __('Apply', 'wp-smushit'),
download: __('Download', 'wp-smushit'),
edit: __('Name and Description', 'wp-smushit'),
delete: __('Delete', 'wp-smushit'),
notificationDismiss: __('Dismiss notice', 'wp-smushit'),
freeButtonLabel: __('Try The Hub', 'wp-smushit'),
defaultRequestError: sprintf(
/* translators: %s request status */
__(
'Request failed. Status: %s. Please reload the page and try again.',
'wp-smushit'
),
'{status}'
),
uploadActionSuccessMessage: sprintf(
/* translators: %s request status */
__(
'%s config has been uploaded successfully you can now apply it to this site.',
'wp-smushit'
),
'{configName}'
),
uploadWrongPluginErrorMessage: sprintf(
/* translators: %s {pluginName} */
__(
'The uploaded file is not a %s Config. Please make sure the uploaded file is correct.',
'wp-smushit'
),
'{pluginName}'
),
applyAction: {
closeIcon,
cancelButton,
title: __('Apply Config', 'wp-smushit'),
description: sprintf(
/* translators: %s config name */
__(
'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.',
'wp-smushit'
),
'{configName}'
),
actionButton: __('Apply', 'wp-smushit'),
successMessage: sprintf(
/* translators: %s. config name */
__('%s config has been applied successfully.', 'wp-smushit'),
'{configName}'
),
},
deleteAction: {
closeIcon,
cancelButton,
title: __('Delete Configuration File', 'wp-smushit'),
description: sprintf(
/* translators: %s config name */
__(
'Are you sure you want to delete %s? You will no longer be able to apply it to this or other connected sites.',
'wp-smushit'
),
'{configName}'
),
actionButton: __('Delete', 'wp-smushit'),
},
editAction: {
closeIcon,
cancelButton,
nameInput: __('Config name', 'wp-smushit'),
descriptionInput: __('Description', 'wp-smushit'),
emptyNameError: __('The config name is required', 'wp-smushit'),
actionButton: __('Save', 'wp-smushit'),
editTitle: __('Rename Config', 'wp-smushit'),
editDescription: __(
'Change your config name to something recognizable.',
'wp-smushit'
),
createTitle: __('Save Config', 'wp-smushit'),
createDescription: __(
'Save your current settings configuration. Youll be able to then download and apply it to your other sites.',
'wp-smushit'
),
successMessage: sprintf(
/* translators: %s. config name */
__('%s config created successfully.', 'wp-smushit'),
'{configName}'
),
},
settingsLabels: {
bulk_smush: __('Bulk Smush', 'wp-smushit'),
integrations: __('Integrations', 'wp-smushit'),
lazy_load: __('Lazy Load', 'wp-smushit'),
cdn: __('CDN', 'wp-smushit'),
webp_mod: __('Local WebP', 'wp-smushit'),
settings: __('Settings', 'wp-smushit'),
networkwide: __('Subsite Controls', 'wp-smushit'),
},
};
return (
<Presets
isWidget={isWidget}
isPro={window.smushReact.isPro}
isWhitelabel={window.smushReact.hideBranding}
sourceLang={lang}
sourceUrls={window.smushReact.links}
requestsData={window.smushReact.requestsData}
/>
);
};
domReady(function () {
const configsPageBox = document.getElementById('smush-box-configs');
if (configsPageBox) {
ReactDOM.render(<Configs isWidget={false} />, configsPageBox);
}
const configsWidgetBox = document.getElementById('smush-widget-configs');
if (configsWidgetBox) {
ReactDOM.render(<Configs isWidget={true} />, configsWidgetBox);
}
});

View File

@ -0,0 +1,110 @@
/* global ajaxurl */
/**
* External dependencies
*/
import React from 'react';
import ReactDOM from 'react-dom';
/**
* WordPress dependencies
*/
import domReady from '@wordpress/dom-ready';
/**
* Internal dependencies
*/
import StepsBar from '../views/webp/steps-bar';
import StepContent from '../views/webp/step-content';
import FreeContent from '../views/webp/free-content';
import StepFooter from '../views/webp/step-footer';
export const WebpPage = ({ smushData }) => {
const [currentStep, setCurrentStep] = React.useState(
parseInt(smushData.startStep)
);
React.useEffect(() => {
if (2 === currentStep) {
window.SUI.suiCodeSnippet();
}
}, [currentStep]);
const [serverType, setServerType] = React.useState(
smushData.detectedServer
);
const [rulesMethod, setRulesMethod] = React.useState('automatic');
const [rulesError, setRulesError] = React.useState(false);
const makeRequest = (action, verb = 'GET') => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(
verb,
`${ajaxurl}?action=${action}&_ajax_nonce=${smushData.nonce}`,
true
);
xhr.setRequestHeader(
'Content-type',
'application/x-www-form-urlencoded'
);
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.response));
} else {
reject(xhr);
}
};
xhr.onerror = () => reject(xhr);
xhr.send();
});
};
const stepContent = smushData.isPro ? (
<StepContent
currentStep={currentStep}
serverType={serverType}
rulesMethod={rulesMethod}
setRulesMethod={setRulesMethod}
rulesError={rulesError}
setServerType={setServerType}
smushData={smushData}
makeRequest={makeRequest}
/>
) : (
<FreeContent smushData={smushData} />
);
return (
<React.Fragment>
<div className="sui-box-body sui-no-padding">
<div className="sui-row-with-sidenav">
<StepsBar smushData={smushData} currentStep={currentStep} />
{stepContent}
</div>
</div>
{smushData.isPro && (
<StepFooter
currentStep={currentStep}
setCurrentStep={setCurrentStep}
serverType={serverType}
rulesMethod={rulesMethod}
setRulesError={setRulesError}
makeRequest={makeRequest}
/>
)}
</React.Fragment>
);
};
domReady(function () {
const webpPageBox = document.getElementById('smush-box-webp-wizard');
if (webpPageBox) {
ReactDOM.render(
<WebpPage smushData={window.smushReact} />,
webpPageBox
);
}
});

View File

@ -0,0 +1,35 @@
export function showSuccessNotice(id, message, dismissible = true) {
return showNotice(id, message, 'success', dismissible);
}
export function showErrorNotice(id, message, dismissible = true) {
return showNotice(id, message, 'error', dismissible);
}
export function showInfoNotice(id, message, dismissible = true) {
return showNotice(id, message, 'info', dismissible);
}
export function showWarningNotice(id, message, dismissible = true) {
return showNotice(id, message, 'warning', dismissible);
}
export function closeNotice(id) {
SUI.closeNotice(id);
}
export function showNotice(id, message, type = 'success', dismissible = true) {
const icons = {
error: 'warning-alert',
info: 'info',
warning: 'warning-alert',
success: 'check-tick'
};
SUI.closeNotice(id);
SUI.openNotice(id, '<p>' + message + '</p>', {
type: type,
icon: icons[type],
dismiss: {show: dismissible}
});
}

View File

@ -0,0 +1,23 @@
import $ from 'jquery';
import ajaxUrl from 'ajaxUrl';
export function post(action, nonce, data = {}) {
return new Promise(function (resolve, reject) {
const request = Object.assign({}, {
action: action,
_ajax_nonce: nonce
}, data);
$.post(ajaxUrl, request)
.done((response) => {
if (response.success) {
resolve(
response?.data
);
} else {
reject(response?.data?.message);
}
})
.fail(() => reject());
});
}

View File

@ -0,0 +1,86 @@
/**
* External dependencies
*/
import React from 'react';
/**
* WordPress dependencies
*/
const { __ } = wp.i18n;
export default ({ smushData }) => {
return (
<div className="sui-box-body">
<div className="sui-message">
<img
className="sui-image"
src={smushData.urls.freeImg}
srcset={smushData.urls.freeImg2x + ' 2x'}
alt={__('Smush WebP', 'wp-smushit')}
/>
<div className="sui-message-content">
<p>
{__(
'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.',
'wp-smushit'
)}
</p>
<ol className="sui-upsell-list">
<li>
<span
className="sui-icon-check sui-sm"
aria-hidden="true"
/>
{__(
'Add or automatically apply the rules to enable Local WebP feature.',
'wp-smushit'
)}
</li>
<li>
<span
className="sui-icon-check sui-sm"
aria-hidden="true"
/>
{__(
'Fix “Serve images in next-gen format" Google PageSpeed recommendation.',
'wp-smushit'
)}
</li>
<li>
<span
className="sui-icon-check sui-sm"
aria-hidden="true"
/>
{__(
'Serve WebP version of images in the browsers that support it and fall back to JPEGs and PNGs for non supported browsers.',
'wp-smushit'
)}
</li>
</ol>
<p className="sui-margin-top">
<a
href={smushData.urls.upsell}
className="sui-button sui-button-purple"
style={{ marginRight: '30px' }}
target="_blank"
rel="noreferrer"
>
{__('UNLOCK WEBP WITH PRO', 'wp-smushit')}
</a>
<a
href={smushData.urls.webpDoc}
style={{ color: '#8D00B1' }}
target="_blank"
rel="noreferrer"
>
{__('Learn more', 'wp-smushit')}
</a>
</p>
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,441 @@
/**
* External dependencies
*/
import React from 'react';
/**
* WordPress dependencies
*/
const { __, sprintf } = wp.i18n;
export default ({
currentStep,
serverType,
rulesMethod,
setRulesMethod,
setServerType,
rulesError,
smushData,
makeRequest,
}) => {
const stepsHeading = {
1: {
title: __('Choose Server Type', 'wp-smushit'),
description: __(
'Choose your server type. If you dont know this, please contact your hosting provider.',
'wp-smushit'
),
},
2: {
title: __('Add Rules', 'wp-smushit'),
description:
'apache' === serverType
? __(
'Smush can automatically apply WebP conversion rules for Apache servers by writing to your .htaccess file. Alternatively, switch to Manual to apply these rules yourself.',
'wp-smushit'
)
: __(
'The following configurations are for NGINX servers. If you do not have access to your NGINX config files you will need to contact your hosting provider to make these changes.',
'wp-smushit'
),
},
3: {
title: __('Finish Setup', 'wp-smushit'),
description: __(
'The rules have been applied successfully.',
'wp-smushit'
),
},
};
const getTopNotice = () => {
if (1 === currentStep && smushData.isS3Enabled) {
return (
<div className="sui-notice sui-notice-warning">
<div className="sui-notice-content">
<div className="sui-notice-message">
<span
className="sui-notice-icon sui-icon-info sui-md"
aria-hidden="true"
></span>
<p>
{__(
'We noticed the Amazon S3 Integration is enabled. Offloaded images will not be served in WebP format, but Smush will create local WebP copies of all images. If this is undesirable, you can quit the setup.',
'wp-smushit'
)}
</p>
</div>
</div>
</div>
);
}
if (2 === currentStep) {
return (
<div
role="alert"
className="sui-notice sui-notice-warning"
aria-live="assertive"
style={rulesError ? { display: 'block' } : {}}
>
{rulesError && (
<div className="sui-notice-content">
<div className="sui-notice-message">
<span
className="sui-notice-icon sui-icon-info sui-md"
aria-hidden="true"
></span>
<p
dangerouslySetInnerHTML={{
__html: rulesError,
}}
/>
</div>
</div>
)}
</div>
);
}
if (smushData.isWpmudevHost) {
const message = !smushData.isWhitelabel
? __(
'Since your site is hosted with WPMU DEV, we already have done the configurations steps for you. The only step for you would be to create WebP images below.',
'wp-smushit'
)
: __(
'WebP conversion is active and working well. Your hosting has automatically pre-configured the conversion for you. The only step for you would be to create WebP images below.',
'wp-smushit'
);
return (
<div className="sui-notice sui-notice-info">
<div className="sui-notice-content">
<div className="sui-notice-message">
<span
className="sui-notice-icon sui-icon-info sui-md"
aria-hidden="true"
></span>
<p>{message}</p>
</div>
</div>
</div>
);
}
};
const getStepContent = () => {
if (1 === currentStep) {
return (
<React.Fragment>
<div className="sui-box-selectors">
<ul>
<li>
<label
htmlFor="smush-wizard-server-type-apache"
className="sui-box-selector"
>
<input
id="smush-wizard-server-type-apache"
type="radio"
value="apache"
checked={'apache' === serverType}
onChange={(e) =>
setServerType(e.currentTarget.value)
}
/>
<span>{__('Apache', 'wp-smushit')}</span>
</label>
</li>
<li>
<label
htmlFor="smush-wizard-server-type-nginx"
className="sui-box-selector"
>
<input
id="smush-wizard-server-type-nginx"
type="radio"
value="nginx"
checked={'nginx' === serverType}
onChange={(e) =>
setServerType(e.currentTarget.value)
}
/>
<span>{__('NGINX', 'wp-smushit')}</span>
</label>
</li>
</ul>
</div>
<div className="sui-notice" style={{ textAlign: 'left' }}>
<div className="sui-notice-content">
<div className="sui-notice-message">
<span
className="sui-notice-icon sui-icon-info sui-md"
aria-hidden="true"
></span>
<p>
{sprintf(
/* translators: server type */
__(
"We've automatically detected your server type is %s. If this is incorrect, manually select your server type to generate the relevant rules and instructions.",
'wp-smushit'
),
'nginx' === smushData.detectedServer
? 'NGINX'
: 'Apache / LiteSpeed'
)}
</p>
</div>
</div>
</div>
</React.Fragment>
);
}
if (2 === currentStep) {
if ('nginx' === serverType) {
return (
<div className="smush-wizard-rules-wrapper">
<ol className="sui-description">
<li>
{__(
'Insert the following in the server context of your configuration file (usually found in /etc/nginx/sites-available). “The server context” refers to the part of the configuration that starts with “server {” and ends with the matching “}”.',
'wp-smushit'
)}
</li>
<li>
{__(
'Copy the generated code found below and paste it inside your http or server blocks.',
'wp-smushit'
)}
</li>
</ol>
<pre
className="sui-code-snippet"
style={{ marginLeft: '12px' }}
>
{smushData.nginxRules}
</pre>
<ol className="sui-description" start="3">
<li>{__('Reload NGINX.', 'wp-smushit')}</li>
</ol>
<p className="sui-description">
{__('Still having trouble?', 'wp_smushit')}{' '}
<a
href={smushData.urls.support}
target="_blank"
rel="noreferrer"
>
{__('Get Support.', 'wp_smushit')}
</a>
</p>
</div>
);
}
// TODO: The non-selected button isn't focusable this way. Why arrows don't workkkkkkk?
return (
<div className="sui-side-tabs sui-tabs">
<div role="tablist" className="sui-tabs-menu">
<button
type="button"
role="tab"
id="smush-tab-automatic"
className={
'sui-tab-item' +
('automatic' === rulesMethod ? ' active' : '')
}
aria-controls="smush-tab-content-automatic"
aria-selected={'automatic' === rulesMethod}
onClick={() => setRulesMethod('automatic')}
tabIndex={'automatic' === rulesMethod ? '0' : '-1'}
>
{__('Automatic', 'wp-smushit')}
</button>
<button
type="button"
role="tab"
id="smush-tab-manual"
className={
'sui-tab-item' +
('manual' === rulesMethod ? ' active' : '')
}
aria-controls="smush-tab-content-manual"
aria-selected={'manual' === rulesMethod}
onClick={() => setRulesMethod('manual')}
tabIndex={'manual' === rulesMethod ? '0' : '-1'}
>
{__('Manual', 'wp-smushit')}
</button>
</div>
<div className="sui-tabs-content">
<div
role="tabpanel"
tabIndex="0"
id="smush-tab-content-automatic"
className={
'sui-tab-content' +
('automatic' === rulesMethod ? ' active' : '')
}
aria-labelledby="smush-tab-automatic"
hidden={'automatic' !== rulesMethod}
>
<p
className="sui-description"
style={{ marginTop: '30px' }}
>
{__(
'Please note: Some servers have both Apache and NGINX software which may not begin serving WebP images after applying the .htaccess rules. If errors occur after applying the rules, we recommend adding NGINX rules manually.',
'wp-smushit'
)}
</p>
</div>
<div
role="tabpanel"
tabIndex="0"
id="smush-tab-content-manual"
className={
'sui-tab-content' +
('manual' === rulesMethod ? ' active' : '')
}
aria-labelledby="smush-tab-manual"
hidden={'manual' !== rulesMethod}
>
<p className="sui-description">
{__(
'If you are unable to get the automated method working, follow these steps:',
'wp-smushit'
)}
</p>
<div className="smush-wizard-rules-wrapper">
<ol className="sui-description">
<li>
{__(
'Copy the generated code below and paste it at the top of your .htaccess file (before any existing code) in the root directory.',
'wp-smushit'
)}
</li>
</ol>
<pre
className="sui-code-snippet"
style={{ marginLeft: '12px' }}
>
{smushData.apacheRules}
</pre>
<ol className="sui-description" start="2">
<li>
{__(
"Next, click Check Status button below to see if it's working.",
'wp-smushit'
)}
</li>
</ol>
<h5
className="sui-settings-label"
style={{
marginTop: '30px',
fontSize: '13px',
color: '#333333',
}}
>
{__('Troubleshooting', 'wp-smushit')}
</h5>
<p className="sui-description">
{__(
'If .htaccess does not work, and you have access to vhosts.conf or httpd.conf, try this:',
'wp-smushit'
)}
</p>
<ol className="sui-description">
<li>
{__(
'Look for your site in the file and find the line that starts with <Directory> - add the code above that line and into that section and save the file.',
'wp-smushit'
)}
</li>
<li>
{__('Reload Apache.', 'wp-smushit')}
</li>
<li>
{__(
"If you don't know where those files are, or you aren't able to reload Apache, you would need to consult with your hosting provider or a system administrator who has access to change the configuration of your server.",
'wp-smushit'
)}
</li>
</ol>
<p className="sui-description">
{__('Still having trouble?', 'wp_smushit')}{' '}
<a
href={smushData.urls.support}
target="_blank"
rel="noreferrer"
>
{__('Get Support.', 'wp_smushit')}
</a>
</p>
</div>
</div>
</div>
</div>
);
}
const hideWizard = (e) => {
e.preventDefault();
makeRequest('smush_toggle_webp_wizard').then(() => {
location.href = smushData.urls.bulkPage;
});
};
return (
<React.Fragment>
<p style={{ marginBottom: 0 }}><b>{__('Convert Images to WebP', 'wp-smushit')}</b></p>
<p className="sui-description" dangerouslySetInnerHTML={ { __html: smushData.thirdStepMsg } } />
{!smushData.isMultisite && (
<p>
<a href={smushData.urls.bulkPage} onClick={hideWizard}>
{__('Convert now', 'wp-smushit')}
</a>
</p>
)}
</React.Fragment>
);
};
const stepIndicatorText = sprintf(
/* translators: currentStep/totalSteps indicator */
__('Step %s', 'wp-smushit'),
currentStep + '/3'
);
return (
<div
className={`smush-wizard-steps-content-wrapper smush-wizard-step-${currentStep}`}
>
{getTopNotice()}
<div className="smush-wizard-steps-content">
<span className="smush-step-indicator">
{stepIndicatorText}
</span>
<h2>{stepsHeading[currentStep].title}</h2>
<p className="sui-description">
{stepsHeading[currentStep].description}
</p>
{getStepContent()}
</div>
</div>
);
};

View File

@ -0,0 +1,180 @@
/**
* External dependencies
*/
import React from 'react';
/**
* WordPress dependencies
*/
const { __ } = wp.i18n;
export default ({
currentStep,
setCurrentStep,
serverType,
rulesMethod,
setRulesError,
makeRequest,
}) => {
const genericRequestError = __(
'Something went wrong with the request.',
'wp-smushit'
);
const checkStatus = () => {
setRulesError(false);
makeRequest('smush_webp_get_status')
.then((res) => {
if (res.success) {
setCurrentStep(currentStep + 1);
} else {
setRulesError(res.data);
}
})
.catch(() => setRulesError(genericRequestError));
};
const applyRules = () => {
setRulesError(false);
makeRequest('smush_webp_apply_htaccess_rules')
.then((res) => {
if (res.success) {
return checkStatus();
}
setRulesError(res.data);
})
.catch(() => setRulesError(genericRequestError));
};
const hideWizard = (e) => {
e.currentTarget.classList.add(
'sui-button-onload',
'sui-button-onload-text'
);
makeRequest('smush_toggle_webp_wizard').then(() => location.reload());
};
// Markup stuff.
let buttonsLeft;
const quitButton = (
<button
type="button"
className="sui-button sui-button-ghost"
onClick={hideWizard}
>
<span className="sui-loading-text">
<span className="sui-icon-logout" aria-hidden="true"></span>
<span className="sui-hidden-xs">
{__('Quit setup', 'wp-smushit')}
</span>
<span className="sui-hidden-sm sui-hidden-md sui-hidden-lg">
{__('Quit', 'wp-smushit')}
</span>
</span>
<span
className="sui-icon-loader sui-loading"
aria-hidden="true"
></span>
</button>
);
if (1 !== currentStep) {
buttonsLeft = (
<button
type="button"
className="sui-button sui-button-compound sui-button-ghost"
onClick={() => setCurrentStep(currentStep - 1)}
>
<span className="sui-compound-desktop" aria-hidden="true">
<span className="sui-icon-arrow-left"></span>
{__('Previous', 'wp-smushit')}
</span>
<span className="sui-compound-mobile" aria-hidden="true">
<span className="sui-icon-arrow-left"></span>
</span>
<span className="sui-screen-reader-text">
{__('Previous', 'wp-smushit')}
</span>
</button>
);
}
const getButtonsRight = () => {
if (1 === currentStep) {
return (
<button
type="button"
className="sui-button sui-button-blue sui-button-icon-right"
onClick={() => setCurrentStep(currentStep + 1)}
>
{__('Next', 'wp-smushit')}
<span
className="sui-icon-arrow-right"
aria-hidden="true"
></span>
</button>
);
}
if (2 === currentStep) {
if ('apache' === serverType && 'automatic' === rulesMethod) {
return (
<button
type="button"
className="sui-button sui-button-blue"
onClick={applyRules}
>
{__('Apply rules', 'wp-smushit')}
</button>
);
}
return (
<button
type="button"
className="sui-button sui-button-blue"
onClick={checkStatus}
>
{__('Check status', 'wp-smushit')}
</button>
);
}
return (
<button
type="button"
className="sui-button sui-button-blue"
onClick={hideWizard}
>
<span className="sui-button-text-default">
{__('Finish', 'wp-smushit')}
</span>
<span className="sui-button-text-onload">
<span
className="sui-icon-loader sui-loading"
aria-hidden="true"
></span>
{__('Finishing setup…', 'wp-smushit')}
</span>
</button>
);
};
return (
<div className="sui-box-footer">
<div className="sui-actions-left">
{quitButton}
{buttonsLeft}
</div>
<div className="sui-actions-right">{getButtonsRight()}</div>
</div>
);
};

View File

@ -0,0 +1,114 @@
/**
* External dependencies
*/
import React from 'react';
/**
* WordPress dependencies
*/
const { __ } = wp.i18n;
export default ({ currentStep, smushData }) => {
const getStepClass = (step) => {
const stepClass = 'smush-wizard-bar-step';
if (!smushData.isPro) {
return stepClass + ' disabled';
}
if (step > currentStep) {
return stepClass;
}
return (
stepClass +
(step === currentStep ? ' current' : ' sui-tooltip done')
);
};
const getStepNumber = (step) => {
return currentStep > step ? (
<span className="sui-icon-check" aria-hidden="true"></span>
) : (
step
);
};
const steps = [
{ number: 1, title: __('Server Type', 'wp-smushit') },
{ number: 2, title: __('Add Rules', 'wp-smushit') },
{ number: 3, title: __('Finish Setup', 'wp-smushit') },
];
return (
<div className="sui-sidenav">
<span className="smush-wizard-bar-subtitle">
{__('Setup', 'wp-smushit')}
</span>
<div className="smush-sidenav-title">
<h4>{__('Local WebP', 'wp-smushit')}</h4>
{!smushData.isPro && (
<span className="sui-tag sui-tag-pro">
{__('Pro', 'wp-smushit')}
</span>
)}
</div>
<div className="smush-wizard-steps-container">
<svg
className="smush-svg-mobile"
focusable="false"
aria-hidden="true"
>
<line
x1="0"
x2="50%"
stroke={1 !== currentStep ? '#1ABC9C' : '#E6E6E6'}
/>
<line
x1="50%"
x2="100%"
stroke={3 === currentStep ? '#1ABC9C' : '#E6E6E6'}
/>
</svg>
<ul>
{steps.map((step) => (
<React.Fragment key={step.number}>
<li
className={getStepClass(step.number)}
data-tooltip={__(
'This stage is already completed.',
'wp-smushit'
)}
>
<div className="smush-wizard-bar-step-number">
{getStepNumber(step.number)}
</div>
{step.title}
</li>
{3 !== step.number && (
<svg
data={step.number}
data2={currentStep}
className="smush-svg-desktop"
focusable="false"
aria-hidden="true"
>
<line
y1="0"
y2="40px"
stroke={
step.number < currentStep
? '#1ABC9C'
: '#E6E6E6'
}
/>
</svg>
)}
</React.Fragment>
))}
</ul>
</div>
</div>
);
};

View File

@ -0,0 +1,50 @@
@include body-class(true) {
&.sui-color-accessible {
.smush-final-log .smush-bulk-error-row {
box-shadow: inset 2px 0 0 0 $accessible-dark;
.smush-bulk-image-data:before {
color: $accessible-dark;
}
}
// Bulk Smush Fancy Tree
ul.fancytree-container {
.fancytree-selected {
background-color: #F8F8F8;
span.fancytree-checkbox {
border: 1px solid $accessible-dark;
background-color: $accessible-dark;
}
}
span.fancytree-expander:before,
span.fancytree-icon:before,
span.fancytree-title {
color: $accessible-dark;
}
}
// CDN
.smush-filename-extension {
background-color: $accessible-dark;
}
// Check images button.
.sui-button {
&.smush-button-check-success:before {
color: $accessible-light;
}
}
// Smush submit note.
.smush-submit-note {
color: $accessible-dark;
}
// Hightlight lazyload spinner.
.sui-lazyload .sui-box-selector [name="animation[spinner-icon]"]:checked+span {
background-color: rgba(220,220,222, 0.7)!important;
}
}
}
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
.sui-wrap .sui-toggle-slider {
-ms-high-contrast-adjust: none;
}
}

View File

@ -0,0 +1,59 @@
// This needs to be here.
@import "modules/variables";
// Share UI styles
@import "~@wpmudev/shared-ui/scss/functions";
@import "~@wpmudev/shared-ui/scss/colors";
@import "~@wpmudev/shared-ui/scss/variables";
$google-fonts-url: 'https://fonts.bunny.net/css?family=Roboto:400,500,700';
@import "~@wpmudev/shared-ui/scss/mixins";
@import "~@wpmudev/shared-ui/scss/accessibility";
@import "~@wpmudev/shared-ui/scss/animations";
@import "~@wpmudev/shared-ui/scss/typography";
@import "~@wpmudev/shared-ui/scss/icons";
@import "~@wpmudev/shared-ui/scss/buttons";
@import "~@wpmudev/shared-ui/scss/toggles";
@import "~@wpmudev/shared-ui/scss/boxes";
@import "~@wpmudev/shared-ui/scss/box-settings";
@import "~@wpmudev/shared-ui/scss/layout";
@import "~@wpmudev/shared-ui/scss/notifications";
@import "~@wpmudev/shared-ui/scss/header";
@import "~@wpmudev/shared-ui/scss/summary";
@import "~@wpmudev/shared-ui/scss/list";
@import "~@wpmudev/shared-ui/scss/tooltips";
@import "~@wpmudev/shared-ui/scss/select2";
@import "~@wpmudev/shared-ui/scss/tags";
@import "~@wpmudev/shared-ui/scss/forms";
@import "~@wpmudev/shared-ui/scss/radio-checkbox";
@import "~@wpmudev/shared-ui/scss/tabs";
@import "~@wpmudev/shared-ui/scss/sidenav";
@import "~@wpmudev/shared-ui/scss/dropdowns";
@import "~@wpmudev/shared-ui/scss/scores";
@import "~@wpmudev/shared-ui/scss/footer";
@import "~@wpmudev/shared-ui/scss/progress-bars";
@import "~@wpmudev/shared-ui/scss/modals";
@import "~@wpmudev/shared-ui/scss/utility";
@import "~@wpmudev/shared-ui/scss/wp-admin-notices";
@import "~@wpmudev/shared-ui/scss/tables";
@import "~@wpmudev/shared-ui/scss/accordions";
// Used on lazy loading page (since 3.2.2).
@import "~@wpmudev/shared-ui/scss/box-selectors";
@import "~@wpmudev/shared-ui/scss/upload";
@import "~@wpmudev/shared-ui/scss/_colorpickers.scss";
// Upgrade page (since 3.2.3).
@import "~@wpmudev/shared-ui/scss/upgrade-page";
@import "~@wpmudev/shared-ui/scss/reviews";
// Used on WebP page (since 3.8.0).
@import "~@wpmudev/shared-ui/scss/_code-snippet.scss";
// Upsells (since 3.9.1).
@import "~@wpmudev/shared-ui/scss/upsells";
// App styles
@import "modules/admin";
@import "modules/directory-smush";
@import "modules/cdn";
@import "modules/webp";
// SUI Color Accessibility
@import "~@wpmudev/shared-ui/scss/color-accessibility";
@import "accessibility/color-accessibility";

View File

@ -0,0 +1,597 @@
/**
* Common styles that are used on all the WP pages in the backend
*/
@import "modules/media";
.sui-wrap .smush-upsell-link,
.sui-wrap a.smush-upsell-link {
color: $purple;
> span:before {
color: $purple;
}
&:hover:not(.sui-button),
&:focus:not(.sui-button),
&:active:not(.sui-button) {
color: #64007e;
> span:before {
color: #64007e;
}
}
}
/**
* Media details (grid layout)
* @since 3.4.0
*/
.attachment-info .smush-stats .value {
display: flex;
flex-wrap: wrap;
.smush-status {
margin: 0 0 10px;
flex-basis: 100%;
font-size: 12px;
line-height: 1.33333;
}
.smush-status-links {
width: 100%;
}
span.sui-tooltip {
float: none;
}
a {
margin-left: 5px;
}
a:first-of-type {
margin-left: 0;
margin-right: 5px;
}
}
.attachment-info .smush-status-links,
.column-smushit .smush-status-links {
color: #ddd;
}
.column-smushit .smush-status-links > a {
box-shadow: none;
outline: none;
}
.wp-smush-progress {
padding-left: 25px;
margin: 0;
background-size: 17px 17px;
visibility: visible;
vertical-align: initial !important; /* prevent from jumping on a line */
display: inline;
color: #32373c;
cursor: default;
}
// Fix grid view links.
.attachment-details .setting span.wp-smush-progress {
width: auto;
line-height: 0;
margin-right: 5px;
}
/** Settings Page **/
.smush-status.fail {
color: #dd3d36;
}
.smush-status.success {
color: #0074a2;
}
.smush-status.error {
color: red;
}
#wpbody-content .wp-smush-error {
color: red;
}
.wp-smush-action[disabled] {
opacity: 0.6;
}
#post-body-content .smush-status {
margin: 4px 0;
}
.attachment-info .wp-smush-error-message {
margin: 0 0 1em;
}
.smush-stats-wrapper .row {
padding: 8px 0;
}
.smush-stats-wrapper .row:first-child {
padding-top: 0;
}
.smush-stats-wrapper td, .smush-stats-wrapper th {
font-size: 11px;
}
.smush-skipped .dashicons-editor-help {
margin-top: -2px;
margin-left: 5px;
}
.smush-skipped {
a:focus { box-shadow: 0 0 black; }
.sui-tag.sui-tag-purple {
min-height: 18px;
padding: 2px 10px;
font-size: 10px;
line-height: 12px;
font-weight: 700;
background-color: #8d00b1;
color: #fff;
border: 2px solid transparent;
border-radius: 13px;
}
}
/** Help Tip **/
.ui-tooltip-content {
font-size: 12px;
}
/** All Smushed **/
.wp-smush-notice {
background-color: #D1F1EA;
border-radius: 5px;
color: #333333;
font-family: 'Roboto', sans-serif;
font-size: 15px;
line-height: 30px;
margin-bottom: 30px;
padding: 15px 30px;
letter-spacing: -0.015em;
}
div.smush-notice-cta a.smush-notice-act.button-primary {
padding: 3px 23px;
background-color: #00B0DB;
box-shadow: none;
border-radius: 4px;
border: none;
text-shadow: none;
font-weight: normal;
-webkit-font-smoothing: antialiased;
&:hover {
border: none;
}
}
a.wp-smush-resize-enable:hover,
a.wp-smush-lossy-enable:hover {
color: #0A9BD6;
}
.wp-smush-bulk-wrapper {
#wp-smush-bulk-image-count {
color: #333333;
font-size: 28px;
line-height: 40px;
letter-spacing: -0.5px;
font-weight: 600;
}
#wp-smush-bulk-image-count-description {
color: #333333;
font-size: 13px;
margin-top: 0;
margin-bottom: 10px;
}
.sui-tooltip,
.sui-tooltip > .sui-icon-info {
vertical-align: top;
}
}
/** Image Remaining **/
div.wp-smush-remaining,
div.wp-smush-dir-limit,
div.smush-s3-setup-message {
background-color: #FFF5D5;
border: none;
color: #333333;
line-height: 30px;
font-size: 15px;
letter-spacing: -0.015em;
}
div.smush-s3-setup-message {
background-color: #DFF6FA;
}
div.wp-smush-dir-limit {
background-color: #dff6fa;
}
.wp-smush-resmush-wrap .wp-smush-remaining {
padding: 10px 50px;
}
.wp-smush-count {
color: #888888;
font-size: 13px;
line-height: 1.5;
margin-top: 15px;
}
/** Stats Container **/
a.wp-smush-lossy-enable {
cursor: pointer;
}
/** Re Smush **/
.wp-smush-settings-changed {
background: #dff6fa;
border-radius: 5px;
font-size: 13px;
line-height: 1.7;
padding: 20px;
}
.compat-item .compat-field-wp_smush {
display: table-row;
}
.manage-column.column-smushit {
width: 260px;
}
.smushit [tooltip],
label.setting.smush-stats [tooltip],
.compat-field-wp_smush [tooltip] {
position: relative;
overflow: visible;
}
.smushit [tooltip]:before,
label.setting.smush-stats [tooltip]:before,
.compat-field-wp_smush [tooltip]:before {
content: '';
position: absolute;
border: 5px solid transparent;
border-top-color: #0B2F3F;
bottom: 100%;
left: 50%;
margin-left: -5px;
margin-bottom: -5px;
opacity: 0;
z-index: -1;
transition: margin .2s, opacity .2s, z-index .2s linear .2s;
pointer-events: none;
}
.smushit [tooltip]:after,
label.setting.smush-stats [tooltip]:after,
.compat-field-wp_smush [tooltip]:after {
background: #0B2F3F;
border-radius: 4px;
bottom: 100%;
color: #FFF;
content: attr(tooltip);
font-size: 13px;
font-weight: 400;
left: 50%;
line-height: 20px;
margin-left: -100px;
margin-bottom: 5px;
opacity: 0;
padding: 5px;
pointer-events: none;
position: absolute;
width: 180px;
text-align: center;
transition: margin .2s, opacity .2s, z-index .2s linear .2s;
white-space: pre-wrap;
z-index: -1;
}
.smushit .smush-skipped [tooltip]:before,
label.setting.smush-stats .smush-skipped [tooltip]:before,
.compat-field-wp_smush .smush-skipped [tooltip]:before {
border-top-color: transparent;
border-left-color: #0B2F3F;
bottom: 0;
left: 0;
}
.smushit .smush-skipped [tooltip]:after,
label.setting.smush-stats .smush-skipped [tooltip]:after,
.compat-field-wp_smush .smush-skipped [tooltip]:after {
margin-left: 0;
left: -195px;
top: -35px;
bottom: inherit;
margin-bottom: 5px;
}
label.setting.smush-stats .smush-skipped [tooltip]:after {
top: -98px;
}
div.media-sidebar label.setting.smush-stats .smush-skipped [tooltip]:after {
left: -188px;
padding-left: 10px;
width: 170px;
}
div.media-sidebar label.setting.smush-stats .smush-skipped [tooltip]:before {
margin-left: -3px;
}
.smushit [tooltip].tooltip-s:after,
label.setting.smush-stats [tooltip].tooltip-s:after,
.compat-field-wp_smush [tooltip].tooltip-s:after {
width: 150px;
margin-left: -75px;
}
.smushit [tooltip].tooltip-l:after,
label.setting.smush-stats [tooltip].tooltip-l:after,
.compat-field-wp_smush [tooltip].tooltip-l:after {
width: 280px;
margin-left: -140px;
}
.smushit [tooltip].tooltip-right:after, .compat-field-wp_smush [tooltip].tooltip-right:after {
margin-left: -180px;
}
.smushit [tooltip].tooltip-s.tooltip-right:after, .compat-field-wp_smush [tooltip].tooltip-s.tooltip-right:after {
margin-left: -130px;
}
.smushit [tooltip].tooltip-l.tooltip-right:after, .compat-field-wp_smush [tooltip].tooltip-l.tooltip-right:after {
margin-left: -260px;
}
.smushit [tooltip].tooltip-bottom:before, .compat-field-wp_smush [tooltip].tooltip-bottom:before {
border-color: transparent;
border-bottom-color: #0B2F3F;
top: 100%;
bottom: auto;
margin-top: -5px;
margin-bottom: 0;
}
.smushit [tooltip].tooltip-bottom:after, .compat-field-wp_smush [tooltip].tooltip-bottom:after {
bottom: auto;
top: 100%;
margin-top: 5px;
margin-bottom: 0;
}
.smushit [tooltip]:hover:before,
label.setting.smush-stats [tooltip]:hover:before,
.compat-field-wp_smush [tooltip]:hover:before {
z-index: 1;
margin-bottom: 0;
opacity: 1;
transition: margin .2s, opacity .2s;
}
.smushit [tooltip]:hover:after,
label.setting.smush-stats [tooltip]:hover:after,
.compat-field-wp_smush [tooltip]:hover:after {
opacity: 1;
z-index: 1;
margin-bottom: 10px;
transition: margin .2s, opacity .2s;
}
.smushit .disabled[tooltip]:before,
.smushit .disabled[tooltip]:after,
label.setting.smush-stats .disabled[tooltip]:before,
label.setting.smush-stats .disabled[tooltip]:after,
.compat-field-wp_smush .disabled[tooltip]:before,
.compat-field-wp_smush .disabled[tooltip]:after {
display: none;
}
/** Image List **/
div.wp-smush-scan-result {
background: white;
div.wp-smush-notice {
margin-top: 14px;
padding: 15px 30px;
}
div.content {
overflow: hidden;
width: 100%;
}
}
div.wp-smush-info.notice {
font-size: 15px;
letter-spacing: -0.015em;
margin: 0 0 30px;
padding: 15px;
}
/** Media Queries **/
@media screen and (max-width: 1024px) and (min-width: 800px) {
/** Stats Section **/
.smush-stats-wrapper h3 {
padding: 6px 0;
}
}
/** Media Queries for resolution below 782px **/
@media only screen and (max-width: 800px) {
.dev-box.bulk-smush-wrapper.wp-smush-container {
padding: 20px 10px;
}
}
/**
* CSS styles used Admin notice
*/
.smush-notice.notice {
padding: 0;
margin: 5px 0 10px;
border: 1px solid #E5E5E5;
background: #FFF;
overflow: hidden;
-webkit-border-radius: 6px;
border-radius: 6px;
-webkit-box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.05);
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.05);
position: relative;
z-index: 1;
min-height: 80px;
display: table; /* The magic ingredient! */
font: 13px "Roboto", sans-serif;
}
.smush-notice.notice.loading:before {
content: attr(data-message);
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.7);
z-index: 5;
text-align: center;
line-height: 80px;
font-size: 22px;
font-weight: bold;
}
.smush-notice > div {
display: table-cell; /* The magic ingredient! */
vertical-align: middle;
cursor: default;
line-height: 1.5;
}
.smush-notice.notice.loading > div {
-webkit-filter: blur(2px);
-moz-filter: blur(2px);
-o-filter: blur(2px);
-ms-filter: blur(2px);
filter: blur(2px);
}
.smush-notice-logo {
padding-left: 30px;
}
.smush-notice-message {
color: #23282D;
font-size: 13px;
font-weight: normal;
line-height: 20px;
padding: 20px;
-webkit-font-smoothing: antialiased;
width: 100%;
}
.smush-notice-cta {
background: #F8F8F8;
padding: 0 30px;
position: relative;
white-space: nowrap;
}
.wp-core-ui .smush-notice-cta button,
.wp-core-ui .smush-notice-cta .button-primary:active {
vertical-align: middle;
}
.wp-core-ui .smush-notice-cta input[type="email"] {
vertical-align: middle;
line-height: 20px;
margin: 0;
min-width: 50px;
max-width: 320px;
text-align: center;
padding-left: 0;
padding-right: 0;
}
/**
* Upsell lists.
* @since 3.9.1
*/
#smush-box-cdn-upsell,
#smush-box-webp-wizard {
.sui-upsell-list {
max-width: 525px;
text-align: left;
margin: 0 auto;
}
}
@media only all and (max-width: 1000px) {
.smush-notice.notice {
display: block;
font-size: 13px;
}
.smush-notice > .smush-notice-logo {
float: left;
display: inline-block;
height: 80px;
margin: 10px;
border-radius: 4px;
}
.smush-notice > .smush-notice-message {
width: auto;
display: block;
min-height: 80px;
}
.smush-notice > .smush-notice-cta {
display: block;
border-top: 1px solid #E5E5E5;
border-left: 0;
text-align: center;
white-space: normal;
line-height: 30px;
padding: 10px 20px;
}
.wp-core-ui .smush-notice > .smush-notice-cta > input[type="email"],
.smush-notice > .smush-notice-cta > button {
font-size: 14px;
}
}
@media only all and (max-width: 500px) {
.wp-core-ui .smush-notice > .smush-notice-cta > input[type="email"],
.smush-notice > .smush-notice-cta > button {
display: block;
width: 100% !important;
max-width: none;
margin-bottom: 4px;
font-size: 16px;
height: 34px;
}
}
.smush-dismissible-notice {
position: relative;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,72 @@
@import "variables";
/**
* CDN styles
*
* @since 3.0
*/
@include body-class {
.sui-wrap {
.sui-box-settings-row .sui-box-settings-col-1 {
vertical-align: top;
}
&.wrap-smush-cdn {
.sui-block-content-center p {
max-width: 600px;
margin: 20px auto 30px;
}
.sui-box-header .sui-actions-right .sui-icon-info{
font-size: 16px;
position: relative;
top: 1.5px;
}
}
.sui-cdn {
form p:first-of-type {
margin-top: 0;
}
}
.wp-smush-stats {
display: flex;
align-items: center;
line-height: 0;
.sui-tooltip {
line-height: 10px;
margin-right: 10px;
}
}
/* Filename Extensions Icons */
.smush-filename-extension {
border-radius: 4px;
display: inline-block;
font-size: 9px;
font-weight: 600;
color: #fff;
text-transform: uppercase;
text-align: center;
line-height: 43px;
height: 30px;
margin: 0 5px 0 0;
width: 30px;
&.smush-extension-jpeg,
&.smush-extension-jpg { background-color: #F7E100; }
&.smush-extension-png { background-color: #FFB694; }
&.smush-extension-gif { background-color: #72D5D4; }
&.smush-extension-webp { background-color: #72ADD5; }
&.smush-extension-svg { background-color: #88D572; }
&.smush-extension-iframe {
background-color: #8772D5;
font-size: 7px;
}
}
}
}

View File

@ -0,0 +1,338 @@
/* ****************************************************************************
* MODULE: Directory Smush styles.
*/
@include body-class {
.wp-smush-progress-dialog,
.wp-smush-list-dialog {
text-align: left;
}
.sui-directory.sui-message {
text-align: left;
.sui-message-content {
text-align: center;
}
}
.sui-directory .smush-final-log {
margin-top: 30px;
.sui-description {
margin-top: 10px;
}
}
ul.fancytree-container {
color: #666;
font-family: "Roboto", sans-serif;
font-size: 13px;
font-weight: 500;
letter-spacing: -0.25px;
line-height: 40px;
padding: 0;
margin: 0;
outline: 0 solid transparent;
min-height: 0%;
position: relative;
ul {
padding: 0 0 0 16px;
margin: 0;
display: block;
}
/*------------------------------------------------------------------------------
* Expander icon
*
* Note: IE6 doesn't correctly evaluate multiples class names,
* so we create combined class names that can be used in the CSS.
*
* Prefix: fancytree-exp-
* 1st character: 'e': expanded, 'c': collapsed, 'n': no children
* 2nd character (optional): 'd': lazy (Delayed)
* 3rd character (optional): 'l': Last sibling
*----------------------------------------------------------------------------*/
span.fancytree-expander {
cursor: pointer;
font-size: 12px;
margin-left: 13px;
width: 15px;
&:before {
font-family: wpmudev-plugin-icons, sans-serif;
}
}
.fancytree-exp-c span.fancytree-expander,
.fancytree-exp-cd:not(.fancytree-unselectable) span.fancytree-expander,
.fancytree-exp-cf:not(.fancytree-unselectable) span.fancytree-expander {
margin-left: 13px;
}
// --- End nodes (use connectors instead of expanders)
.fancytree-expanded.fancytree-exp-n span.fancytree-expander,
.fancytree-expanded.fancytree-exp-nl span.fancytree-expander {
width: 13px;
display: inline-block;
&:before {
background-image: none;
cursor: default;
}
}
.fancytree-exp-n span.fancytree-expander,
.fancytree-exp-nl span.fancytree-expander {
width: 12px;
display: inline-block;
}
.fancytree-exp-nl span.fancytree-expander:before {
content: "\131";
cursor: default;
}
span.fancytree-ico-c span.fancytree-expander:before {
content: '';
cursor: default;
}
// --- Collapsed
.fancytree-exp-c span.fancytree-expander:before,
.fancytree-exp-cl span.fancytree-expander:before,
.fancytree-exp-cd span.fancytree-expander:before,
.fancytree-exp-cdl span.fancytree-expander:before,
.fancytree-exp-e span.fancytree-expander:before,
.fancytree-exp-ed span.fancytree-expander:before,
.fancytree-exp-el span.fancytree-expander:before,
.fancytree-exp-edl span.fancytree-expander:before {
color: #888888;
content: "\2DC";
}
// --- Expanded
.fancytree-exp-e span.fancytree-expander:before,
.fancytree-exp-ed span.fancytree-expander:before,
.fancytree-exp-el span.fancytree-expander:before,
.fancytree-exp-edl span.fancytree-expander:before {
content: "\131";
}
// --- Unselectable
.fancytree-unselectable span.fancytree-expander:before {
content: "9";
}
/* Fade out expanders, when container is not hovered or active */
.fancytree-fade-expander {
span.fancytree-expander:before {
transition: opacity 1.5s;
opacity: 0;
}
&:hover span.fancytree-expander:before,
&.fancytree-treefocus span.fancytree-expander:before,
.fancytree-treefocus span.fancytree-expander:before,
[class*='fancytree-statusnode-'] span.fancytree-expander:before {
transition: opacity 0.6s;
opacity: 1;
}
}
/*------------------------------------------------------------------------------
* Checkbox icon
*----------------------------------------------------------------------------*/
span.fancytree-checkbox {
margin-right: 5px;
margin-left: 12px;
border-radius: 3px;
border: 1px solid #ddd;
background-color: #e6e6e6;
display: inline-block;
width: 16px;
height: 16px;
top: 2px;
position: relative;
transition: .2s;
@include icon( before, check );
&:before {
opacity: 0;
color: #fff;
font-size: 10px;
line-height: 14px;
position: absolute;
width: 100%;
text-align: center;
transition: .2s;
}
}
.fancytree-selected span.fancytree-checkbox {
border: 1px solid #17a8e3;
background-color: #17a8e3;
&:before {
opacity: 1;
}
}
.fancytree-expanded span.fancytree-checkbox {
margin-left: 11px;
}
// Unselectable is dimmed, without hover effects
.fancytree-unselectable {
background-color: transparent !important;
// Fix for bug in library.
&.fancytree-selected {
margin-left: -9px;
span.fancytree-expander {
margin-left: 10px;
}
span.fancytree-checkbox {
border: 1px solid #ddd;
background-color: #e6e6e6;
&:before {
color: #e6e6e6 !important;
}
}
span.fancytree-title {
color: #666;
}
}
span.fancytree-expander,
span.fancytree-icon,
span.fancytree-checkbox,
span.fancytree-title {
opacity: 0.4;
filter: alpha(opacity=40);
&:before {
color: #666 !important;
}
}
}
/*------------------------------------------------------------------------------
* Node type icon
* Note: IE6 doesn't correctly evaluate multiples class names,
* so we create combined class names that can be used in the CSS.
*
* Prefix: fancytree-ico-
* 1st character: 'e': expanded, 'c': collapsed
* 2nd character (optional): 'f': folder
*----------------------------------------------------------------------------*/
span.fancytree-icon:before { // Default icon
margin-left: 10px;
font-family: wpmudev-plugin-icons, sans-serif;
font-size: 16px;
color: #AAA;
content: 'D';
position: relative;
top: 1px;
}
/* Documents */
.fancytree-ico-c span.fancytree-icon:before { // Collapsed folder (empty)
content: 'D';
}
.fancytree-has-children.fancytree-ico-c span.fancytree-icon:before { // Collapsed folder (not empty)
content: 'D';
}
.fancytree-ico-e span.fancytree-icon:before { // Expanded folder
content: '\BB';
}
/* Folders */
.fancytree-exp-n.fancytree-ico-ef span.fancytree-icon:before,
.fancytree-exp-nl.fancytree-ico-ef span.fancytree-icon:before,
.fancytree-ico-cf span.fancytree-icon:before { // Collapsed folder (empty)
content: '\2D8';
}
.fancytree-has-children.fancytree-ico-cf span.fancytree-icon:before { // Collapsed folder (not empty)
content: '\2D8';
}
.fancytree-ico-ef span.fancytree-icon:before { // Expanded folder
content: '\BB';
}
// 'Loading' status overrides all others
.fancytree-loading span.fancytree-expander:before,
.fancytree-statusnode-loading span.fancytree-icon:before {
content: 'N';
display: inline-block;
animation: spin 1.3s linear infinite;
}
/*------------------------------------------------------------------------------
* Node titles and highlighting
*----------------------------------------------------------------------------*/
span.fancytree-node {
display: inherit;
width: 100%;
margin-top: 5px;
min-height: 40px;
&:not(.fancytree-unselectable):hover {
background-color: #F8F8F8;
}
}
span.fancytree-title {
color: #666; // inherit doesn't work on IE
cursor: pointer;
display: inline-block; // Better alignment, when title contains <br>
vertical-align: top;
min-height: 20px;
padding: 0 3px 0 3px; // Otherwise italic font will be outside right bounds
margin: 0 0 0 5px;
border: 1px solid transparent; // avoid jumping, when a border is added on hover
border-radius: 4px;
font-weight: 500;
}
span.fancytree-node.fancytree-error span.fancytree-title {
//color: @fancy-font-error-color;
}
span.fancytree-expanded,
span.fancytree-selected {
border-radius: 4px;
background-color: #F8F8F8;
color: #17A8E3;
span.fancytree-title {
color: #666666;
}
}
span.fancytree-selected {
background-color: #E1F6FF;
span.fancytree-expander:before,
span.fancytree-icon:before,
span.fancytree-title {
color: #17A8E3;
}
}
span.fancytree-focused {
background-color: #e1e1e1 !important;
}
}
}

View File

@ -0,0 +1,216 @@
/* ****************************************************************************
* MEDIA AREA SCSS FILE
*/
@import "~@wpmudev/shared-ui/scss/functions";
@import "~@wpmudev/shared-ui/scss/colors";
@import "~@wpmudev/shared-ui/scss/variables";
// Override body class
$sui-version: 'smush-media';
$sui-wrap-class: false;
@import "~@wpmudev/shared-ui/scss/mixins";
@import "~@wpmudev/shared-ui/scss/tooltips";
/* ****************************************************************************
* MEDIA AREA STYLES
*/
// Set column width.
.manage-column.column-smushit {
width: 260px;
}
// Margin for buttons.
.sui-smush-media {
.button {
margin-right: 5px;
&:last-of-type {
margin-right: 0;
}
}
}
// Smush button loading icon.
#ngg-listimages,
.column-smushit {
.spinner {
float: none;
&.visible {
visibility: visible;
}
}
}
.smush-status-links{
.smush-upgrade-link {
color: #8D00B1;
font-size: 12px;
}
.smush-ignore-utm,.smush-revert-utm{
display: block;
margin: 6px 0 4px;
}
a {
text-decoration: none;
}
span {
float: none !important;;
}
.smush-cdn-notice {
color: #50575E;
a {
color:#2271B1;
&:focus {
box-shadow: none;
opacity: 0.7;
}
}
}
}
.smush-status {
&.smush-warning,&.smush-ignored,&.smush-success{
padding-left:17px;
position: relative;
&:before{
content:"";
background: url('../images/icon-warning.png' ) no-repeat 0 0;
position: absolute;
width:12px;
height:12px;
background-size: contain;
left: 0;
top:3px;
}
}
&.smush-ignored{
&:before{
background-image: url('../images/icon-ignored.png' ) !important;
}
}
&.smush-success{
&:before{
background-image: url('../images/icon-success.png' ) !important;
}
}
.sui-icon-warning-media-lib {
margin-right:4px;
position:relative;
top:1px;
}
}
.column-smushit .smush-status{
color:#50575E;
}
// Stats table.
.sui-smush-media {
table.wp-smush-stats-holder {
width: 100%;
border: 1px solid #E6E6E6;
border-radius: 4px;
margin-top: 6px;
border-collapse: collapse;
border-spacing: 0;
thead {
th.smush-stats-header {
padding: 8px 10px;
border-bottom: 1px solid #E6E6E6 !important;
color: #32373D;
font-size: 12px;
font-weight: bold;
letter-spacing: -0.23px;
line-height: 16px;
text-align: left;
}
}
tr {
border: 1px solid #E6E6E6;
}
td {
overflow-wrap: break-word;
vertical-align: middle;
padding: 8px 10px;
color: #555555;
font-size: 11px;
letter-spacing: -0.21px;
line-height: 16px;
border-bottom: 1px solid #E6E6E6;
&:first-of-type {
max-width: 110px;
font-weight: 500;
}
}
}
}
// Override !important set from WordPress.
#the-list {
.sui-smush-media {
thead {
th.smush-stats-header {
border-bottom: 1px solid #E6E6E6 !important;
}
}
}
}
// Responsive table for list mode.
@media screen and (max-width: 1024px) {
.wp-list-table .smushit {
table.wp-smush-stats-holder {
th {
display: table-cell;
box-sizing: border-box;
}
tr td {
word-wrap: break-word;
display: table-cell !important;
&:first-child {
border-right: none;
box-sizing: border-box;
}
&:last-child {
box-sizing: border-box;
float: none;
overflow: visible;
}
}
}
}
}
// NextGen Integration.
.iedit .wp-smush-action,
.iedit .smush-stats-details {
font-size: 11px;
}
/*NextGen Gallery stats*/
#ngg-listimages {
table.wp-smush-stats-holder {
table-layout: fixed;
border: 1px solid lightgray;
border-collapse: collapse;
width: 100%;
td,
th {
border: 1px solid #CECECE;
}
}
.column-7 {
width: 300px;
}
.spinner {
width: auto;
padding-left: 30px;
}
}
/** NextGen Gallery tr height, to show the progress bar properly for alternate rows **/
.alternate.iedit {
height: 120px;
}
/** Allows to click on button, otherwise row-actions from NextGen interferes **/
.wp-smush-nextgen-send {
position: relative;
z-index: 2;
}

View File

@ -0,0 +1,22 @@
/* ****************************************************************************
* MODULE: VARIABLES
*/
$font--path: "../fonts" !default;
$img--path: "../images" !default;
$sui-font-path: '~@wpmudev/shared-ui/dist/fonts/';
$summary-image: '#{$img--path}/smush-graphic-dashboard-summary.svg';
// Promo banners for free footer
$cross-sell-1: 'hummingbird';
$cross-sell-2: 'defender';
$cross-sell-3: 'smartcrawl';
$main-color: #17A8E3;
$text-color: #333;
$border-radius: 4px;
$upgrade-image: '../../app/assets/images/hero@2x.png';
$upgrade-image-mobile: '../../app/assets/images/hero.png';

View File

@ -0,0 +1,212 @@
/**
* Webp styles
*
* @since 3.8.0
*/
@include body-class(true) {
#smush-box-webp-webp {
.smush-webp-supported-browser {
height: 30px;
width: 30px;
padding: 5px;
margin-right: 10px;
border-radius: 4px;
background-color: #F2F2F2;
display: inline-block;
img {
height: 20px;
width: 20px;
}
}
}
#smush-box-webp-wizard {
.sui-row-with-sidenav {
margin-bottom: 0;
.sui-sidenav {
padding: 30px;
border-top-left-radius: $border-radius;
background-color: #F8F8F8;
.smush-wizard-bar-subtitle {
font-size: 11px;
line-height: 20px;
font-weight: 700;
color: #AAAAAA;
text-transform: uppercase;
}
.smush-sidenav-title {
display: flex;
align-items: center;
margin-bottom: 33px;
h4 {
margin: 0;
line-height: 20px;
}
}
.smush-wizard-steps-container {
ul {
margin: 0;
@include media(max-width, lg) {
display: flex;
flex-direction: row;
justify-content: space-between;
}
}
.smush-wizard-bar-step {
display: inline-block;
font-size: 13px;
color: #AAAAAA;
line-height: 22px;
font-weight: 500;
margin-bottom: 0;
.smush-wizard-bar-step-number {
display: inline-block;
margin-right: 10px;
text-align: center;
border-radius: 50%;
width: 22px;
height: 22px;
font-size: 11px;
background-color: #F2F2F2;
border: 1px solid #DDDDDD;
@include media(max-width, lg) {
display: block;
margin: 0 auto 5px auto;
}
}
&.disabled {
color: #DDDDDD;
}
&.current {
color: #333333;
.smush-wizard-bar-step-number {
background-color: #FFFFFF;
border-color: #333333;
}
}
&.done .smush-wizard-bar-step-number {
background-color: #1ABC9C;
border-color: #1ABC9C;
.sui-icon-check::before {
color: #FFFFFF;
}
}
@include media(min-width, lg) {
display: block;
}
@include media(max-width, lg) {
width: 70px;
text-align: center;
}
}
svg {
line {
stroke-width: 4px;
}
&.smush-svg-desktop {
height: 40px;
width: 22px;
margin-left: 10px;
display: none;
@include media(min-width, lg) {
display: block;
}
}
&.smush-svg-mobile {
width: 100%;
height: 4px;
display: block;
margin-bottom: -14px;
padding: 0 35px;
@include media(min-width, lg) {
display: none;
}
}
}
}
@include media(max-width, sm) {
padding: 20px;
}
}
.smush-wizard-steps-content-wrapper {
padding: 20px;
.smush-wizard-steps-content {
padding: 0 70px;
text-align: center;
&:first-child {
padding-top: 30px;
}
.smush-step-indicator {
font-size: 11px;
font-weight: 500;
color: #888888;
}
h2 {
margin: 0;
}
@include media(max-width, sm) {
padding: 0;
}
}
&.smush-wizard-step-1 {
.sui-box-selectors {
padding-left: 115px;
padding-right: 115px;
margin-bottom: 15px;
}
}
&.smush-wizard-step-2 {
.smush-wizard-rules-wrapper {
text-align: left;
}
.sui-tabs-menu {
justify-content: center;
}
}
@include media(min-width, sm) {
padding: 30px;
}
}
}
}
}

View File

@ -0,0 +1,360 @@
@import url('https://fonts.bunny.net/css?family=Roboto:400,500,700');
@import "modules/variables";
$font--path: '~@wpmudev/shared-ui/dist/fonts/';
@font-face {
font-family: "wpmudev-plugin-icons";
src: url('#{$font--path}/wpmudev-plugin-icons.eot?');
src: url('#{$font--path}/wpmudev-plugin-icons.eot?') format('embedded-opentype'),
url('#{$font--path}/wpmudev-plugin-icons.ttf') format('truetype'),
url('#{$font--path}/wpmudev-plugin-icons.woff') format('woff'),
url('#{$font--path}/wpmudev-plugin-icons.woff2') format('woff2'),
url('#{$font--path}/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);
&.closed {
right: 0;
}
&.smush-toggle-success {
box-shadow: inset 2px 0 0 0 #1abc9c, -13px 5px 20px 0 rgba(0, 0, 0, 0.1);
}
i.sui-icon-info,
i.sui-icon-loader {
font-family: "wpmudev-plugin-icons" !important;
font-style: normal;
font-size: 16px;
line-height: 50px;
color: #FECF2F;
}
i.sui-icon-info:before {
content: "I";
}
&.smush-toggle-success i.sui-icon-info {
color: #1abc9c;
&:before {
content: "_";
}
}
i.sui-icon-loader {
&:before {
display: block;
content: "N";
-webkit-animation: spin 1.3s linear infinite;
animation: spin 1.3s linear infinite;
}
}
@-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
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);
&.closed {
max-width: 0;
overflow-y: hidden;
}
h3, p, strong, span {
font-family: 'Roboto', sans-serif;
letter-spacing: -0.25px;
}
h3 {
color: #333333;
font-size: 15px;
font-weight: bold;
line-height: 30px;
background-color: #FAFAFA;
padding: 15px 20px;
margin: 0;
}
p {
color: #888888;
font-size: 13px;
line-height: 22px;
padding: 0 20px;
}
strong {
color: #AAAAAA;
font-size: 12px;
font-weight: bold;
line-height: 22px;
padding: 0 20px;
}
.smush-resize-box {
background-color: #F8F8F8;
&:first-of-type {
border-top: 1px solid #E6E6E6;
margin-top: 5px;
}
&:last-of-type {
margin-bottom: 20px;
}
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-info {
background-color: #FFF;
display: flex;
align-items: center;
align-content: center;
justify-content: space-between;
padding: 17px 20px;
border-bottom: 1px solid #E6E6E6;
cursor: pointer;
}
.smush-front-icons {
margin: 0 10px;
line-height: 5px;
&: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-front-icon-arrows-in {
&:before {
content: '\2264';
}
}
}
.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: flex;
align-items: center;
justify-content: center;
&.smush-tag-success {
background-color: #1abc9c;
color: #fff;
}
}
&.smush-tooltip {
position: relative;
&:before,
&:after {
content: "";
opacity: 0;
backface-visibility: hidden;
pointer-events: none;
position: absolute;
z-index: 1;
transition: margin .2s, opacity .2s;
}
&:before {
border: 5px solid transparent;
bottom: 100%;
left: 50%;
border-top-color: #000000;
transform: translateX(-50%);
}
&: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-tooltip-constrained {
&:after {
min-width: 240px;
white-space: normal;
}
}
&:not(.show-description):hover {
&:before,
&:after {
opacity: 1;
}
}
}
&:not(.show-description):hover,
&.show-description {
.smush-image-info { background-color: #F8F8F8; }
span:first-of-type {
background-color: #E6E6E6;
color: transparent;
&:before {
font-family: "wpmudev-plugin-icons" !important;
font-weight: 400;
content: "";
color: #666;
margin-right: -7px;
}
}
}
.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;
}
&.show-description {
padding-bottom: 1px;
border-bottom: 1px solid #E6E6E6;
.smush-image-info { border-bottom: 0; }
.smush-image-description { display: block; }
span:first-of-type {
background-color: #FECF2F;
border-color: #FECF2F;
&:before { color: #333; }
}
}
}
#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;
p:before {
position: absolute;
left: 42px;
font-family: "wpmudev-plugin-icons" !important;
font-weight: 400;
content: "_";
color: #1abc9c;
margin-right: -7px;
}
}
}
}