Merged in feature/from-pantheon (pull request #16)

code from pantheon

* code from pantheon
This commit is contained in:
Tony Volpe
2024-01-10 17:03:02 +00:00
parent 054b4fffc9
commit 4eb982d7a8
16492 changed files with 3475854 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
// Admin bar =======================================================================================
(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
var busy = false;
$( d ).on( 'mouseenter', '#wp-admin-bar-imagify', function() {
var $adminBarProfile, url;
if ( true === busy ) {
return;
}
busy = true;
$adminBarProfile = $( '#wp-admin-bar-imagify-profile-content' );
if ( ! $adminBarProfile.is( ':empty' ) ) {
return;
}
if ( w.ajaxurl ) {
url = w.ajaxurl;
} else {
url = w.imagifyAdminBar.ajaxurl;
}
url += url.indexOf( '?' ) > 0 ? '&' : '?';
$.get( url + 'action=imagify_get_admin_bar_profile&imagifygetadminbarprofilenonce=' + $( '#imagifygetadminbarprofilenonce' ).val() )
.done( function( response ) {
$adminBarProfile.html( response.data );
$( '#wp-admin-bar-imagify-profile-loading' ).remove();
busy = false;
} );
} );
} )(jQuery, document, window);

View File

@@ -0,0 +1 @@
!function(n,i,e){var m=!1;n(i).on("mouseenter","#wp-admin-bar-imagify",function(){var a,i;!0!==m&&(m=!0,(a=n("#wp-admin-bar-imagify-profile-content")).is(":empty"))&&(i=e.ajaxurl||e.imagifyAdminBar.ajaxurl,i+=0<i.indexOf("?")?"&":"?",n.get(i+"action=imagify_get_admin_bar_profile&imagifygetadminbarprofilenonce="+n("#imagifygetadminbarprofilenonce").val()).done(function(i){a.html(i.data),n("#wp-admin-bar-imagify-profile-loading").remove(),m=!1}))})}(jQuery,document,window);

View File

@@ -0,0 +1,103 @@
window.imagify = window.imagify || {};
jQuery.extend( window.imagify, {
concat: ajaxurl.indexOf( '?' ) > 0 ? '&' : '?',
log: function( content ) {
if ( undefined !== console ) {
console.log( content ); // eslint-disable-line no-console
}
},
info: function( content ) {
if ( undefined !== console ) {
console.info( content ); // eslint-disable-line no-console
}
},
openModal: function( $link ) {
var target = $link.data( 'target' ) || $link.attr( 'href' );
jQuery( target ).css( 'display', 'flex' ).hide().fadeIn( 400 ).attr( {
'aria-hidden': 'false',
'tabindex': '0'
} ).trigger('focus').removeAttr( 'tabindex' ).addClass( 'modal-is-open' );
jQuery( 'body' ).addClass( 'imagify-modal-is-open' );
},
template: function( id ) {
if ( undefined === _ ) {
// No need to load underscore everywhere if we don't use it.
return '';
}
return _.memoize( function( data ) {
var compiled,
options = {
evaluate: /<#([\s\S]+?)#>/g,
interpolate: /\{\{\{([\s\S]+?)\}\}\}/g,
escape: /\{\{([^}]+?)\}\}(?!\})/g,
variable: 'data'
};
return function() {
compiled = compiled || _.template( jQuery( '#tmpl-' + id ).html(), null, options );
data = data || {};
return compiled( data );
};
} );
},
humanSize: function( bytes ) {
var sizes = ['B', 'kB', 'MB'],
i;
if ( 0 === bytes ) {
return '0\xA0kB';
}
i = parseInt( Math.floor( Math.log( bytes ) / Math.log( 1024 ) ), 10 );
return ( bytes / Math.pow( 1024, i ) ).toFixed( 2 ) + '\xA0' + sizes[ i ];
}
} );
// Imagify light modal =============================================================================
(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
// Accessibility.
$( '.imagify-modal' ).attr( 'aria-hidden', 'true' );
$( d )
// On click on modal trigger, open modal.
.on( 'click.imagify', '.imagify-modal-trigger', function( e ) {
e.preventDefault();
w.imagify.openModal( $( this ) );
} )
// On click on close button, close modal.
.on( 'click.imagify', '.imagify-modal .close-btn', function() {
var $modal = $( this ).closest( '.imagify-modal' );
$modal.fadeOut( 400 ).attr( 'aria-hidden', 'true' ).removeClass( 'modal-is-open' ).trigger( 'modalClosed.imagify' );
$( 'body' ).removeClass( 'imagify-modal-is-open' );
} )
// On close button blur, improve accessibility.
.on( 'blur.imagify', '.imagify-modal .close-btn', function() {
var $modal = $( this ).closest( '.imagify-modal' );
if ( $modal.attr( 'aria-hidden' ) === 'false' ) {
$modal.attr( 'tabindex', '0' ).trigger('focus').removeAttr( 'tabindex' );
}
} )
// On click on dropped layer of modal, close modal.
.on( 'click.imagify', '.imagify-modal', function( e ) {
$( e.target ).filter( '.modal-is-open' ).find( '.close-btn' ).trigger( 'click.imagify' );
} )
// `Esc` key binding, close modal.
.on( 'keydown.imagify', function( e ) {
if ( 27 === e.keyCode && $( '.imagify-modal.modal-is-open' ).length > 0 ) {
e.preventDefault();
// Trigger the event.
$( '.imagify-modal.modal-is-open' ).find( '.close-btn' ).trigger( 'click.imagify' );
}
} );
} )(jQuery, document, window);

View File

@@ -0,0 +1 @@
window.imagify=window.imagify||{},jQuery.extend(window.imagify,{concat:0<ajaxurl.indexOf("?")?"&":"?",log:function(i){void 0!==console&&console.log(i)},info:function(i){void 0!==console&&console.info(i)},openModal:function(i){i=i.data("target")||i.attr("href");jQuery(i).css("display","flex").hide().fadeIn(400).attr({"aria-hidden":"false",tabindex:"0"}).trigger("focus").removeAttr("tabindex").addClass("modal-is-open"),jQuery("body").addClass("imagify-modal-is-open")},template:function(o){return void 0===_?"":_.memoize(function(i){var a,e={evaluate:/<#([\s\S]+?)#>/g,interpolate:/\{\{\{([\s\S]+?)\}\}\}/g,escape:/\{\{([^}]+?)\}\}(?!\})/g,variable:"data"};return function(){return(a=a||_.template(jQuery("#tmpl-"+o).html(),null,e))(i=i||{})}})},humanSize:function(i){var a;return 0===i?"0 kB":(a=parseInt(Math.floor(Math.log(i)/Math.log(1024)),10),(i/Math.pow(1024,a)).toFixed(2)+" "+["B","kB","MB"][a])}}),function(a,i,e){a(".imagify-modal").attr("aria-hidden","true"),a(i).on("click.imagify",".imagify-modal-trigger",function(i){i.preventDefault(),e.imagify.openModal(a(this))}).on("click.imagify",".imagify-modal .close-btn",function(){a(this).closest(".imagify-modal").fadeOut(400).attr("aria-hidden","true").removeClass("modal-is-open").trigger("modalClosed.imagify"),a("body").removeClass("imagify-modal-is-open")}).on("blur.imagify",".imagify-modal .close-btn",function(){var i=a(this).closest(".imagify-modal");"false"===i.attr("aria-hidden")&&i.attr("tabindex","0").trigger("focus").removeAttr("tabindex")}).on("click.imagify",".imagify-modal",function(i){a(i.target).filter(".modal-is-open").find(".close-btn").trigger("click.imagify")}).on("keydown.imagify",function(i){27===i.keyCode&&0<a(".imagify-modal.modal-is-open").length&&(i.preventDefault(),a(".imagify-modal.modal-is-open").find(".close-btn").trigger("click.imagify"))})}(jQuery,document,window);

View File

@@ -0,0 +1,872 @@
/**
* Imagify beat API
*
* This is a modified version of WordPress Heartbeat (WP 5.2.1).
* The main difference is that it allows to prevent suspension entirely.
* It uses the var imagifybeatSettings on init.
*
* Custom jQuery events:
* - imagifybeat-send
* - imagifybeat-tick
* - imagifybeat-error
* - imagifybeat-connection-lost
* - imagifybeat-connection-restored
* - imagifybeat-nonces-expired
*
* @since 1.9.3
*/
window.imagify = window.imagify || {};
/* eslint-disable no-use-before-define */
(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
/**
* Constructs the Imagifybeat API.
*
* @since 1.9.3
* @constructor
*
* @return {Imagifybeat} An instance of the Imagifybeat class.
*/
var Imagifybeat = function() {
var $document = $( d ),
settings = {
// Suspend/resume.
suspend: false,
// Whether suspending is enabled.
suspendEnabled: true,
// Current screen id, defaults to the JS global 'pagenow' when present
// (in the admin) or 'front'.
screenId: '',
// XHR request URL, defaults to the JS global 'ajaxurl' when present.
url: '',
// Timestamp, start of the last connection request.
lastTick: 0,
// Container for the enqueued items.
queue: {},
// Connect interval (in seconds).
mainInterval: 60,
// Used when the interval is set to 5 sec. temporarily.
tempInterval: 0,
// Used when the interval is reset.
originalInterval: 0,
// Used to limit the number of AJAX requests.
minimalInterval: 0,
// Used together with tempInterval.
countdown: 0,
// Whether a connection is currently in progress.
connecting: false,
// Whether a connection error occurred.
connectionError: false,
// Used to track non-critical errors.
errorcount: 0,
// Whether at least one connection has been completed successfully.
hasConnected: false,
// Whether the current browser w is in focus and the user is active.
hasFocus: true,
// Timestamp, last time the user was active. Checked every 30 sec.
userActivity: 0,
// Flag whether events tracking user activity were set.
userActivityEvents: false,
// Timer that keeps track of how long a user has focus.
checkFocusTimer: 0,
// Timer that keeps track of how long needs to be waited before connecting to
// the server again.
beatTimer: 0
};
/**
* Sets local variables and events, then starts the beat.
*
* @since 1.9.3
* @access private
*
* @return {void}
*/
function initialize() {
var options, hidden, visibilityState, visibilitychange;
if ( typeof w.pagenow === 'string' ) {
settings.screenId = w.pagenow;
}
if ( typeof w.ajaxurl === 'string' ) {
settings.url = w.ajaxurl;
}
// Pull in options passed from PHP.
if ( typeof w.imagifybeatSettings === 'object' ) {
options = w.imagifybeatSettings;
// The XHR URL can be passed as option when w.ajaxurl is not set.
if ( ! settings.url && options.ajaxurl ) {
settings.url = options.ajaxurl;
}
/*
* The interval can be from 15 to 120 sec. and can be set temporarily to 5 sec.
* It can be set in the initial options or changed later through JS and/or
* through PHP.
*/
if ( options.interval ) {
settings.mainInterval = options.interval;
if ( settings.mainInterval < 15 ) {
settings.mainInterval = 15;
} else if ( settings.mainInterval > 120 ) {
settings.mainInterval = 120;
}
}
/*
* Used to limit the number of AJAX requests. Overrides all other intervals if
* they are shorter. Needed for some hosts that cannot handle frequent requests
* and the user may exceed the allocated server CPU time, etc. The minimal
* interval can be up to 600 sec. however setting it to longer than 120 sec.
* will limit or disable some of the functionality (like post locks). Once set
* at initialization, minimalInterval cannot be changed/overridden.
*/
if ( options.minimalInterval ) {
options.minimalInterval = parseInt( options.minimalInterval, 10 );
settings.minimalInterval = options.minimalInterval > 0 && options.minimalInterval <= 600 ? options.minimalInterval * 1000 : 0;
}
if ( settings.minimalInterval && settings.mainInterval < settings.minimalInterval ) {
settings.mainInterval = settings.minimalInterval;
}
// 'screenId' can be added from settings on the front end where the JS global
// 'pagenow' is not set.
if ( ! settings.screenId ) {
settings.screenId = options.screenId || 'front';
}
if ( 'disable' === options.suspension ) {
disableSuspend();
}
}
// Convert to milliseconds.
settings.mainInterval = settings.mainInterval * 1000;
settings.originalInterval = settings.mainInterval;
/*
* Switch the interval to 120 seconds by using the Page Visibility API.
* If the browser doesn't support it (Safari < 7, Android < 4.4, IE < 10), the
* interval will be increased to 120 seconds after 5 minutes of mouse and keyboard
* inactivity.
*/
if ( typeof document.hidden !== 'undefined' ) {
hidden = 'hidden';
visibilitychange = 'visibilitychange';
visibilityState = 'visibilityState';
} else if ( typeof document.msHidden !== 'undefined' ) { // IE10
hidden = 'msHidden';
visibilitychange = 'msvisibilitychange';
visibilityState = 'msVisibilityState';
} else if ( typeof document.webkitHidden !== 'undefined' ) { // Android
hidden = 'webkitHidden';
visibilitychange = 'webkitvisibilitychange';
visibilityState = 'webkitVisibilityState';
}
if ( hidden ) {
if ( document[ hidden ] ) {
settings.hasFocus = false;
}
$document.on( visibilitychange + '.imagifybeat', function() {
if ( 'hidden' === document[ visibilityState ] ) {
blurred();
w.clearInterval( settings.checkFocusTimer );
} else {
focused();
if ( document.hasFocus ) {
settings.checkFocusTimer = w.setInterval( checkFocus, 10000 );
}
}
});
}
// Use document.hasFocus() if available.
if ( document.hasFocus ) {
settings.checkFocusTimer = w.setInterval( checkFocus, 10000 );
}
$( w ).on( 'unload.imagifybeat', function() {
// Don't connect anymore.
settings.suspend = true;
// Abort the last request if not completed.
if ( settings.xhr && 4 !== settings.xhr.readyState ) {
settings.xhr.abort();
}
} );
// Check for user activity every 30 seconds.
w.setInterval( checkUserActivity, 30000 );
// Start one tick after DOM ready.
$document.ready( function() {
settings.lastTick = time();
scheduleNextTick();
} );
}
/**
* Returns the current time according to the browser.
*
* @since 1.9.3
* @access private
*
* @return {int} Returns the current time.
*/
function time() {
return (new Date()).getTime();
}
/**
* Checks if the iframe is from the same origin.
*
* @since 1.9.3
* @access private
*
* @return {bool} Returns whether or not the iframe is from the same origin.
*/
function isLocalFrame( frame ) {
var origin, src = frame.src; // eslint-disable-line no-shadow
/*
* Need to compare strings as WebKit doesn't throw JS errors when iframes have different origin. It throws uncatchable exceptions.
*/
if ( src && /^https?:\/\//.test( src ) ) {
origin = w.location.origin ? w.location.origin : w.location.protocol + '//' + w.location.host;
if ( src.indexOf( origin ) !== 0 ) {
return false;
}
}
try {
if ( frame.contentWindow.document ) {
return true;
}
} catch ( e ) {} // eslint-disable-line no-empty
return false;
}
/**
* Checks if the document's focus has changed.
*
* @since 1.9.3
* @access private
*
* @return {void}
*/
function checkFocus() {
if ( settings.hasFocus && ! document.hasFocus() ) {
blurred();
} else if ( ! settings.hasFocus && document.hasFocus() ) {
focused();
}
}
/**
* Sets error state and fires an event on XHR errors or timeout.
*
* @since 1.9.3
* @access private
*
* @param {string} error The error type passed from the XHR.
* @param {int} httpStatus The HTTP status code passed from jqXHR (200, 404, 500, etc.).
* @return {void}
*/
function setErrorState( error, httpStatus ) {
var trigger;
if ( error ) {
switch ( error ) {
case 'abort':
// Do nothing.
break;
case 'timeout':
// No response for 30 sec.
trigger = true;
break;
case 'error':
if ( 503 === httpStatus && settings.hasConnected ) {
trigger = true;
break;
}
/* falls through */
case 'parsererror':
case 'empty':
case 'unknown':
settings.errorcount++;
if ( settings.errorcount > 2 && settings.hasConnected ) {
trigger = true;
}
break;
}
if ( trigger && ! hasConnectionError() ) {
settings.connectionError = true;
$document.trigger( 'imagifybeat-connection-lost', [ error, httpStatus ] );
if ( w.wp.hooks ) {
w.wp.hooks.doAction( 'imagifybeat.connection-lost', error, httpStatus );
}
}
}
}
/**
* Clears the error state and fires an event if there is a connection error.
*
* @since 1.9.3
* @access private
*
* @return {void}
*/
function clearErrorState() {
// Has connected successfully.
settings.hasConnected = true;
if ( hasConnectionError() ) {
settings.errorcount = 0;
settings.connectionError = false;
$document.trigger( 'imagifybeat-connection-restored' );
if ( w.wp.hooks ) {
w.wp.hooks.doAction( 'imagifybeat.connection-restored' );
}
}
}
/**
* Gathers the data and connects to the server.
*
* @since 1.9.3
* @access private
*
* @return {void}
*/
function connect() {
var ajaxData, imagifybeatData;
// If the connection to the server is slower than the interval,
// imagifybeat connects as soon as the previous connection's response is received.
if ( settings.connecting || settings.suspend ) {
return;
}
settings.lastTick = time();
imagifybeatData = $.extend( {}, settings.queue );
// Clear the data queue. Anything added after this point will be sent on the next tick.
settings.queue = {};
$document.trigger( 'imagifybeat-send', [ imagifybeatData ] );
if ( w.wp.hooks ) {
w.wp.hooks.doAction( 'imagifybeat.send', imagifybeatData );
}
ajaxData = {
data: imagifybeatData,
interval: settings.tempInterval ? settings.tempInterval / 1000 : settings.mainInterval / 1000,
_nonce: typeof w.imagifybeatSettings === 'object' ? w.imagifybeatSettings.nonce : '',
action: 'imagifybeat',
screen_id: settings.screenId,
has_focus: settings.hasFocus
};
if ( 'customize' === settings.screenId ) {
ajaxData.wp_customize = 'on';
}
settings.connecting = true;
settings.xhr = $.ajax( {
url: settings.url,
type: 'post',
timeout: 60000, // Throw an error if not completed after 60 sec.
data: ajaxData,
dataType: 'json'
} ).always( function() {
settings.connecting = false;
scheduleNextTick();
} ).done( function( response, textStatus, jqXHR ) {
var newInterval;
if ( ! response ) {
setErrorState( 'empty' );
return;
}
clearErrorState();
if ( response.nonces_expired ) {
$document.trigger( 'imagifybeat-nonces-expired' );
if ( w.wp.hooks ) {
w.wp.hooks.doAction( 'imagifybeat.nonces-expired' );
}
}
// Change the interval from PHP
if ( response.imagifybeat_interval ) {
newInterval = response.imagifybeat_interval;
delete response.imagifybeat_interval;
}
// Update the imagifybeat nonce if set.
if ( response.imagifybeat_nonce && typeof w.imagifybeatSettings === 'object' ) {
w.imagifybeatSettings.nonce = response.imagifybeat_nonce;
delete response.imagifybeat_nonce;
}
$document.trigger( 'imagifybeat-tick', [ response, textStatus, jqXHR ] );
if ( w.wp.hooks ) {
w.wp.hooks.doAction( 'imagifybeat.tick', response, textStatus, jqXHR );
}
// Do this last. Can trigger the next XHR if connection time > 5 sec. and newInterval == 'fast'.
if ( newInterval ) {
interval( newInterval );
}
} ).fail( function( jqXHR, textStatus, error ) {
setErrorState( textStatus || 'unknown', jqXHR.status );
$document.trigger( 'imagifybeat-error', [ jqXHR, textStatus, error ] );
if ( w.wp.hooks ) {
w.wp.hooks.doAction( 'imagifybeat.error', jqXHR, textStatus, error );
}
} );
}
/**
* Schedules the next connection.
*
* Fires immediately if the connection time is longer than the interval.
*
* @since 1.9.3
* @access private
*
* @return {void}
*/
function scheduleNextTick() {
var delta = time() - settings.lastTick,
interv = settings.mainInterval;
if ( settings.suspend ) {
return;
}
if ( ! settings.hasFocus && settings.suspendEnabled ) {
// When no user activity or the window lost focus, increase polling interval to 120 seconds, but only if suspend is enabled.
interv = 120000; // 120 sec.
} else if ( settings.countdown > 0 && settings.tempInterval ) {
interv = settings.tempInterval;
settings.countdown--;
if ( settings.countdown < 1 ) {
settings.tempInterval = 0;
}
}
if ( settings.minimalInterval && interv < settings.minimalInterval ) {
interv = settings.minimalInterval;
}
w.clearTimeout( settings.beatTimer );
if ( delta < interv ) {
settings.beatTimer = w.setTimeout(
function() {
connect();
},
interv - delta
);
} else {
connect();
}
}
/**
* Sets the internal state when the browser w becomes hidden or loses focus.
*
* @since 1.9.3
* @access private
*
* @return {void}
*/
function blurred() {
settings.hasFocus = false;
}
/**
* Sets the internal state when the browser w becomes visible or is in focus.
*
* @since 1.9.3
* @access private
*
* @return {void}
*/
function focused() {
settings.userActivity = time();
// Resume if suspended
settings.suspend = false;
if ( ! settings.hasFocus ) {
settings.hasFocus = true;
scheduleNextTick();
}
}
/**
* Runs when the user becomes active after a period of inactivity.
*
* @since 1.9.3
* @access private
*
* @return {void}
*/
function userIsActive() {
settings.userActivityEvents = false;
$document.off( '.imagifybeat-active' );
$( 'iframe' ).each( function( i, frame ) {
if ( isLocalFrame( frame ) ) {
$( frame.contentWindow ).off( '.imagifybeat-active' );
}
} );
focused();
}
/**
* Checks for user activity.
*
* Runs every 30 sec. Sets 'hasFocus = true' if user is active and the w is
* in the background. Sets 'hasFocus = false' if the user has been inactive
* (no mouse or keyboard activity) for 5 min. even when the w has focus.
*
* @since 1.9.3
* @access private
*
* @return {void}
*/
function checkUserActivity() {
var lastActive = settings.userActivity ? time() - settings.userActivity : 0;
// Set hasFocus to false when no mouse or keyboard activity for 5 min.
if ( lastActive > 300000 && settings.hasFocus ) {
blurred();
}
// Suspend after 10 min. of inactivity.
if ( settings.suspendEnabled && lastActive > 600000 ) {
settings.suspend = true;
}
if ( ! settings.userActivityEvents ) {
$document.on( 'mouseover.imagifybeat-active keyup.imagifybeat-active touchend.imagifybeat-active', function() {
userIsActive();
} );
$( 'iframe' ).each( function( i, frame ) {
if ( isLocalFrame( frame ) ) {
$( frame.contentWindow ).on( 'mouseover.imagifybeat-active keyup.imagifybeat-active touchend.imagifybeat-active', function() {
userIsActive();
} );
}
} );
settings.userActivityEvents = true;
}
}
// Public methods.
/**
* Checks whether the w (or any local iframe in it) has focus, or the user
* is active.
*
* @since 1.9.3
* @memberOf imagify.beat.prototype
*
* @return {bool} True if the w or the user is active.
*/
function hasFocus() {
return settings.hasFocus;
}
/**
* Checks whether there is a connection error.
*
* @since 1.9.3
* @memberOf imagify.beat.prototype
*
* @return {bool} True if a connection error was found.
*/
function hasConnectionError() {
return settings.connectionError;
}
/**
* Connects as soon as possible regardless of 'hasFocus' state.
*
* Will not open two concurrent connections. If a connection is in progress,
* will connect again immediately after the current connection completes.
*
* @since 1.9.3
* @memberOf imagify.beat.prototype
*
* @return {void}
*/
function connectNow() {
settings.lastTick = 0;
scheduleNextTick();
}
/**
* Disables suspending.
*
* Should be used only when Imagifybeat is performing critical tasks like
* autosave, post-locking, etc. Using this on many screens may overload the
* user's hosting account if several browser ws/tabs are left open for a
* long time.
*
* @since 1.9.3
* @memberOf imagify.beat.prototype
*
* @return {void}
*/
function disableSuspend() {
settings.suspendEnabled = false;
}
/**
* Enables suspending.
*
* @since 1.9.3
* @memberOf imagify.beat.prototype
*
* @return {void}
*/
function enableSuspend() {
settings.suspendEnabled = true;
}
/**
* Gets/Sets the interval.
*
* When setting to 'fast' or 5, the interval is 5 seconds for the next 30 ticks
* (for 2 minutes and 30 seconds) by default. In this case the number of 'ticks'
* can be passed as second argument. If the window doesn't have focus, the
* interval slows down to 2 min.
*
* @since 1.9.3
* @memberOf imagify.beat.prototype
*
* @param {string|int} speed Interval: 'fast' or 5, 15, 30, 60, 120. Fast equals 5.
* @param {string} ticks Tells how many ticks before the interval reverts back. Used with speed = 'fast' or 5.
* @return {int} Current interval in seconds.
*/
function interval( speed, ticks ) {
var newInterval,
oldInterval = settings.tempInterval ? settings.tempInterval : settings.mainInterval;
if ( speed ) {
switch ( speed ) {
case 'fast':
case 5:
newInterval = 5000;
break;
case 15:
newInterval = 15000;
break;
case 30:
newInterval = 30000;
break;
case 60:
newInterval = 60000;
break;
case 120:
newInterval = 120000;
break;
case 'long-polling':
// Allow long polling, (experimental)
settings.mainInterval = 0;
return 0;
default:
newInterval = settings.originalInterval;
}
if ( settings.minimalInterval && newInterval < settings.minimalInterval ) {
newInterval = settings.minimalInterval;
}
if ( 5000 === newInterval ) {
ticks = parseInt( ticks, 10 ) || 30;
ticks = ticks < 1 || ticks > 30 ? 30 : ticks;
settings.countdown = ticks;
settings.tempInterval = newInterval;
} else {
settings.countdown = 0;
settings.tempInterval = 0;
settings.mainInterval = newInterval;
}
// Change the next connection time if new interval has been set.
// Will connect immediately if the time since the last connection
// is greater than the new interval.
if ( newInterval !== oldInterval ) {
scheduleNextTick();
}
}
return settings.tempInterval ? settings.tempInterval / 1000 : settings.mainInterval / 1000;
}
/**
* Resets the interval.
*
* @since 1.9.3
* @memberOf imagify.beat.prototype
*
* @return {int} Current interval in seconds.
*/
function resetInterval() {
return interval( settings.originalInterval );
}
/**
* Enqueues data to send with the next XHR.
*
* As the data is send asynchronously, this function doesn't return the XHR
* response. To see the response, use the custom jQuery event 'imagifybeat-tick'
* on the document, example:
* $(document).on( 'imagifybeat-tick.myname', function( event, data, textStatus, jqXHR ) {
* // code
* });
* If the same 'handle' is used more than once, the data is not overwritten when
* the third argument is 'true'. Use `imagify.beat.isQueued('handle')` to see if
* any data is already queued for that handle.
*
* @since 1.9.3
* @memberOf imagify.beat.prototype
*
* @param {string} handle Unique handle for the data, used in PHP to receive the data.
* @param {mixed} data The data to send.
* @param {bool} noOverwrite Whether to overwrite existing data in the queue.
* @return {bool} True if the data was queued.
*/
function enqueue( handle, data, noOverwrite ) {
if ( handle ) {
if ( noOverwrite && this.isQueued( handle ) ) {
return false;
}
settings.queue[handle] = data;
return true;
}
return false;
}
/**
* Checks if data with a particular handle is queued.
*
* @since 1.9.3
*
* @param {string} handle The handle for the data.
* @return {bool} True if the data is queued with this handle.
*/
function isQueued( handle ) {
if ( handle ) {
return Object.prototype.hasOwnProperty.call( settings.queue, handle );
}
}
/**
* Removes data with a particular handle from the queue.
*
* @since 1.9.3
* @memberOf imagify.beat.prototype
*
* @param {string} handle The handle for the data.
*/
function dequeue( handle ) {
if ( handle ) {
delete settings.queue[handle];
}
}
/**
* Gets data that was enqueued with a particular handle.
*
* @since 1.9.3
* @memberOf imagify.beat.prototype
*
* @param {string} handle The handle for the data.
* @return {mixed} The data or undefined.
*/
function getQueuedItem( handle ) {
if ( handle ) {
return this.isQueued( handle ) ? settings.queue[ handle ] : undefined;
}
}
initialize();
// Expose public methods.
return {
hasFocus: hasFocus,
connectNow: connectNow,
disableSuspend: disableSuspend,
enableSuspend: enableSuspend,
interval: interval,
resetInterval: resetInterval,
hasConnectionError: hasConnectionError,
enqueue: enqueue,
dequeue: dequeue,
isQueued: isQueued,
getQueuedItem: getQueuedItem
};
};
/**
* Contains the Imagifybeat API.
*
* @namespace imagify.beat
* @type {Imagifybeat}
*/
w.imagify.beat = new Imagifybeat();
} )( jQuery, document, window );

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,457 @@
/**
* Mini chart.
*
* @param {element} canvas The canvas element.
*/
window.imagify.drawMeAChart = function( canvas ) {
canvas.each( function() {
var value = parseInt( jQuery( this ).closest( '.imagify-chart' ).next( '.imagify-chart-value' ).text(), 10 );
new window.imagify.Chart( this, { // eslint-disable-line no-new
type: 'doughnut',
data: {
datasets: [ {
data: [ value, 100 - value ],
backgroundColor: [ '#00B3D3', '#D8D8D8' ],
borderColor: '#fff',
borderWidth: 1
} ]
},
options: {
legend: {
display: false
},
events: [],
animation: {
easing: 'easeOutBounce'
},
tooltips: {
enabled: false
},
responsive: false
}
} );
} );
};
(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
w.imagify.filesList = {
working: [],
/*
* Init.
*/
init: function () {
var $document = $( d ),
$processing;
// Update the chart in the media modal when a media is selected, and the ones already printed.
$( w ).on( 'canvasprinted.imagify', this.updateChart ).trigger( 'canvasprinted.imagify' );
// Handle bulk actions.
this.insertBulkActionTags();
$( '#doaction, #doaction2' ).on( 'click.imagify', this.processBulkAction );
// Optimize, restore, etc.
$document.on( 'click.imagify', '.button-imagify-optimize, .button-imagify-manual-reoptimize, .button-imagify-generate-webp, .button-imagify-delete-webp, .button-imagify-restore, .button-imagify-refresh-status', this.processOptimization );
$document.on( 'imagifybeat-send', this.addToImagifybeat );
$document.on( 'imagifybeat-tick', this.processImagifybeat );
// Some items may be processed in background on page load.
$processing = $( '.wp-list-table.imagify-files .button-imagify-processing' );
if ( $processing.length ) {
// Some media are already being processed.
// Lock the items, so we can check their status with Imagifybeat.
$processing.closest( 'tr' ).find( '.check-column [name="bulk_select[]"]' ).each( function() {
var id = w.imagify.filesList.sanitizeId( this.value );
w.imagify.filesList.lockItem( w.imagifyFiles.context, id );
} );
// Fasten Imagifybeat.
w.imagify.beat.interval( 15 );
}
},
// Charts ==================================================================================
/**
* Update the chart in the media modal when a media is selected, and the ones already printed.
*
* @param {object} e Event.
* @param {string} selector A CSS selector.
*/
updateChart: function( e, selector ) {
var $canvas;
selector = selector || '.imagify-consumption-chart';
$canvas = $( selector );
w.imagify.drawMeAChart( $canvas );
$canvas.closest( '.imagify-datas-list' ).siblings( '.imagify-datas-details' ).hide();
},
// Bulk optimization =======================================================================
/**
* Insert the bulk actions in the <select> tag.
*/
insertBulkActionTags: function() {
var bulkActions = '<option value="imagify-bulk-optimize">' + w.imagifyFiles.labels.bulkActionsOptimize + '</option>';
if ( w.imagifyFiles.backupOption || $( '.file-has-backup' ).length ) {
// If the backup option is enabled, or if we have items that can be restored.
bulkActions += '<option value="imagify-bulk-restore">' + w.imagifyFiles.labels.bulkActionsRestore + '</option>';
}
$( '.bulkactions select[name="action"] option:first-child, .bulkactions select[name="action2"] option:first-child' ).after( bulkActions );
},
/**
* Process one of these actions: bulk restore, bulk optimize, or bulk refresh-status.
*
* @param {object} e Event.
*/
processBulkAction: function( e ) {
var value = $( this ).prev( 'select' ).val(),
action;
if ( 'imagify-bulk-optimize' !== value && 'imagify-bulk-restore' !== value && 'imagify-bulk-refresh-status' !== value ) {
return;
}
e.preventDefault();
action = value.replace( 'imagify-bulk-', '' );
$( 'input[name="bulk_select[]"]:checked' ).closest( 'tr' ).find( '.button-imagify-' + action ).each( function ( index, el ) {
setTimeout( function() {
$( el ).trigger( 'click.imagify' );
}, index * 500 );
} );
},
// Optimization ============================================================================
/**
* Process one of these actions: optimize, re-optimize, restore, or refresh-status.
*
* @param {object} e Event.
*/
processOptimization: function( e ) {
var $button = $( this ),
$row = $button.closest( 'tr' ),
$checkbox = $row.find( '.check-column [type="checkbox"]' ),
id = imagify.filesList.sanitizeId( $checkbox.val() ),
context = w.imagifyFiles.context,
$parent, href, processingTemplate;
e.preventDefault();
if ( imagify.filesList.isItemLocked( context, id ) ) {
return;
}
imagify.filesList.lockItem( context, id );
href = $button.attr( 'href' );
processingTemplate = w.imagify.template( 'imagify-button-processing' );
$parent = $button.closest( '.column-actions, .column-status' );
$parent.html( processingTemplate( {
label: $button.data( 'processing-label' )
} ) );
$.get( href.replace( 'admin-post.php', 'admin-ajax.php' ) )
.done( function( r ) {
if ( ! r.success ) {
if ( r.data && r.data.row ) {
$row.html( '<td class="colspanchange" colspan="' + $row.children().length + '">' + r.data.row + '</td>' );
} else {
$parent.html( r.data );
}
$row.find( '.check-column [type="checkbox"]' ).prop( 'checked', false );
imagify.filesList.unlockItem( context, id );
return;
}
if ( r.data && r.data.columns ) {
// The work is done.
w.imagify.filesList.displayProcessResult( context, id, r.data.columns );
} else {
// Still processing in background: we're waiting for the result by poking Imagifybeat.
// Set the Imagifybeat interval to 15 seconds.
w.imagify.beat.interval( 15 );
}
} );
},
// Imagifybeat =============================================================================
/**
* Send the media IDs and their status to Imagifybeat.
*
* @param {object} e Event object.
* @param {object} data Object containing all Imagifybeat IDs.
*/
addToImagifybeat: function ( e, data ) {
var $boxes = $( '.wp-list-table.imagify-files .check-column [name="bulk_select[]"]' );
if ( ! $boxes.length ) {
return;
}
data[ w.imagifyFiles.imagifybeatID ] = {};
$boxes.each( function() {
var id = w.imagify.filesList.sanitizeId( this.value ),
context = w.imagifyFiles.context,
locked = w.imagify.filesList.isItemLocked( context, id ) ? 1 : 0;
data[ w.imagifyFiles.imagifybeatID ][ context ] = data[ w.imagifyFiles.imagifybeatID ][ context ] || {};
data[ w.imagifyFiles.imagifybeatID ][ context ][ '_' + id ] = locked;
} );
},
/**
* Listen for the custom event "imagifybeat-tick" on $(document).
*
* @param {object} e Event object.
* @param {object} data Object containing all Imagifybeat IDs.
*/
processImagifybeat: function ( e, data ) {
if ( typeof data[ w.imagifyFiles.imagifybeatID ] === 'undefined' ) {
return;
}
$.each( data[ w.imagifyFiles.imagifybeatID ], function( contextId, columns ) {
var context, id;
context = $.trim( contextId ).match( /^(.+)_(\d+)$/ );
if ( ! context ) {
return;
}
id = w.imagify.filesList.sanitizeId( context[2] );
context = w.imagify.filesList.sanitizeContext( context[1] );
if ( context !== w.imagifyFiles.context ) {
return;
}
w.imagify.filesList.displayProcessResult( context, id, columns );
} );
},
// DOM manipulation tools ==================================================================
/**
* Display a successful process result.
*
* @param {string} context The media context.
* @param {int} id The media ID.
* @param {string} columns A list of HTML, keyed by column name.
*/
displayProcessResult: function( context, id, columns ) {
var $row = w.imagify.filesList.getContainers( id );
$.each( columns, function( k, v ) {
$row.children( '.column-' + k ).html( v );
} );
$row.find( '.check-column [type="checkbox"]' ).prop( 'checked', false );
w.imagify.filesList.unlockItem( context, id );
if ( ! w.imagify.filesList.working.length ) {
// Work is done.
// Reset Imagifybeat interval.
w.imagify.beat.resetInterval();
}
},
/**
* Get all containers matching the given id.
*
* @param {int} id The media ID.
* @return {object} A jQuery collection.
*/
getContainers: function( id ) {
return $( '.wp-list-table.imagify-files .check-column [name="bulk_select[]"][value="' + id + '"]' ).closest( 'tr' );
},
// Sanitization ============================================================================
/**
* Sanitize a media ID.
*
* @param {int|string} id A media ID.
* @return {int}
*/
sanitizeId: function( id ) {
return parseInt( id, 10 );
},
/**
* Sanitize a media context.
*
* @param {string} context A media context.
* @return {string}
*/
sanitizeContext: function( context ) {
context = context.replace( '/[^a-z0-9_-]/gi', '' ).toLowerCase();
return context ? context : 'wp';
},
// Locks ===================================================================================
/**
* Lock an item.
*
* @param {string} context The media context.
* @param {int} id The media ID.
*/
lockItem: function( context, id ) {
if ( ! this.isItemLocked( context, id ) ) {
this.working.push( context + '_' + id );
}
},
/**
* Unlock an item.
*
* @param {string} context The media context.
* @param {int} id The media ID.
*/
unlockItem: function( context, id ) {
var name = context + '_' + id,
i = _.indexOf( this.working, name );
if ( i > -1 ) {
this.working.splice( i, 1 );
}
},
/**
* Tell if an item is locked.
*
* @param {string} context The media context.
* @param {int} id The media ID.
* @return {bool}
*/
isItemLocked: function( context, id ) {
return _.indexOf( this.working, context + '_' + id ) > -1;
}
};
w.imagify.filesList.init();
} )(jQuery, document, window);
(function(w) { // eslint-disable-line no-shadow, no-shadow-restricted-names
/**
* requestAnimationFrame polyfill by Erik Möller.
* Fixes from Paul Irish and Tino Zijdel.
* MIT license - http://paulirish.com/2011/requestanimationframe-for-smart-animating/ - http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating.
*/
var lastTime = 0,
vendors = ['ms', 'moz', 'webkit', 'o'];
for ( var x = 0; x < vendors.length && ! w.requestAnimationFrame; ++x ) {
w.requestAnimationFrame = w[vendors[x] + 'RequestAnimationFrame'];
w.cancelAnimationFrame = w[vendors[x] + 'CancelAnimationFrame'] || w[vendors[x] + 'CancelRequestAnimationFrame'];
}
if ( ! w.requestAnimationFrame ) {
w.requestAnimationFrame = function( callback ) {
var currTime = new Date().getTime(),
timeToCall = Math.max( 0, 16 - ( currTime - lastTime ) ),
id = setTimeout( function() {
callback( currTime + timeToCall );
}, timeToCall );
lastTime = currTime + timeToCall;
return id;
};
}
if ( ! w.cancelAnimationFrame ) {
w.cancelAnimationFrame = function( id ) {
clearTimeout( id );
};
}
})(window);
(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
/**
* LazyLoad images in the list.
*/
var lazyImages = $( '#imagify-files-list-form' ).find( '[data-lazy-src]' ),
lazyTimer;
function lazyLoadThumbnails() {
w.cancelAnimationFrame( lazyTimer );
lazyTimer = w.requestAnimationFrame( lazyLoadThumbnailsCallback ); // eslint-disable-line no-use-before-define
}
function lazyLoadThumbnailsCallback() {
var $w = $( w ),
winScroll = $w.scrollTop(),
winHeight = $w.outerHeight();
$.each( lazyImages, function() {
var $image = $( this ),
imgTop = $image.offset().top,
imgBottom = imgTop + $image.outerHeight(),
screenTopThresholded = winScroll - 150,
screenBottomThresholded = winScroll + winHeight + 150,
src;
lazyImages = lazyImages.not( $image );
if ( ! lazyImages.length ) {
$w.off( 'scroll resize orientationchange', lazyLoadThumbnails );
}
/**
* Hidden images that are above the fold and below the top, are reported as:
* - offset: window scroll,
* - height: 0,
* (at least in Firefox).
* That's why I use <= and >=.
*
* 150 is the threshold.
*/
if ( imgBottom >= screenTopThresholded && imgTop <= screenBottomThresholded ) {
src = $image.attr( 'data-lazy-src' );
if ( undefined !== src && src ) {
$image.attr( 'src', src ).removeAttr( 'data-lazy-src' );
}
$image.next( 'noscript' ).remove();
}
} );
}
if ( lazyImages.length ) {
$( w ).on( 'scroll resize orientationchange', lazyLoadThumbnails );
lazyLoadThumbnailsCallback();
}
} )(jQuery, document, window);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,400 @@
/**
* Library that handles the bulk optimization processes.
*
* @requires jQuery
*/
window.imagify = window.imagify || {};
/* eslint-disable no-underscore-dangle, consistent-this */
(function($, d, w) {
/**
* Construct the optimizer.
*
* @param {object} settings {
* Optimizer settings:
*
* @type {string} groupID Group ID, like 'library' or 'custom-folders'.
* @type {string} context Context within this group, like 'wp' or 'custom-folders' (yes, again).
* @type {int} level Optimization level: 0 to 2.
* @type {int} bufferSize Number of parallel optimizations: usually 4.
* @type {string} ajaxUrl URL to request to optimize.
* @type {object} files Files to optimize: media ID as key (prefixed with an underscore), file URL as value.
* @type {string} defaultThumb A default thumbnail URL.
* @type {string} doneEvent Name of the event to listen to know when optimizations end.
* @type {array} imageExtensions A list of supported image extensions (only images).
* }
*/
w.imagify.Optimizer = function ( settings ) {
// Settings.
this.groupID = settings.groupID;
this.context = settings.context;
this.level = settings.level;
this.bufferSize = settings.bufferSize || 1;
this.ajaxUrl = settings.ajaxUrl;
this.files = settings.files;
this.defaultThumb = settings.defaultThumb;
this.doneEvent = settings.doneEvent;
if ( settings.imageExtensions ) {
this.imageExtensions = settings.imageExtensions;
} else {
this.imageExtensions = [ 'jpg', 'jpeg', 'jpe', 'png', 'gif' ];
}
/**
* An array of media IDs (prefixed with an underscore).
*/
this.prefixedMediaIDs = Object.keys( this.files );
/**
* An array of medias currently being optimized: {
* @type {int} mediaID The media ID.
* @type {string} filename The file name.
* @type {string} thumbnail The file thumbnail URL.
* }
*/
this.currentItems = [];
// Internal counters.
this.totalMedia = this.prefixedMediaIDs.length;
this.processedMedia = 0;
// Global stats.
this.globalOriginalSize = 0;
this.globalOptimizedSize = 0;
this.globalGain = 0;
this.globalPercent = 0;
// Callbacks.
this._before = function () {};
this._each = function () {};
this._done = function () {};
// Listen to the "optimization done" event.
if ( this.totalMedia && this.doneEvent ) {
$( w ).on( this.doneEvent, { _this: this }, this.processedCallback );
}
};
/**
* Callback to trigger before each media optimization.
*
* @param {callable} fnc A callback.
* @return this
*/
w.imagify.Optimizer.prototype.before = function( fnc ) {
this._before = fnc;
return this;
};
/**
* Callback to trigger after each media optimization.
*
* @param {callable} fnc A callback.
* @return this
*/
w.imagify.Optimizer.prototype.each = function( fnc ) {
this._each = fnc;
return this;
};
/**
* Callback to trigger all media optimizations have been done.
*
* @param {callable} fnc A callback.
* @return this
*/
w.imagify.Optimizer.prototype.done = function( fnc ) {
this._done = fnc;
return this;
};
/**
* Launch optimizations.
*
* @return this
*/
w.imagify.Optimizer.prototype.run = function() {
var chunkLength = this.prefixedMediaIDs.length > this.bufferSize ? this.bufferSize : this.prefixedMediaIDs.length,
i;
for ( i = 0; i < chunkLength; i++ ) {
this.processNext();
}
return this;
};
/**
* Launch next optimization.
*
* @return this
*/
w.imagify.Optimizer.prototype.processNext = function() {
if ( this.prefixedMediaIDs.length ) {
this.process( this.prefixedMediaIDs.shift() );
}
return this;
};
/**
* Launch an optimization.
*
* @param {string} prefixedId A media ID, prefixed with an underscore.
* @return this
*/
w.imagify.Optimizer.prototype.process = function( prefixedId ) {
var _this = this,
fileURL = this.files[ prefixedId ],
data = {
mediaID: parseInt( prefixedId.toString().substr( 1 ), 10 ),
filename: this.files[ prefixedId ].split( '/' ).pop(),
thumbnail: this.defaultThumb
},
extension = data.filename.split( '.' ).pop().toLowerCase(),
regexp = new RegExp( '^' + this.imageExtensions.join( '|' ).toLowerCase() + '$' ),
image;
if ( ! extension.match( regexp ) ) {
// Not an image.
this.currentItems.push( data );
this._before( data );
this.send( data );
return this;
}
// Create a thumbnail and send the ajax request.
image = new Image();
image.onerror = function () {
_this.currentItems.push( data );
_this._before( data );
_this.send( data );
};
image.onload = function () {
var maxWidth = 33,
maxHeight = 33,
imageWidth = image.width,
imageHeight = image.height,
newHeight = 0,
newWidth = 0,
topOffset = 0,
leftOffset = 0,
canvas = null,
ctx = null;
if ( imageWidth < imageHeight ) {
// Portrait.
newWidth = maxWidth;
newHeight = newWidth * imageHeight / imageWidth;
topOffset = ( maxHeight - newHeight ) / 2;
} else {
// Landscape.
newHeight = maxHeight;
newWidth = newHeight * imageWidth / imageHeight;
leftOffset = ( maxWidth - newWidth ) / 2;
}
canvas = d.createElement( 'canvas' );
canvas.width = maxWidth;
canvas.height = maxHeight;
ctx = canvas.getContext( '2d' );
ctx.drawImage( this, leftOffset, topOffset, newWidth, newHeight );
try {
data.thumbnail = canvas.toDataURL( 'image/jpeg' );
} catch ( e ) {
data.thumbnail = _this.defaultThumb;
}
canvas = null;
ctx = null;
image = null;
_this.currentItems.push( data );
_this._before( data );
_this.send( data );
};
image.src = fileURL;
return this;
};
/**
* Do the ajax request.
*
* @param {object} data {
* The data:
*
* @type {int} mediaID The media ID.
* @type {string} filename The file name.
* @type {string} thumbnail The file thumbnail URL.
* }
* @return this
*/
w.imagify.Optimizer.prototype.send = function( data ) {
var _this = this,
defaultResponse = {
success: false,
mediaID: data.mediaID,
groupID: this.groupID,
context: this.context,
filename: data.filename,
thumbnail: data.thumbnail,
status: 'error',
error: ''
};
$.post( {
url: this.ajaxUrl,
data: {
media_id: data.mediaID,
context: this.context,
optimization_level: this.level
},
dataType: 'json'
} )
.done( function( response ) {
if ( response.success ) {
return;
}
defaultResponse.error = response.data.error;
_this.processed( defaultResponse );
} )
.fail( function( jqXHR ) {
if ( 200 === jqXHR.status ) {
defaultResponse.error = jqXHR.responseText.replace( /<h1>.*<\/h1>\n*/, '' );
} else {
defaultResponse.error = jqXHR.statusText;
}
_this.processed( defaultResponse );
} );
return this;
};
/**
* Callback triggered when an optimization is complete.
*
* @param {object} e jQuery's Event object.
* @param {object} item {
* The response:
*
* @type {int} mediaID The media ID.
* @type {string} context The context.
* }
*/
w.imagify.Optimizer.prototype.processedCallback = function( e, item ) {
var _this = e.data._this;
if ( item.context !== _this.context ) {
return;
}
if ( ! item.mediaID || typeof _this.files[ '_' + item.mediaID ] === 'undefined' ) {
return;
}
item.groupID = _this.groupID;
if ( ! _this.currentItems.length ) {
// Trouble.
_this.processed( item );
return;
}
$.each( _this.currentItems, function( i, mediaData ) {
if ( item.mediaID === mediaData.mediaID ) {
item.filename = mediaData.filename;
item.thumbnail = mediaData.thumbnail;
return false;
}
} );
_this.processed( item );
};
/**
* After a media has been processed.
*
* @param {object} response {
* The response:
*
* @type {bool} success Whether the optimization succeeded or not ("already optimized" is a success).
* @type {int} mediaID The media ID.
* @type {string} groupID The group ID.
* @type {string} context The context.
* @type {string} filename The file name.
* @type {string} thumbnail The file thumbnail URL.
* @type {string} status The status, like 'optimized', 'already-optimized', 'over-quota', 'error'.
* @type {string} error The error message.
* }
* @return this
*/
w.imagify.Optimizer.prototype.processed = function( response ) {
var currentItems = this.currentItems;
if ( currentItems.length ) {
// Remove this media from the "current" list.
$.each( currentItems, function( i, mediaData ) {
if ( response.mediaID === mediaData.mediaID ) {
currentItems.splice( i, 1 );
return false;
}
} );
this.currentItems = currentItems;
}
// Update stats.
if ( response.success && 'already-optimized' !== response.status ) {
this.globalOriginalSize += response.originalOverallSize;
this.globalOptimizedSize += response.newOverallSize;
this.globalGain += response.overallSaving;
this.globalPercent = ( 100 - this.globalOptimizedSize / this.globalOptimizedSize * 100 ).toFixed( 2 );
}
++this.processedMedia;
response.progress = Math.floor( this.processedMedia / this.totalMedia * 100 );
this._each( response );
if ( this.prefixedMediaIDs.length ) {
this.processNext();
} else if ( this.totalMedia === this.processedMedia ) {
this._done( {
globalOriginalSize: this.globalOriginalSize,
globalOptimizedSize: this.globalOptimizedSize,
globalGain: this.globalGain
} );
}
return this;
};
/**
* Stop the process.
*
* @return this
*/
w.imagify.Optimizer.prototype.stopProcess = function() {
this.files = {};
this.prefixedMediaIDs = [];
this.currentItems = [];
if ( this.doneEvent ) {
$( w ).off( this.doneEvent, this.processedCallback );
}
return this;
};
} )(jQuery, document, window);

View File

@@ -0,0 +1,396 @@
/**
* Library that handles the bulk optimization processes.
*
* @requires jQuery
*/
window.imagify = window.imagify || {};
/* eslint-disable no-underscore-dangle, consistent-this */
(function($, d, w) {
/**
* Construct the optimizer.
*
* @param {object} settings {
* Optimizer settings:
*
* @type {string} groupID Group ID, like 'library' or 'custom-folders'.
* @type {string} context Context within this group, like 'wp' or 'custom-folders' (yes, again).
* @type {int} level Optimization level: 0 to 2.
* @type {int} bufferSize Number of parallel optimizations: usually 4.
* @type {string} ajaxUrl URL to request to optimize.
* @type {object} files Files to optimize: media ID as key (prefixed with an underscore), file URL as value.
* @type {string} defaultThumb A default thumbnail URL.
* @type {string} doneEvent Name of the event to listen to know when optimizations end.
* @type {array} imageExtensions A list of supported image extensions (only images).
* }
*/
w.imagify.Optimizer = function ( settings ) {
// Settings.
this.groupID = settings.groupID;
this.context = settings.context;
this.level = settings.level;
this.bufferSize = settings.bufferSize || 1;
this.ajaxUrl = settings.ajaxUrl;
this.files = settings.files;
this.defaultThumb = settings.defaultThumb;
this.doneEvent = settings.doneEvent;
if ( settings.imageExtensions ) {
this.imageExtensions = settings.imageExtensions;
} else {
this.imageExtensions = [ 'jpg', 'jpeg', 'jpe', 'png', 'gif' ];
}
/**
* An array of media IDs (prefixed with an underscore).
*/
this.prefixedMediaIDs = Object.keys( this.files );
/**
* An array of medias currently being optimized: {
* @type {int} mediaID The media ID.
* @type {string} filename The file name.
* @type {string} thumbnail The file thumbnail URL.
* }
*/
this.currentItems = [];
// Internal counters.
this.totalMedia = this.prefixedMediaIDs.length;
this.processedMedia = 0;
// Global stats.
this.globalOriginalSize = 0;
this.globalOptimizedSize = 0;
this.globalGain = 0;
this.globalPercent = 0;
// Callbacks.
this._before = function () {};
this._each = function () {};
this._done = function () {};
// Listen to the "optimization done" event.
if ( this.totalMedia && this.doneEvent ) {
$( w ).on( this.doneEvent, { _this: this }, this.processedCallback );
}
};
/**
* Callback to trigger before each media optimization.
*
* @param {callable} fnc A callback.
* @return this
*/
w.imagify.Optimizer.prototype.before = function( fnc ) {
this._before = fnc;
return this;
};
/**
* Callback to trigger after each media optimization.
*
* @param {callable} fnc A callback.
* @return this
*/
w.imagify.Optimizer.prototype.each = function( fnc ) {
this._each = fnc;
return this;
};
/**
* Callback to trigger all media optimizations have been done.
*
* @param {callable} fnc A callback.
* @return this
*/
w.imagify.Optimizer.prototype.done = function( fnc ) {
this._done = fnc;
return this;
};
/**
* Launch optimizations.
*
* @return this
*/
w.imagify.Optimizer.prototype.run = function() {
var chunkLength = this.prefixedMediaIDs.length > this.bufferSize ? this.bufferSize : this.prefixedMediaIDs.length,
i;
for ( i = 0; i < chunkLength; i++ ) {
this.processNext();
}
return this;
};
/**
* Launch next optimization.
*
* @return this
*/
w.imagify.Optimizer.prototype.processNext = function() {
if ( this.prefixedMediaIDs.length ) {
this.process( this.prefixedMediaIDs.shift() );
}
return this;
};
/**
* Launch an optimization.
*
* @param {string} prefixedId A media ID, prefixed with an underscore.
* @return this
*/
w.imagify.Optimizer.prototype.process = function( prefixedId ) {
var _this = this,
fileURL = this.files[ prefixedId ],
data = {
mediaID: parseInt( prefixedId.toString().substr( 1 ), 10 ),
filename: this.files[ prefixedId ].split( '/' ).pop(),
thumbnail: this.defaultThumb
},
extension = data.filename.split( '.' ).pop().toLowerCase(),
regexp = new RegExp( '^' + this.imageExtensions.join( '|' ).toLowerCase() + '$' ),
image;
if ( ! extension.match( regexp ) ) {
// Not an image.
this.currentItems.push( data );
this._before( data );
this.send( data );
return this;
}
// Create a thumbnail and send the ajax request.
image = new Image();
image.onerror = function () {
_this.currentItems.push( data );
_this._before( data );
_this.send( data );
};
image.onload = function () {
var maxWidth = 33,
maxHeight = 33,
imageWidth = image.width,
imageHeight = image.height,
newHeight = 0,
newWidth = 0,
topOffset = 0,
leftOffset = 0,
canvas = null,
ctx = null;
if ( imageWidth < imageHeight ) {
// Portrait.
newWidth = maxWidth;
newHeight = newWidth * imageHeight / imageWidth;
topOffset = ( maxHeight - newHeight ) / 2;
} else {
// Landscape.
newHeight = maxHeight;
newWidth = newHeight * imageWidth / imageHeight;
leftOffset = ( maxWidth - newWidth ) / 2;
}
canvas = d.createElement( 'canvas' );
canvas.width = maxWidth;
canvas.height = maxHeight;
ctx = canvas.getContext( '2d' );
ctx.drawImage( this, leftOffset, topOffset, newWidth, newHeight );
try {
data.thumbnail = canvas.toDataURL( 'image/jpeg' );
} catch ( e ) {
data.thumbnail = _this.defaultThumb;
}
canvas = null;
ctx = null;
image = null;
_this.currentItems.push( data );
_this._before( data );
_this.send( data );
};
image.src = fileURL;
return this;
};
/**
* Do the ajax request.
*
* @param {object} data {
* The data:
*
* @type {int} mediaID The media ID.
* @type {string} filename The file name.
* @type {string} thumbnail The file thumbnail URL.
* }
* @return this
*/
w.imagify.Optimizer.prototype.send = function( data ) {
var _this = this,
defaultResponse = {
success: false,
mediaID: data.mediaID,
groupID: this.groupID,
context: this.context,
filename: data.filename,
thumbnail: data.thumbnail,
status: 'error',
error: ''
};
$.post( {
url: this.ajaxUrl,
data: {
media_id: data.mediaID,
context: this.context,
optimization_level: this.level
},
dataType: 'json'
} )
.done( function( response ) {
if ( response.success ) {
return;
}
defaultResponse.error = response.data.error;
_this.processed( defaultResponse );
} )
.fail( function( jqXHR ) {
defaultResponse.error = jqXHR.statusText;
_this.processed( defaultResponse );
} );
return this;
};
/**
* Callback triggered when an optimization is complete.
*
* @param {object} e jQuery's Event object.
* @param {object} item {
* The response:
*
* @type {int} mediaID The media ID.
* @type {string} context The context.
* }
*/
w.imagify.Optimizer.prototype.processedCallback = function( e, item ) {
var _this = e.data._this;
if ( item.context !== _this.context ) {
return;
}
if ( ! item.mediaID || typeof _this.files[ '_' + item.mediaID ] === 'undefined' ) {
return;
}
item.groupID = _this.groupID;
if ( ! _this.currentItems.length ) {
// Trouble.
_this.processed( item );
return;
}
$.each( _this.currentItems, function( i, mediaData ) {
if ( item.mediaID === mediaData.mediaID ) {
item.filename = mediaData.filename;
item.thumbnail = mediaData.thumbnail;
return false;
}
} );
_this.processed( item );
};
/**
* After a media has been processed.
*
* @param {object} response {
* The response:
*
* @type {bool} success Whether the optimization succeeded or not ("already optimized" is a success).
* @type {int} mediaID The media ID.
* @type {string} groupID The group ID.
* @type {string} context The context.
* @type {string} filename The file name.
* @type {string} thumbnail The file thumbnail URL.
* @type {string} status The status, like 'optimized', 'already-optimized', 'over-quota', 'error'.
* @type {string} error The error message.
* }
* @return this
*/
w.imagify.Optimizer.prototype.processed = function( response ) {
var currentItems = this.currentItems;
if ( currentItems.length ) {
// Remove this media from the "current" list.
$.each( currentItems, function( i, mediaData ) {
if ( response.mediaID === mediaData.mediaID ) {
currentItems.splice( i, 1 );
return false;
}
} );
this.currentItems = currentItems;
}
// Update stats.
if ( response.success && 'already-optimized' !== response.status ) {
this.globalOriginalSize += response.originalOverallSize;
this.globalOptimizedSize += response.newOverallSize;
this.globalGain += response.overallSaving;
this.globalPercent = ( 100 - this.globalOptimizedSize / this.globalOptimizedSize * 100 ).toFixed( 2 );
}
++this.processedMedia;
response.progress = Math.floor( this.processedMedia / this.totalMedia * 100 );
this._each( response );
if ( this.prefixedMediaIDs.length ) {
this.processNext();
} else if ( this.totalMedia === this.processedMedia ) {
this._done( {
globalOriginalSize: this.globalOriginalSize,
globalOptimizedSize: this.globalOptimizedSize,
globalGain: this.globalGain
} );
}
return this;
};
/**
* Stop the process.
*
* @return this
*/
w.imagify.Optimizer.prototype.stopProcess = function() {
this.files = {};
this.prefixedMediaIDs = [];
this.currentItems = [];
if ( this.doneEvent ) {
$( w ).off( this.doneEvent, this.processedCallback );
}
return this;
};
} )(jQuery, document, window);

View File

@@ -0,0 +1,584 @@
// DOM.event.move
//
// 2.0.1
//
// Stephen Band
//
// Triggers 'movestart', 'move' and 'moveend' events after
// mousemoves following a mousedown cross a distance threshold,
// similar to the native 'dragstart', 'drag' and 'dragend' events.
// Move events are throttled to animation frames. Move event objects
// have the properties:
//
// pageX:
// pageY: Page coordinates of pointer.
// startX:
// startY: Page coordinates of pointer at movestart.
// distX:
// distY: Distance the pointer has moved since movestart.
// deltaX:
// deltaY: Distance the finger has moved since last event.
// velocityX:
// velocityY: Average velocity over last few events.
(function(fn) {
if (typeof define === 'function' && define.amd) {
define([], fn);
} else if ((typeof module !== "undefined" && module !== null) && module.exports) {
module.exports = fn;
} else {
fn();
}
})(function(){
var assign = Object.assign || window.jQuery && jQuery.extend;
// Number of pixels a pressed pointer travels before movestart
// event is fired.
var threshold = 8;
// Shim for requestAnimationFrame, falling back to timer. See:
// see http://paulirish.com/2011/requestanimationframe-for-smart-animating/
var requestFrame = (function(){
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(fn, element){
return window.setTimeout(function(){
fn();
}, 25);
}
);
})();
var ignoreTags = {
textarea: true,
input: true,
select: true,
button: true
};
var mouseevents = {
move: 'mousemove',
cancel: 'mouseup dragstart',
end: 'mouseup'
};
var touchevents = {
move: 'touchmove',
cancel: 'touchend',
end: 'touchend'
};
var rspaces = /\s+/;
// DOM Events
var eventOptions = { bubbles: true, cancelable: true };
var eventsSymbol = Symbol('events');
function createEvent(type) {
return new CustomEvent(type, eventOptions);
}
function getEvents(node) {
return node[eventsSymbol] || (node[eventsSymbol] = {});
}
function on(node, types, fn, data, selector) {
types = types.split(rspaces);
var events = getEvents(node);
var i = types.length;
var handlers, type;
function handler(e) { fn(e, data); }
while (i--) {
type = types[i];
handlers = events[type] || (events[type] = []);
handlers.push([fn, handler]);
node.addEventListener(type, handler);
}
}
function off(node, types, fn, selector) {
types = types.split(rspaces);
var events = getEvents(node);
var i = types.length;
var type, handlers, k;
if (!events) { return; }
while (i--) {
type = types[i];
handlers = events[type];
if (!handlers) { continue; }
k = handlers.length;
while (k--) {
if (handlers[k][0] === fn) {
node.removeEventListener(type, handlers[k][1]);
handlers.splice(k, 1);
}
}
}
}
function trigger(node, type, properties) {
// Don't cache events. It prevents you from triggering an event of a
// given type from inside the handler of another event of that type.
var event = createEvent(type);
if (properties) { assign(event, properties); }
node.dispatchEvent(event);
}
// Constructors
function Timer(fn){
var callback = fn,
active = false,
running = false;
function trigger(time) {
if (active){
callback();
requestFrame(trigger);
running = true;
active = false;
}
else {
running = false;
}
}
this.kick = function(fn) {
active = true;
if (!running) { trigger(); }
};
this.end = function(fn) {
var cb = callback;
if (!fn) { return; }
// If the timer is not running, simply call the end callback.
if (!running) {
fn();
}
// If the timer is running, and has been kicked lately, then
// queue up the current callback and the end callback, otherwise
// just the end callback.
else {
callback = active ?
function(){ cb(); fn(); } :
fn ;
active = true;
}
};
}
// Functions
function noop() {}
function preventDefault(e) {
e.preventDefault();
}
function isIgnoreTag(e) {
return !!ignoreTags[e.target.tagName.toLowerCase()];
}
function isPrimaryButton(e) {
// Ignore mousedowns on any button other than the left (or primary)
// mouse button, or when a modifier key is pressed.
return (e.which === 1 && !e.ctrlKey && !e.altKey);
}
function identifiedTouch(touchList, id) {
var i, l;
if (touchList.identifiedTouch) {
return touchList.identifiedTouch(id);
}
// touchList.identifiedTouch() does not exist in
// webkit yet… we must do the search ourselves...
i = -1;
l = touchList.length;
while (++i < l) {
if (touchList[i].identifier === id) {
return touchList[i];
}
}
}
function changedTouch(e, data) {
var touch = identifiedTouch(e.changedTouches, data.identifier);
// This isn't the touch you're looking for.
if (!touch) { return; }
// Chrome Android (at least) includes touches that have not
// changed in e.changedTouches. That's a bit annoying. Check
// that this touch has changed.
if (touch.pageX === data.pageX && touch.pageY === data.pageY) { return; }
return touch;
}
// Handlers that decide when the first movestart is triggered
function mousedown(e){
// Ignore non-primary buttons
if (!isPrimaryButton(e)) { return; }
// Ignore form and interactive elements
if (isIgnoreTag(e)) { return; }
on(document, mouseevents.move, mousemove, e);
on(document, mouseevents.cancel, mouseend, e);
}
function mousemove(e, data){
checkThreshold(e, data, e, removeMouse);
}
function mouseend(e, data) {
removeMouse();
}
function removeMouse() {
off(document, mouseevents.move, mousemove);
off(document, mouseevents.cancel, mouseend);
}
function touchstart(e) {
// Don't get in the way of interaction with form elements
if (ignoreTags[e.target.tagName.toLowerCase()]) { return; }
var touch = e.changedTouches[0];
// iOS live updates the touch objects whereas Android gives us copies.
// That means we can't trust the touchstart object to stay the same,
// so we must copy the data. This object acts as a template for
// movestart, move and moveend event objects.
var data = {
target: touch.target,
pageX: touch.pageX,
pageY: touch.pageY,
identifier: touch.identifier,
// The only way to make handlers individually unbindable is by
// making them unique.
touchmove: function(e, data) { touchmove(e, data); },
touchend: function(e, data) { touchend(e, data); }
};
on(document, touchevents.move, data.touchmove, data);
on(document, touchevents.cancel, data.touchend, data);
}
function touchmove(e, data) {
var touch = changedTouch(e, data);
if (!touch) { return; }
checkThreshold(e, data, touch, removeTouch);
}
function touchend(e, data) {
var touch = identifiedTouch(e.changedTouches, data.identifier);
if (!touch) { return; }
removeTouch(data);
}
function removeTouch(data) {
off(document, touchevents.move, data.touchmove);
off(document, touchevents.cancel, data.touchend);
}
function checkThreshold(e, data, touch, fn) {
var distX = touch.pageX - data.pageX;
var distY = touch.pageY - data.pageY;
// Do nothing if the threshold has not been crossed.
if ((distX * distX) + (distY * distY) < (threshold * threshold)) { return; }
triggerStart(e, data, touch, distX, distY, fn);
}
function triggerStart(e, data, touch, distX, distY, fn) {
var touches = e.targetTouches;
var time = e.timeStamp - data.timeStamp;
// Create a movestart object with some special properties that
// are passed only to the movestart handlers.
var template = {
altKey: e.altKey,
ctrlKey: e.ctrlKey,
shiftKey: e.shiftKey,
startX: data.pageX,
startY: data.pageY,
distX: distX,
distY: distY,
deltaX: distX,
deltaY: distY,
pageX: touch.pageX,
pageY: touch.pageY,
velocityX: distX / time,
velocityY: distY / time,
identifier: data.identifier,
targetTouches: touches,
finger: touches ? touches.length : 1,
enableMove: function() {
this.moveEnabled = true;
this.enableMove = noop;
e.preventDefault();
}
};
// Trigger the movestart event.
trigger(data.target, 'movestart', template);
// Unbind handlers that tracked the touch or mouse up till now.
fn(data);
}
// Handlers that control what happens following a movestart
function activeMousemove(e, data) {
var timer = data.timer;
data.touch = e;
data.timeStamp = e.timeStamp;
timer.kick();
}
function activeMouseend(e, data) {
var target = data.target;
var event = data.event;
var timer = data.timer;
removeActiveMouse();
endEvent(target, event, timer, function() {
// Unbind the click suppressor, waiting until after mouseup
// has been handled.
setTimeout(function(){
off(target, 'click', preventDefault);
}, 0);
});
}
function removeActiveMouse() {
off(document, mouseevents.move, activeMousemove);
off(document, mouseevents.end, activeMouseend);
}
function activeTouchmove(e, data) {
var event = data.event;
var timer = data.timer;
var touch = changedTouch(e, event);
if (!touch) { return; }
// Stop the interface from gesturing
e.preventDefault();
event.targetTouches = e.targetTouches;
data.touch = touch;
data.timeStamp = e.timeStamp;
timer.kick();
}
function activeTouchend(e, data) {
var target = data.target;
var event = data.event;
var timer = data.timer;
var touch = identifiedTouch(e.changedTouches, event.identifier);
// This isn't the touch you're looking for.
if (!touch) { return; }
removeActiveTouch(data);
endEvent(target, event, timer);
}
function removeActiveTouch(data) {
off(document, touchevents.move, data.activeTouchmove);
off(document, touchevents.end, data.activeTouchend);
}
// Logic for triggering move and moveend events
function updateEvent(event, touch, timeStamp) {
var time = timeStamp - event.timeStamp;
event.distX = touch.pageX - event.startX;
event.distY = touch.pageY - event.startY;
event.deltaX = touch.pageX - event.pageX;
event.deltaY = touch.pageY - event.pageY;
// Average the velocity of the last few events using a decay
// curve to even out spurious jumps in values.
event.velocityX = 0.3 * event.velocityX + 0.7 * event.deltaX / time;
event.velocityY = 0.3 * event.velocityY + 0.7 * event.deltaY / time;
event.pageX = touch.pageX;
event.pageY = touch.pageY;
}
function endEvent(target, event, timer, fn) {
timer.end(function(){
trigger(target, 'moveend', event);
return fn && fn();
});
}
// Set up the DOM
function movestart(e) {
if (e.defaultPrevented) { return; }
if (!e.moveEnabled) { return; }
var event = {
startX: e.startX,
startY: e.startY,
pageX: e.pageX,
pageY: e.pageY,
distX: e.distX,
distY: e.distY,
deltaX: e.deltaX,
deltaY: e.deltaY,
velocityX: e.velocityX,
velocityY: e.velocityY,
identifier: e.identifier,
targetTouches: e.targetTouches,
finger: e.finger
};
var data = {
target: e.target,
event: event,
timer: new Timer(update),
touch: undefined,
timeStamp: e.timeStamp
};
function update(time) {
updateEvent(event, data.touch, data.timeStamp);
trigger(data.target, 'move', event);
}
if (e.identifier === undefined) {
// We're dealing with a mouse event.
// Stop clicks from propagating during a move
on(e.target, 'click', preventDefault);
on(document, mouseevents.move, activeMousemove, data);
on(document, mouseevents.end, activeMouseend, data);
}
else {
// In order to unbind correct handlers they have to be unique
data.activeTouchmove = function(e, data) { activeTouchmove(e, data); };
data.activeTouchend = function(e, data) { activeTouchend(e, data); };
// We're dealing with a touch.
on(document, touchevents.move, data.activeTouchmove, data);
on(document, touchevents.end, data.activeTouchend, data);
}
}
on(document, 'mousedown', mousedown);
on(document, 'touchstart', touchstart);
on(document, 'movestart', movestart);
// jQuery special events
//
// jQuery event objects are copies of DOM event objects. They need
// a little help copying the move properties across.
if (!window.jQuery) { return; }
var properties = ("startX startY pageX pageY distX distY deltaX deltaY velocityX velocityY").split(' ');
function enableMove1(e) { e.enableMove(); }
function enableMove2(e) { e.enableMove(); }
function enableMove3(e) { e.enableMove(); }
function add(handleObj) {
var handler = handleObj.handler;
handleObj.handler = function(e) {
// Copy move properties across from originalEvent
var i = properties.length;
var property;
while(i--) {
property = properties[i];
e[property] = e.originalEvent[property];
}
handler.apply(this, arguments);
};
}
jQuery.event.special.movestart = {
setup: function() {
// Movestart must be enabled to allow other move events
on(this, 'movestart', enableMove1);
// Do listen to DOM events
return false;
},
teardown: function() {
off(this, 'movestart', enableMove1);
return false;
},
add: add
};
jQuery.event.special.move = {
setup: function() {
on(this, 'movestart', enableMove2);
return false;
},
teardown: function() {
off(this, 'movestart', enableMove2);
return false;
},
add: add
};
jQuery.event.special.moveend = {
setup: function() {
on(this, 'movestart', enableMove3);
return false;
},
teardown: function() {
off(this, 'movestart', enableMove3);
return false;
},
add: add
};
});

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,708 @@
(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
$.fn.twentytwenty = function( options, callback ) {
options = $.extend( {
handlePosition: 0.5,
orientation: 'horizontal',
labelBefore: 'Before',
labelAfter: 'After'
}, options );
return this.each( function() {
var sliderPct = options.handlePosition,
$container = $( this ),
sliderOrientation = options.orientation,
beforeDirection = ( 'vertical' === sliderOrientation ) ? 'down' : 'left',
afterDirection = ( 'vertical' === sliderOrientation ) ? 'up' : 'right',
$beforeImg = $container.find( 'img:first' ),
$afterImg = $container.find( 'img:last' ),
offsetX = 0,
offsetY = 0,
imgWidth = 0,
imgHeight = 0,
$slider, $overlay,
calcOffset = function( dimensionPct ) {
var width = parseInt( $beforeImg.width(), 10 ),
height = parseInt( $beforeImg.height(), 10 );
if ( ! width || ! height ) {
width = parseInt( $beforeImg.attr( 'width' ), 10 );
height = parseInt( $beforeImg.attr( 'height' ), 10 );
}
return {
w: width + "px",
h: height + "px",
cw: ( dimensionPct * width ) + "px",
ch: ( dimensionPct * height ) + "px"
};
},
adjustContainer = function( offset ) {
// Make it dynamic, in case the "before" image changes.
var $beforeImage = $container.find( '.twentytwenty-before' );
if ( 'vertical' === sliderOrientation ) {
$beforeImage.css( 'clip', 'rect(0,' + offset.w + ',' + offset.ch + ',0)' );
} else {
$beforeImage.css( 'clip', 'rect(0,' + offset.cw + ',' + offset.h + ',0)' );
}
$container.css( 'height', offset.h );
if ( typeof callback === 'function' ) {
callback();
}
},
adjustSlider = function( pct ) {
var offset = calcOffset( pct );
if ( 'vertical' === sliderOrientation ) {
$slider.css( 'top', offset.ch );
} else {
$slider.css( 'left', offset.cw );
}
adjustContainer( offset );
};
if ( $container.parent( '.twentytwenty-wrapper' ).length ) {
$container.unwrap();
}
$container.wrap( '<div class="twentytwenty-wrapper twentytwenty-' + sliderOrientation + '"></div>' );
$container.children( '.twentytwenty-overlay, .twentytwenty-handle' ).remove();
$container.append( '<div class="twentytwenty-overlay"></div>' );
$container.append( '<div class="twentytwenty-handle"></div>' );
$slider = $container.find( '.twentytwenty-handle' );
$slider.append( '<span class="twentytwenty-' + beforeDirection + '-arrow"></span>' );
$slider.append( '<span class="twentytwenty-' + afterDirection + '-arrow"></span>' );
$container.addClass( 'twentytwenty-container' );
$beforeImg.addClass( 'twentytwenty-before' );
$afterImg.addClass( 'twentytwenty-after' );
$overlay = $container.find( '.twentytwenty-overlay' );
$overlay.append( '<div class="twentytwenty-labels twentytwenty-before-label"><span class="twentytwenty-label-content">' + options.labelBefore + '</span></div>' );
$overlay.append( '<div class="twentytwenty-labels twentytwenty-after-label"><span class="twentytwenty-label-content">' + options.labelAfter + '</span></div>' );
$( w ).on( 'resize.twentytwenty', function() {
adjustSlider( sliderPct );
} );
$slider.on( 'movestart', function( e ) {
if ( 'vertical' !== sliderOrientation && ( ( e.distX > e.distY && e.distX < -e.distY ) || ( e.distX < e.distY && e.distX > -e.distY ) ) ) {
e.preventDefault();
} else if ( 'vertical' === sliderOrientation && ( ( e.distX < e.distY && e.distX < -e.distY ) || ( e.distX > e.distY && e.distX > -e.distY ) ) ) {
e.preventDefault();
}
$container.addClass( 'active' );
offsetX = $container.offset().left;
offsetY = $container.offset().top;
imgWidth = $beforeImg.width();
imgHeight = $beforeImg.height();
} );
$slider.on( 'moveend', function() {
$container.removeClass( 'active' );
} );
$slider.on( 'move', function( e ) {
if ( $container.hasClass('active') ) {
sliderPct = 'vertical' === sliderOrientation ? ( e.pageY - offsetY ) / imgHeight : ( e.pageX - offsetX ) / imgWidth;
if ( sliderPct < 0 ) {
sliderPct = 0;
} else if ( sliderPct > 1 ) {
sliderPct = 1;
}
adjustSlider( sliderPct );
}
} );
$container.find( 'img' ).on( 'mousedown', function( e ) {
e.preventDefault();
} );
$( w ).trigger( 'resize.twentytwenty' );
} );
};
} )(jQuery, document, window);
/**
* Twentytwenty Imagify Init
*/
(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
/*
* Mini chart
*
* @param {element} canvas
*/
var drawMeAChart = function ( canvas ) {
canvas.each( function() {
var value = parseInt( $( this ).closest( '.imagify-chart' ).next( '.imagify-chart-value' ).text(), 10 );
new w.imagify.Chart( this, { // eslint-disable-line no-new
type: 'doughnut',
data: {
datasets: [{
data: [ value, 100 - value ],
backgroundColor: [ '#00B3D3', '#D8D8D8' ],
borderColor: '#2A2E3C',
borderWidth: 1
}]
},
options: {
legend: {
display: false
},
events: [],
animation: {
easing: 'easeOutBounce'
},
tooltips: {
enabled: false
},
responsive: false,
cutoutPercentage: 60
}
} );
} );
},
/**
* Dynamic modal
*
* @param {object} Parameters to build modal with datas
*/
imagifyTwentyModal = function( options ) {
var defaults = {
width: 0, //px
height: 0, //px
originalUrl: '', //url
optimizedUrl: '', //url
originalSize: 0, //mb
optimizedSize: 0, // mb
saving: 0, //percent
modalAppendTo: $( 'body' ), // jQuery element
trigger: $( '[data-target="imagify-visual-comparison"]' ), // jQuery element (button, link) with data-target="modalId"
modalId: 'imagify-visual-comparison', // should be dynamic if multiple modals
openModal: false
},
settings = $.extend( {}, defaults, options ),
modalHtml;
if ( 0 === settings.width || 0 === settings.height || '' === settings.originalUrl || '' === settings.optimizedUrl || 0 === settings.originalSize || 0 === settings.optimizedSize || 0 === settings.saving ) {
return 'error';
}
// create modal box
modalHtml = '<div id="' + settings.modalId + '" class="imagify-modal imagify-visual-comparison" aria-hidden="true">';
/* eslint-disable indent */
modalHtml += '<div class="imagify-modal-content loading">';
modalHtml += '<div class="twentytwenty-container">';
modalHtml += '<img class="imagify-img-before" alt="" width="' + settings.width + '" height="' + settings.height + '">';
modalHtml += '<img class="imagify-img-after" alt="" width="' + settings.width + '" height="' + settings.height + '">';
modalHtml += '</div>';
modalHtml += '<div class="imagify-comparison-levels">';
modalHtml += '<div class="imagify-c-level imagify-level-original go-left">';
modalHtml += '<p class="imagify-c-level-row">';
modalHtml += '<span class="label">' + imagifyTTT.labels.filesize + '</span>';
modalHtml += '<span class="value level">' + settings.originalSize + '</span>';
modalHtml += '</p>';
modalHtml += '</div>';
modalHtml += '<div class="imagify-c-level imagify-level-optimized go-right">';
modalHtml += '<p class="imagify-c-level-row">';
modalHtml += '<span class="label">' + imagifyTTT.labels.filesize + '</span>';
modalHtml += '<span class="value level">' + settings.optimizedSize + '</span>';
modalHtml += '</p>';
modalHtml += '<p class="imagify-c-level-row">';
modalHtml += '<span class="label">' + imagifyTTT.labels.saving + '</span>';
modalHtml += '<span class="value"><span class="imagify-chart"><span class="imagify-chart-container"><canvas id="imagify-consumption-chart-normal" width="15" height="15"></canvas></span></span><span class="imagify-chart-value">' + settings.saving + '</span>%</span>';
modalHtml += '</p>';
modalHtml += '</div>';
modalHtml += '</div>';
modalHtml += '<button class="close-btn absolute" type="button"><i aria-hidden="true" class="dashicons dashicons-no-alt"></i><span class="screen-reader-text">' + imagifyTTT.labels.close + '</span></button>';
modalHtml += '</div>';
/* eslint-enable indent */
modalHtml += '</div>';
settings.modalAppendTo.append( modalHtml );
settings.trigger.on( 'click.imagify', function( e ) {
var $modal = $( $( this ).data( 'target' ) ),
imgsLoaded = 0,
$tt, checkLoad;
e.preventDefault();
if ( settings.openModal ) {
w.imagify.openModal( $( this ) );
}
$modal.find( '.imagify-modal-content' ).css( {
'width': ( $( w ).outerWidth() * 0.85 ) + 'px',
'max-width': settings.width
} );
// Load before img.
$modal.find( '.imagify-img-before' ).on( 'load', function() {
imgsLoaded++;
} ).attr( 'src', settings.originalUrl );
// Load after img.
$modal.find( '.imagify-img-after' ).on( 'load', function() {
imgsLoaded++;
} ).attr( 'src', settings.optimizedUrl + ( settings.optimizedUrl.indexOf( '?' ) > 0 ? '&' : '?' ) + 'v=' + Date.now() );
$tt = $modal.find( '.twentytwenty-container' );
checkLoad = setInterval( function() {
if ( 2 !== imgsLoaded ) {
return;
}
$tt.twentytwenty( {
handlePosition: 0.3,
orientation: 'horizontal',
labelBefore: imagifyTTT.labels.originalL,
labelAfter: imagifyTTT.labels.optimizedL
}, function() {
var windowH = $( w ).height(),
ttH = $modal.find( '.twentytwenty-container' ).height(),
ttTop = $modal.find( '.twentytwenty-wrapper' ).position().top,
$handle, $labels, $datas, datasH, handlePos, labelsPos;
if ( ! $tt.closest( '.imagify-modal-content' ).hasClass( 'loaded' ) ) {
$tt.closest( '.imagify-modal-content' ).removeClass( 'loading' ).addClass( 'loaded' );
drawMeAChart( $modal.find( '.imagify-level-optimized .imagify-chart canvas' ) );
}
// Check if image height is to big.
if ( windowH < ttH && ! $modal.hasClass( 'modal-is-too-high' ) ) {
$modal.addClass( 'modal-is-too-high' );
$handle = $modal.find( '.twentytwenty-handle' );
$labels = $modal.find( '.twentytwenty-label-content' );
$datas = $modal.find( '.imagify-comparison-levels' );
datasH = $datas.outerHeight();
handlePos = ( windowH - ttTop - $handle.height() ) / 2;
labelsPos = ( windowH - ttTop * 3 - datasH );
$handle.css( {
top: handlePos
} );
$labels.css( {
top: labelsPos,
bottom: 'auto'
} );
$modal.find( '.twentytwenty-wrapper' ).css( {
paddingBottom: datasH
} );
$modal.find( '.imagify-modal-content' ).on( 'scroll.imagify', function() {
var scrollTop = $( this ).scrollTop();
$handle.css( {
top: handlePos + scrollTop
} );
$labels.css( {
top: labelsPos + scrollTop
} );
$datas.css( {
bottom: -scrollTop
} );
} );
}
} );
clearInterval( checkLoad );
checkLoad = null;
return 'done';
}, 75 );
} );
}; // imagifyTwentyModal( options );
/**
* The complexe visual comparison
*/
$( '.imagify-visual-comparison-btn' ).on( 'click', function() {
var $tt, imgsLoaded, loader,
labelOriginal, labelNormal, labelAggressive, labelUltra,
originalLabel, originalAlt, originalSrc, originalDim,
normalAlt, normalSrc, normalDim,
aggressiveAlt, aggressiveSrc, aggressiveDim,
ultraLabel, ultraAlt, ultraSrc, ultraDim,
ttBeforeButtons, ttAfterButtons, image50, twentyMe;
if ( $( '.twentytwenty-wrapper' ).length === 1 ) {
return;
}
$( $( this ).data( 'target' ) ).find( '.imagify-modal-content' ).css( 'width', ( $( w ).outerWidth() * 0.95 ) + 'px' );
if ( $( '.twentytwenty-container' ).length > 0 && $( w ).outerWidth() <= 800 ) {
return;
}
$tt = $( '.twentytwenty-container' );
imgsLoaded = 0;
loader = $tt.data( 'loader' );
labelOriginal = $tt.data( 'label-original' );
labelNormal = $tt.data( 'label-normal' );
labelAggressive = $tt.data( 'label-aggressive' );
labelUltra = $tt.data( 'label-ultra' );
originalLabel = $tt.data( 'original-label' ).replace( /\*\*/, '<strong>' ).replace( /\*\*/, '</strong>' );
originalAlt = $tt.data( 'original-alt' );
originalSrc = $tt.data( 'original-img' );
originalDim = $tt.data( 'original-dim' ).split( 'x' );
normalAlt = $tt.data( 'normal-alt' );
normalSrc = $tt.data( 'normal-img' );
normalDim = $tt.data( 'normal-dim' ).split( 'x' );
aggressiveAlt = $tt.data( 'aggressive-alt' );
aggressiveSrc = $tt.data( 'aggressive-img' );
aggressiveDim = $tt.data( 'aggressive-dim' ).split( 'x' );
ultraLabel = $tt.data( 'ultra-label' ).replace( /\*\*/, '<strong>' ).replace( /\*\*/, '</strong>' );
ultraAlt = $tt.data( 'ultra-alt' );
ultraSrc = $tt.data( 'ultra-img' );
ultraDim = $tt.data( 'ultra-dim' ).split( 'x' );
ttBeforeButtons = '<span class="twentytwenty-duo-buttons twentytwenty-duo-left">';
/* eslint-disable indent */
ttBeforeButtons += '<button type="button" class="imagify-comparison-original selected" data-img="original">' + labelOriginal + '</button>';
ttBeforeButtons += '<button type="button" class="imagify-comparison-normal" data-img="normal">' + labelNormal + '</button>';
ttBeforeButtons += '<button type="button" class="imagify-comparison-aggressive" data-img="aggressive">' + labelAggressive + '</button>';
/* eslint-enable indent */
ttBeforeButtons += '</span>';
ttAfterButtons = '<span class="twentytwenty-duo-buttons twentytwenty-duo-right">';
/* eslint-disable indent */
ttAfterButtons += '<button type="button" class="imagify-comparison-normal" data-img="normal">' + labelNormal + '</button>';
ttAfterButtons += '<button type="button" class="imagify-comparison-aggressive" data-img="aggressive">' + labelAggressive + '</button>';
ttAfterButtons += '<button type="button" class="imagify-comparison-ultra selected" data-img="ultra">' + labelUltra + '</button>';
/* eslint-enable indent */
ttAfterButtons += '</span>';
// Loader.
$tt.before( '<img class="loader" src="' + loader + '" alt="Loading…" width="64" height="64">' );
// Should be more locally integrated...
$( '.twentytwenty-left-buttons' ).append( ttBeforeButtons );
$( '.twentytwenty-right-buttons' ).append( ttAfterButtons );
image50 = '<img class="img-original" alt="' + originalAlt + '" width="' + originalDim[0] + '" height="' + originalDim[1] + '">';
image50 += '<img class="img-normal" alt="' + normalAlt + '" width="' + normalDim[0] + '" height="' + normalDim[1] + '">';
image50 += '<img class="img-aggressive" alt="' + aggressiveAlt + '" width="' + aggressiveDim[0] + '" height="' + aggressiveDim[1] + '">';
image50 += '<img class="img-ultra" alt="' + ultraAlt + '" width="' + ultraDim[0] + '" height="' + ultraDim[1] + '">';
// Add switchers button only if needed.
// Should be more locally integrated...
image50 += $( '.twentytwenty-left-buttons' ).lenght ? ttBeforeButtons + ttAfterButtons : '';
// Add images to 50/50 area.
$tt.closest( '.imagify-modal-content' ).addClass( 'loading' ).find( '.twentytwenty-container' ).append( image50 );
// Load image original.
$( '.img-original' ).on( 'load', function() {
imgsLoaded++;
} ).attr( 'src', originalSrc );
// Load image normal.
$( '.img-normal' ).on( 'load', function() {
imgsLoaded++;
} ).attr( 'src', normalSrc );
// Load image aggressive.
$( '.img-aggressive' ).on( 'load', function() {
imgsLoaded++;
} ).attr( 'src', aggressiveSrc );
// Load image ultra.
$( '.img-ultra' ).on( 'load', function() {
imgsLoaded++;
} ).attr( 'src', ultraSrc );
twentyMe = setInterval( function() {
if ( 4 !== imgsLoaded ) {
return;
}
$tt.twentytwenty({
handlePosition: 0.6,
orientation: 'horizontal',
labelBefore: originalLabel,
labelAfter: ultraLabel
}, function() {
// Fires on initialisation & each time the handle is moving.
if ( ! $tt.closest( '.imagify-modal-content' ).hasClass( 'loaded' ) ) {
$tt.closest( '.imagify-modal-content' ).removeClass( 'loading' ).addClass( 'loaded' );
drawMeAChart( $( '.imagify-level-ultra .imagify-chart canvas' ) );
}
} );
clearInterval( twentyMe );
twentyMe = null;
}, 75);
// On click on button choices.
$( '.imagify-comparison-title' ).on( 'click', '.twentytwenty-duo-buttons button:not(.selected)', function( e ) {
var $this = $( this ),
$container = $this.closest( '.imagify-comparison-title' ).nextAll( '.twentytwenty-wrapper' ).find( '.twentytwenty-container' ),
side = $this.closest( '.twentytwenty-duo-buttons' ).hasClass( 'twentytwenty-duo-left' ) ? 'left' : 'right',
$otherSide = 'left' === side ? $this.closest( '.imagify-comparison-title' ).find( '.twentytwenty-duo-right' ) : $this.closest( '.imagify-comparison-title' ).find( '.twentytwenty-duo-left' ),
$duo = $this.closest( '.twentytwenty-duo-buttons' ).find( 'button' ),
$imgBefore = $container.find( '.twentytwenty-before' ),
$imgAfter = $container.find( '.twentytwenty-after' ),
image = $this.data( 'img' ),
clipStyles;
e.stopPropagation();
e.preventDefault();
// Button coloration.
$duo.removeClass( 'selected' );
$this.addClass( 'selected' );
// Other side action (to not compare same images).
if ( $otherSide.find( '.selected' ).data( 'img' ) === image ) {
$otherSide.find( 'button:not(.selected)' ).eq( 0 ).trigger( 'click' );
}
// Left buttons.
if ( 'left' === side ) {
clipStyles = $imgBefore.css( 'clip' );
$imgBefore.attr( 'style', '' );
$imgBefore.removeClass( 'twentytwenty-before' );
$container.find( '.img-' + image ).addClass( 'twentytwenty-before' ).css( 'clip', clipStyles );
$( '.twentytwenty-before-label .twentytwenty-label-content' ).text( $container.data( image + '-label' ) );
$( '.imagify-c-level.go-left' ).attr( 'aria-hidden', 'true' ).removeClass( 'go-left go-right' );
$( '.imagify-level-' + image ).attr( 'aria-hidden', 'false' ).addClass( 'go-left' );
}
// Right buttons.
if ( 'right' === side ) {
$imgAfter.removeClass( 'twentytwenty-after' );
$container.find( '.img-' + image ).addClass( 'twentytwenty-after' );
$( '.twentytwenty-after-label .twentytwenty-label-content' ).text( $container.data( image + '-label' ) );
$( '.imagify-c-level.go-right' ).attr( 'aria-hidden', 'true' ).removeClass( 'go-left go-right' );
$( '.imagify-level-' + image ).attr( 'aria-hidden', 'false' ).addClass( 'go-right' );
}
drawMeAChart( $( '.imagify-level-' + image + ' .imagify-chart canvas' ) );
} );
} );
/**
* Imagify comparison inside Media post edition.
*/
if ( imagifyTTT.imageWidth && $( '.post-php .wp_attachment_image .thumbnail' ).length > 0 ) {
var $oriParent = $( '.post-php .wp_attachment_image' ),
oriSource = { src: $( '#imagify-full-original' ).val(), size: $( '#imagify-full-original-size' ).val() },
$optimizeBtn = $( '#misc-publishing-actions' ).find( '.misc-pub-imagify .button-primary' ),
filesize, saving;
imagifyTTT.widthLimit = parseInt( imagifyTTT.widthLimit, 10 );
// If shown image > 360, use twentytwenty.
if ( imagifyTTT.imageWidth > imagifyTTT.widthLimit && oriSource.src ) {
filesize = $( '.misc-pub-filesize strong' ).text();
saving = $( '.imagify-data-item .imagify-chart-value' ).text();
// Create button to trigger.
$( '[id^="imgedit-open-btn-"]' ).before( '<button type="button" class="imagify-button-primary button-primary imagify-modal-trigger" data-target="#imagify-visual-comparison" id="imagify-start-comparison">' + imagifyTTT.labels.compare + '</button>' );
// Modal and trigger event creation.
imagifyTwentyModal( {
width: parseInt( imagifyTTT.imageWidth, 10 ),
height: parseInt( imagifyTTT.imageHeight, 10 ),
originalUrl: oriSource.src,
optimizedUrl: imagifyTTT.imageSrc,
originalSize: oriSource.size,
optimizedSize: filesize,
saving: saving,
modalAppendTo: $oriParent,
trigger: $( '#imagify-start-comparison' ),
modalId: 'imagify-visual-comparison'
} );
}
// Else put images next to next.
else if ( imagifyTTT.imageWidth < imagifyTTT.widthLimit && oriSource.src ) {
// TODO
}
// If image has no backup.
else if ( $( '#imagify-full-original' ).length > 0 && '' === oriSource.src ) {
// do nothing ?
}
// In case image is not optimized.
else {
// If is not in optimizing process, propose the Optimize button trigger.
if ( $( '#misc-publishing-actions' ).find( '.misc-pub-imagify .button-primary' ).length === 1 ) {
$( '[id^="imgedit-open-btn-"]' ).before( '<span class="spinner imagify-hidden"></span><a class="imagify-button-primary button-primary imagify-optimize-trigger" id="imagify-optimize-trigger" href="' + $optimizeBtn.attr( 'href' ) + '">' + imagifyTTT.labels.optimize + '</a>' );
$( '#imagify-optimize-trigger' ).on( 'click', function() {
$( this ).prev( '.spinner' ).removeClass( 'imagify-hidden' ).addClass( 'is-active' );
} );
}
}
}
/**
* Images comparison in attachments list page (upload.php).
*/
if ( $( '.upload-php .imagify-compare-images' ).length > 0 ) {
$( '.imagify-compare-images' ).each( function() {
var $this = $( this ),
id = $this.data( 'id' ),
$datas = $this.closest( '#post-' + id ).find( '.column-imagify_optimized_file' );
// Modal and trigger event creation.
imagifyTwentyModal( {
width: parseInt( $this.data( 'full-width' ), 10 ),
height: parseInt( $this.data( 'full-height' ), 10 ),
originalUrl: $this.data( 'backup-src' ),
optimizedUrl: $this.data( 'full-src' ),
originalSize: $datas.find( '.original' ).text(),
optimizedSize: $datas.find( '.imagify-data-item .big' ).text(),
saving: $datas.find( '.imagify-chart-value' ).text(),
modalAppendTo: $this.closest( '.column-primary' ),
trigger: $this,
modalId: 'imagify-comparison-' + id
} );
} );
}
/**
* Images Comparison in Grid View modal.
*/
if ( $( '.upload-php' ).length > 0 ) {
var getVar = function( param ) {
var vars = {};
w.location.href.replace(
/[?&]+([^=&]+)=?([^&]*)?/gi,
function( m, key, value ) {
vars[ key ] = undefined !== value ? value : '';
}
);
if ( param ) {
return vars[ param ] ? vars[ param ] : null;
}
return vars;
},
imagifyContentInModal = function() {
var tempTimer = setInterval( function() {
var $datas, originalSrc, $actions;
if ( ! $( '.media-modal .imagify-datas-details' ).length ) {
return;
}
originalSrc = $( '#imagify-original-src' ).val();
if ( originalSrc ) {
// Trigger creation.
$actions = $( '.media-frame-content .attachment-actions' );
$actions.find( '#imagify-media-frame-comparison-btn' ).remove();
$actions.prepend( '<button type="button" class="imagify-button-primary button-primary imagify-modal-trigger" data-target="#imagify-comparison-modal" id="imagify-media-frame-comparison-btn">' + imagifyTTT.labels.compare + '</button>' );
// Get datas.
$datas = $( '.media-frame-content .compat-field-imagify' );
// Modal and trigger event creation.
imagifyTwentyModal( {
width: parseInt( $( '#imagify-full-width' ).val(), 10 ),
height: parseInt( $( '#imagify-full-height' ).val(), 10 ),
originalUrl: originalSrc,
optimizedUrl: $( '#imagify-full-src' ).val(),
originalSize: $( '#imagify-original-size' ).val(),
optimizedSize: $datas.find( '.imagify-data-item .big' ).text(),
saving: $datas.find( '.imagify-chart-value' ).text(),
modalAppendTo: $( '.media-frame-content .thumbnail-image' ),
trigger: $( '#imagify-media-frame-comparison-btn' ),
modalId: 'imagify-comparison-modal',
openModal: true
} );
}
clearInterval( tempTimer );
tempTimer = null;
}, 20 );
};
// If attachment is clicked, or the "Previous" and "Next" buttons, build the modal inside the modal.
$( '.upload-php' ).on( 'click', '.media-frame.mode-grid .attachment, .edit-media-header .left, .edit-media-header .right', function() {
imagifyContentInModal();
} );
// If attachment is mentionned in URL, build the modal inside the modal.
if ( getVar( 'item' ) ) {
imagifyContentInModal();
}
}
/**
* Images comparison in custom folders list page.
*/
if ( $( '#imagify-files-list-form' ).length > 0 ) {
var buildComparisonModal = function( $buttons ) {
$buttons.each( function() {
var $this = $( this ),
id = $this.data( 'id' ),
$datas = $this.closest( 'tr' ).find( '.column-optimization .imagify-data-item' );
$( '#imagify-comparison-' + id ).remove();
// Modal and trigger event creation.
imagifyTwentyModal( {
width: parseInt( $this.data( 'full-width' ), 10 ),
height: parseInt( $this.data( 'full-height' ), 10 ),
originalUrl: $this.data( 'backup-src' ),
optimizedUrl: $this.data( 'full-src' ),
originalSize: $datas.find( '.original' ).text(),
optimizedSize: $datas.find( '.optimized' ).text(),
saving: $datas.find( '.imagify-chart-value' ).text(),
modalAppendTo: $this.closest( '.column-primary' ),
trigger: $this,
modalId: 'imagify-comparison-' + id
} );
} );
};
/**
* Update the comparison tool window when a file row is updated via ajax, and the ones already printed.
*/
$( w ).on( 'comparisonprinted.imagify', function( e, id ) {
var $buttons;
id = id || 0;
if ( id ) {
$buttons = $( '#imagify-files-list-form' ).find( '.imagify-compare-images[data-id="' + id + '"]' );
} else {
$buttons = $( '#imagify-files-list-form' ).find( '.imagify-compare-images' );
}
if ( $buttons.length ) {
buildComparisonModal( $buttons );
}
} )
.trigger( 'comparisonprinted.imagify' );
}
} )(jQuery, document, window);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,51 @@
(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
var bulkOpt;
/**
* Add a "Imagify'em all" in the select list.
*/
bulkOpt = '<option value="imagify-bulk|optimize">' + imagifyLibrary.labels.bulkActionsOptimize + '</option>';
if ( $( '.button-imagify-optimize-missing-sizes' ).length ) {
// If we have items that have missing sizes.
bulkOpt += '<option value="imagify-bulk|optimize_missing_sizes">' + imagifyLibrary.labels.bulkActionsOptimizeMissingSizes + '</option>';
}
if ( imagifyLibrary.backupOption || $( '.attachment-has-backup' ).length ) {
// If the backup option is enabled, or if we have items that can be restored.
bulkOpt += '<option value="imagify-bulk|restore">' + imagifyLibrary.labels.bulkActionsRestore + '</option>';
}
$( '.bulkactions select[name="action"] option:last-child' ).before( bulkOpt );
$( '.bulkactions select[name="action2"] option:last-child' ).before( bulkOpt );
$( '#bulkaction option:last-child' ).after( bulkOpt );
/**
* Process optimization for all selected images.
*/
$( '#doaction' )
.add( '#doaction2' )
.add( '#bulkaction + [name="showThickbox"]' )
.on( 'click', function( e ) {
var value = $( this ).prev( 'select' ).val().split( '|' ),
action, ids;
if ( 'imagify-bulk' !== value[0] ) {
return;
}
e.preventDefault();
action = value[1];
ids = $( 'input[name^="media"]:checked, input[name^="doaction"]:checked' ).map( function() {
return this.value;
} ).get();
ids.forEach( function( id, index ) {
setTimeout( function() {
$( 'table .imagify-data-actions-container[data-id="' + id + '"] .button-imagify-' + action ).first().trigger( 'click' );
}, index * 300 );
} );
} );
} )(jQuery, document, window);

View File

@@ -0,0 +1 @@
!function(o){var i='<option value="imagify-bulk|optimize">'+imagifyLibrary.labels.bulkActionsOptimize+"</option>";o(".button-imagify-optimize-missing-sizes").length&&(i+='<option value="imagify-bulk|optimize_missing_sizes">'+imagifyLibrary.labels.bulkActionsOptimizeMissingSizes+"</option>"),(imagifyLibrary.backupOption||o(".attachment-has-backup").length)&&(i+='<option value="imagify-bulk|restore">'+imagifyLibrary.labels.bulkActionsRestore+"</option>"),o('.bulkactions select[name="action"] option:last-child').before(i),o('.bulkactions select[name="action2"] option:last-child').before(i),o("#bulkaction option:last-child").after(i),o("#doaction").add("#doaction2").add('#bulkaction + [name="showThickbox"]').on("click",function(i){var a,t=o(this).prev("select").val().split("|");"imagify-bulk"===t[0]&&(i.preventDefault(),a=t[1],o('input[name^="media"]:checked, input[name^="doaction"]:checked').map(function(){return this.value}).get().forEach(function(i,t){setTimeout(function(){o('table .imagify-data-actions-container[data-id="'+i+'"] .button-imagify-'+a).first().trigger("click")},300*t)}))})}(jQuery,(document,window));

View File

@@ -0,0 +1,334 @@
/**
* Mini chart.
*
* @param {element} canvas The canvas element.
*/
window.imagify.drawMeAChart = function( canvas ) {
canvas.each( function() {
var value = parseInt( jQuery( this ).closest( '.imagify-chart' ).next( '.imagify-chart-value' ).text(), 10 );
new window.imagify.Chart( this, { // eslint-disable-line no-new
type: 'doughnut',
data: {
datasets: [ {
data: [ value, 100 - value ],
backgroundColor: [ '#00B3D3', '#D8D8D8' ],
borderColor: '#fff',
borderWidth: 1
} ]
},
options: {
legend: {
display: false
},
events: [],
animation: {
easing: 'easeOutBounce'
},
tooltips: {
enabled: false
},
responsive: false
}
} );
} );
};
(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
w.imagify.modal = {
working: [],
/*
* Init.
*/
init: function () {
var $document = $( d ),
$processing;
// Update the chart in the media modal when a media is selected, and the ones already printed.
$( w ).on( 'canvasprinted.imagify', this.updateChart ).trigger( 'canvasprinted.imagify' );
// Toggle slide in custom column.
$( '.imagify-datas-details' ).hide();
$document.on( 'click', '.imagify-datas-more-action a', this.toggleSlide );
// Optimize, restore, etc.
$document.on( 'click', '.button-imagify-restore, .button-imagify-optimize, .button-imagify-manual-reoptimize, .button-imagify-optimize-missing-sizes, .button-imagify-generate-webp, .button-imagify-delete-webp', this.processOptimization );
$document.on( 'imagifybeat-send', this.addToImagifybeat );
$document.on( 'imagifybeat-tick', this.processImagifybeat );
// Some items may be processed in background on page load.
$processing = $( '.imagify-data-actions-container .button-imagify-processing' );
if ( $processing.length ) {
// Some media are already being processed.
// Lock the items, so we can check their status with Imagifybeat.
$processing.closest( '.imagify-data-actions-container' ).each( function() {
var $this = $( this ),
id = w.imagify.modal.sanitizeId( $this.data( 'id' ) ),
context = w.imagify.modal.sanitizeContext( $this.data( 'context' ) );
w.imagify.modal.lockItem( context, id );
} );
// Fasten Imagifybeat.
w.imagify.beat.interval( 15 );
}
},
// Charts ==================================================================================
/**
* Update the chart in the media modal when a media is selected, and the ones already printed.
*
* @param {object} e Event.
* @param {string} selector A CSS selector.
*/
updateChart: function( e, selector ) {
var $canvas;
selector = selector || '.imagify-consumption-chart';
$canvas = $( selector );
w.imagify.drawMeAChart( $canvas );
$canvas.closest( '.imagify-datas-list' ).siblings( '.imagify-datas-details' ).hide();
},
// Optimization ============================================================================
/**
* Process to one of these actions: restore, optimize, re-optimize, or optimize missing sizes.
*
* @param {object} e Event.
*/
processOptimization: function( e ) {
var $obj = $( this ),
$container = $obj.parents( '.imagify-data-actions-container' ),
id = w.imagify.modal.sanitizeId( $container.data( 'id' ) ),
context = w.imagify.modal.sanitizeContext( $container.data( 'context' ) ),
href, processingTemplate;
e.preventDefault();
if ( w.imagify.modal.isItemLocked( context, id ) ) {
return;
}
w.imagify.modal.lockItem( context, id );
href = $obj.attr( 'href' );
processingTemplate = w.imagify.template( 'imagify-button-processing' );
$container.html( processingTemplate( {
label: $obj.data( 'processing-label' )
} ) );
$.get( href.replace( 'admin-post.php', 'admin-ajax.php' ) )
.done( function( response ) {
if ( response.data && response.data.html ) {
// The work is done.
w.imagify.modal.displayProcessResult( context, id, response.data.html );
} else {
// Still processing in background: we're waiting for the result by poking Imagifybeat.
// Set the Imagifybeat interval to 15 seconds.
w.imagify.beat.interval( 15 );
}
} );
},
// Imagifybeat =============================================================================
/**
* Send the media IDs and their status to Imagifybeat.
*
* @param {object} e Event object.
* @param {object} data Object containing all Imagifybeat IDs.
*/
addToImagifybeat: function ( e, data ) {
var $containers = $( '.imagify-data-actions-container' );
if ( ! $containers.length ) {
return;
}
data[ w.imagifyModal.imagifybeatID ] = {};
$containers.each( function() {
var $this = $( this ),
id = w.imagify.modal.sanitizeId( $this.data( 'id' ) ),
context = w.imagify.modal.sanitizeContext( $this.data( 'context' ) ),
locked = w.imagify.modal.isItemLocked( context, id ) ? 1 : 0;
data[ w.imagifyModal.imagifybeatID ][ context ] = data[ w.imagifyModal.imagifybeatID ][ context ] || {};
data[ w.imagifyModal.imagifybeatID ][ context ][ '_' + id ] = locked;
} );
},
/**
* Listen for the custom event "imagifybeat-tick" on $(document).
*
* @param {object} e Event object.
* @param {object} data Object containing all Imagifybeat IDs.
*/
processImagifybeat: function ( e, data ) {
if ( typeof data[ w.imagifyModal.imagifybeatID ] === 'undefined' ) {
return;
}
$.each( data[ w.imagifyModal.imagifybeatID ], function( contextId, htmlContent ) {
var context, id;
context = $.trim( contextId ).match( /^(.+)_(\d+)$/ );
if ( ! context ) {
return;
}
id = w.imagify.modal.sanitizeId( context[2] );
context = w.imagify.modal.sanitizeContext( context[1] );
w.imagify.modal.displayProcessResult( context, id, htmlContent );
} );
},
// DOM manipulation tools ==================================================================
/**
* Display the process result.
*
* @param {string} context The media context.
* @param {int} id The media ID.
* @param {string} htmlContent The HTML to insert.
*/
displayProcessResult: function( context, id, htmlContent ) {
var $containers = w.imagify.modal.getContainers( context, id );
$containers.html( htmlContent );
w.imagify.modal.unlockItem( context, id );
if ( ! w.imagify.modal.working.length ) {
// Work is done.
// Open the last container being processed.
w.imagify.modal.openSlide( $containers );
// Reset Imagifybeat interval.
w.imagify.beat.resetInterval();
}
},
/**
* Open a slide rapidly.
*
* @param {object} $containers A jQuery collection.
*/
openSlide: function( $containers ) {
$containers.each( function() {
var $container = $( this ),
text = $container.find( '.imagify-datas-more-action a' ).data( 'close' );
$container.find( '.imagify-datas-more-action a' ).addClass( 'is-open' ).find( '.the-text' ).text( text );
$container.find( '.imagify-datas-details' ).show().addClass( 'is-open' );
} );
},
/**
* Toggle slide in custom column.
*
* @param {object} e Event.
*/
toggleSlide: function( e ) {
var $this = $( this );
e.preventDefault();
if ( $this.hasClass( 'is-open' ) ) {
$( $this.attr( 'href' ) ).slideUp( 300 ).removeClass( 'is-open' );
$this.removeClass( 'is-open' ).find( '.the-text' ).text( $this.data( 'open' ) );
} else {
$( $this.attr( 'href' ) ).slideDown( 300 ).addClass( 'is-open' );
$this.addClass( 'is-open' ).find( '.the-text' ).text( $this.data( 'close' ) );
}
},
/**
* Get all containers matching the given context and id.
*
* @param {string} context The media context.
* @param {int} id The media ID.
* @return {object} A jQuery collection.
*/
getContainers: function( context, id ) {
return $( '.imagify-data-actions-container[data-id="' + id + '"][data-context="' + context + '"]' );
},
// Sanitization ============================================================================
/**
* Sanitize a media ID.
*
* @param {int|string} id A media ID.
* @return {int}
*/
sanitizeId: function( id ) {
return parseInt( id, 10 );
},
/**
* Sanitize a media context.
*
* @param {string} context A media context.
* @return {string}
*/
sanitizeContext: function( context ) {
context = context.replace( '/[^a-z0-9_-]/gi', '' ).toLowerCase();
return context ? context : 'wp';
},
// Locks ===================================================================================
/**
* Lock an item.
*
* @param {string} context The media context.
* @param {int} id The media ID.
*/
lockItem: function( context, id ) {
if ( ! this.isItemLocked( context, id ) ) {
this.working.push( context + '_' + id );
}
},
/**
* Unlock an item.
*
* @param {string} context The media context.
* @param {int} id The media ID.
*/
unlockItem: function( context, id ) {
var name = context + '_' + id,
i = _.indexOf( this.working, name );
if ( i > -1 ) {
this.working.splice( i, 1 );
}
},
/**
* Tell if an item is locked.
*
* @param {string} context The media context.
* @param {int} id The media ID.
* @return {bool}
*/
isItemLocked: function( context, id ) {
return _.indexOf( this.working, context + '_' + id ) > -1;
}
};
w.imagify.modal.init();
} )(jQuery, document, window);

View File

@@ -0,0 +1 @@
window.imagify.drawMeAChart=function(i){i.each(function(){var i=parseInt(jQuery(this).closest(".imagify-chart").next(".imagify-chart-value").text(),10);new window.imagify.Chart(this,{type:"doughnut",data:{datasets:[{data:[i,100-i],backgroundColor:["#00B3D3","#D8D8D8"],borderColor:"#fff",borderWidth:1}]},options:{legend:{display:!1},events:[],animation:{easing:"easeOutBounce"},tooltips:{enabled:!1},responsive:!1}})})},function(s,a,d){d.imagify.modal={working:[],init:function(){var i=s(a);s(d).on("canvasprinted.imagify",this.updateChart).trigger("canvasprinted.imagify"),s(".imagify-datas-details").hide(),i.on("click",".imagify-datas-more-action a",this.toggleSlide),i.on("click",".button-imagify-restore, .button-imagify-optimize, .button-imagify-manual-reoptimize, .button-imagify-optimize-missing-sizes, .button-imagify-generate-webp, .button-imagify-delete-webp",this.processOptimization),i.on("imagifybeat-send",this.addToImagifybeat),i.on("imagifybeat-tick",this.processImagifybeat),(i=s(".imagify-data-actions-container .button-imagify-processing")).length&&(i.closest(".imagify-data-actions-container").each(function(){var i=s(this),a=d.imagify.modal.sanitizeId(i.data("id")),i=d.imagify.modal.sanitizeContext(i.data("context"));d.imagify.modal.lockItem(i,a)}),d.imagify.beat.interval(15))},updateChart:function(i,a){a=s(a=a||".imagify-consumption-chart");d.imagify.drawMeAChart(a),a.closest(".imagify-datas-list").siblings(".imagify-datas-details").hide()},processOptimization:function(i){var a,t=s(this),e=t.parents(".imagify-data-actions-container"),n=d.imagify.modal.sanitizeId(e.data("id")),o=d.imagify.modal.sanitizeContext(e.data("context"));i.preventDefault(),d.imagify.modal.isItemLocked(o,n)||(d.imagify.modal.lockItem(o,n),i=t.attr("href"),a=d.imagify.template("imagify-button-processing"),e.html(a({label:t.data("processing-label")})),s.get(i.replace("admin-post.php","admin-ajax.php")).done(function(i){i.data&&i.data.html?d.imagify.modal.displayProcessResult(o,n,i.data.html):d.imagify.beat.interval(15)}))},addToImagifybeat:function(i,e){var a=s(".imagify-data-actions-container");a.length&&(e[d.imagifyModal.imagifybeatID]={},a.each(function(){var i=s(this),a=d.imagify.modal.sanitizeId(i.data("id")),i=d.imagify.modal.sanitizeContext(i.data("context")),t=d.imagify.modal.isItemLocked(i,a)?1:0;e[d.imagifyModal.imagifybeatID][i]=e[d.imagifyModal.imagifybeatID][i]||{},e[d.imagifyModal.imagifybeatID][i]["_"+a]=t}))},processImagifybeat:function(i,a){void 0!==a[d.imagifyModal.imagifybeatID]&&s.each(a[d.imagifyModal.imagifybeatID],function(i,a){var t,i=s.trim(i).match(/^(.+)_(\d+)$/);i&&(t=d.imagify.modal.sanitizeId(i[2]),i=d.imagify.modal.sanitizeContext(i[1]),d.imagify.modal.displayProcessResult(i,t,a))})},displayProcessResult:function(i,a,t){var e=d.imagify.modal.getContainers(i,a);e.html(t),d.imagify.modal.unlockItem(i,a),d.imagify.modal.working.length||(d.imagify.modal.openSlide(e),d.imagify.beat.resetInterval())},openSlide:function(i){i.each(function(){var i=s(this),a=i.find(".imagify-datas-more-action a").data("close");i.find(".imagify-datas-more-action a").addClass("is-open").find(".the-text").text(a),i.find(".imagify-datas-details").show().addClass("is-open")})},toggleSlide:function(i){var a=s(this);i.preventDefault(),a.hasClass("is-open")?(s(a.attr("href")).slideUp(300).removeClass("is-open"),a.removeClass("is-open").find(".the-text").text(a.data("open"))):(s(a.attr("href")).slideDown(300).addClass("is-open"),a.addClass("is-open").find(".the-text").text(a.data("close")))},getContainers:function(i,a){return s('.imagify-data-actions-container[data-id="'+a+'"][data-context="'+i+'"]')},sanitizeId:function(i){return parseInt(i,10)},sanitizeContext:function(i){return(i=i.replace("/[^a-z0-9_-]/gi","").toLowerCase())||"wp"},lockItem:function(i,a){this.isItemLocked(i,a)||this.working.push(i+"_"+a)},unlockItem:function(i,a){i=_.indexOf(this.working,i+"_"+a);-1<i&&this.working.splice(i,1)},isItemLocked:function(i,a){return-1<_.indexOf(this.working,i+"_"+a)}},d.imagify.modal.init()}(jQuery,document,window);

View File

@@ -0,0 +1,127 @@
// All notices =====================================================================================
(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
/**
* Close an Imagify notice.
*/
$( '.imagify-notice-dismiss' ).on( 'click.imagify', function( e ) {
var $this = $( this ),
$parent = $this.parents( '.imagify-welcome, .imagify-notice, .imagify-rkt-notice, .imagify-upsell, .imagify-upsell-admin-bar' ),
href = $this.attr( 'href' );
e.preventDefault();
// Hide the notice.
$parent.fadeTo( 100 , 0, function() {
$( this ).slideUp( 100, function() {
$( this ).remove();
} );
} );
// Save the dismiss notice.
$.get( href.replace( 'admin-post.php', 'admin-ajax.php' ) );
} );
} )(jQuery, document, window);
// The "welcome steps" notice + "wrong API key" notice =============================================
(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
/**
* 1. Create a new Imagify account.
*/
$( '#imagify-signup' ).on( 'click.imagify', function( e ) {
e.preventDefault();
// Display the sign up form.
swal( {
title: imagifyNotices.labels.signupTitle,
html: imagifyNotices.labels.signupText,
confirmButtonText: imagifyNotices.labels.signupConfirmButtonText,
input: 'email',
padding: 0,
showLoaderOnConfirm: true,
customClass: 'imagify-sweet-alert imagify-sweet-alert-signup',
inputValidator: function( inputValue ) {
return new Promise( function( resolve, reject ) {
if ( $.trim( inputValue ) === '' || ! inputValue ) {
reject( imagifyNotices.labels.signupErrorEmptyEmail );
} else {
resolve();
}
} );
},
preConfirm: function( inputValue ) {
return new Promise( function( resolve, reject ) {
setTimeout( function() {
$.get( ajaxurl + w.imagify.concat + 'action=imagify_signup&email=' + inputValue + '&imagifysignupnonce=' + $( '#imagifysignupnonce' ).val() )
.done( function( response ) {
if ( ! response.success ) {
reject( response.data );
} else {
resolve();
}
} );
}, 2000 );
} );
},
} ).then( function() {
swal( {
title: imagifyNotices.labels.signupSuccessTitle,
html: imagifyNotices.labels.signupSuccessText,
type: 'success',
padding: 0,
customClass: 'imagify-sweet-alert'
} );
} );
} );
/**
* 2. Check and save the Imagify API Key.
*/
$( '#imagify-save-api-key' ).on( 'click.imagify', function( e ) {
e.preventDefault();
// Display the API key form.
swal( {
title: imagifyNotices.labels.saveApiKeyTitle,
html: imagifyNotices.labels.saveApiKeyText,
confirmButtonText: imagifyNotices.labels.saveApiKeyConfirmButtonText,
input: 'text',
padding: 0,
showLoaderOnConfirm: true,
customClass: 'imagify-sweet-alert imagify-sweet-alert-signup',
inputValidator: function( inputValue ) {
return new Promise( function( resolve, reject ) {
if ( $.trim( inputValue ) === '' || ! inputValue ) {
reject( imagifyNotices.labels.ApiKeyErrorEmpty );
} else {
resolve();
}
} );
},
preConfirm: function( inputValue ) {
return new Promise( function( resolve, reject ) {
$.get( ajaxurl + w.imagify.concat + 'action=imagify_check_api_key_validity&api_key=' + inputValue + '&imagifycheckapikeynonce=' + $( '#imagifycheckapikeynonce' ).val() )
.done( function( response ) {
if ( ! response.success ) {
reject( response.data );
} else {
resolve();
}
} );
} );
},
} ).then( function() {
swal( {
title: imagifyNotices.labels.ApiKeyCheckSuccessTitle,
html: imagifyNotices.labels.ApiKeyCheckSuccessText,
type: 'success',
padding: 0,
customClass: 'imagify-sweet-alert'
} );
} );
} );
} )(jQuery, document, window);

View File

@@ -0,0 +1 @@
!function(n){n(".imagify-notice-dismiss").on("click.imagify",function(i){var e=n(this),t=e.parents(".imagify-welcome, .imagify-notice, .imagify-rkt-notice, .imagify-upsell, .imagify-upsell-admin-bar"),e=e.attr("href");i.preventDefault(),t.fadeTo(100,0,function(){n(this).slideUp(100,function(){n(this).remove()})}),n.get(e.replace("admin-post.php","admin-ajax.php"))})}(jQuery,(document,window)),function(n,a){n("#imagify-signup").on("click.imagify",function(i){i.preventDefault(),swal({title:imagifyNotices.labels.signupTitle,html:imagifyNotices.labels.signupText,confirmButtonText:imagifyNotices.labels.signupConfirmButtonText,input:"email",padding:0,showLoaderOnConfirm:!0,customClass:"imagify-sweet-alert imagify-sweet-alert-signup",inputValidator:function(t){return new Promise(function(i,e){""!==n.trim(t)&&t?i():e(imagifyNotices.labels.signupErrorEmptyEmail)})},preConfirm:function(i){return new Promise(function(e,t){setTimeout(function(){n.get(ajaxurl+a.imagify.concat+"action=imagify_signup&email="+i+"&imagifysignupnonce="+n("#imagifysignupnonce").val()).done(function(i){i.success?e():t(i.data)})},2e3)})}}).then(function(){swal({title:imagifyNotices.labels.signupSuccessTitle,html:imagifyNotices.labels.signupSuccessText,type:"success",padding:0,customClass:"imagify-sweet-alert"})})}),n("#imagify-save-api-key").on("click.imagify",function(i){i.preventDefault(),swal({title:imagifyNotices.labels.saveApiKeyTitle,html:imagifyNotices.labels.saveApiKeyText,confirmButtonText:imagifyNotices.labels.saveApiKeyConfirmButtonText,input:"text",padding:0,showLoaderOnConfirm:!0,customClass:"imagify-sweet-alert imagify-sweet-alert-signup",inputValidator:function(t){return new Promise(function(i,e){""!==n.trim(t)&&t?i():e(imagifyNotices.labels.ApiKeyErrorEmpty)})},preConfirm:function(i){return new Promise(function(e,t){n.get(ajaxurl+a.imagify.concat+"action=imagify_check_api_key_validity&api_key="+i+"&imagifycheckapikeynonce="+n("#imagifycheckapikeynonce").val()).done(function(i){i.success?e():t(i.data)})})}}).then(function(){swal({title:imagifyNotices.labels.ApiKeyCheckSuccessTitle,html:imagifyNotices.labels.ApiKeyCheckSuccessText,type:"success",padding:0,customClass:"imagify-sweet-alert"})})})}(jQuery,(document,window));

View File

@@ -0,0 +1,993 @@
window.imagify = window.imagify || {};
(function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
/*
* Process an API key check validity.
*/
var busy = false,
xhr = false;
$( '#imagify-settings #api_key' ).on( 'blur.imagify', function() {
var obj = $( this ),
value = obj.val();
if ( $.trim( value ) === '' ) {
return false;
}
if ( $( '#check_api_key' ).val() === value ) {
$( '#imagify-check-api-container' ).html( '<span class="dashicons dashicons-yes"></span> ' + imagifyOptions.labels.ValidApiKeyText );
return false;
}
if ( true === busy ) {
xhr.abort();
} else {
$( '#imagify-check-api-container' ).remove();
obj.after( '<span id="imagify-check-api-container"><span class="imagify-spinner"></span>' + imagifyOptions.labels.waitApiKeyCheckText + '</span>' );
}
busy = true;
xhr = $.get( ajaxurl + w.imagify.concat + 'action=imagify_check_api_key_validity&api_key=' + obj.val() + '&imagifycheckapikeynonce=' + $( '#imagifycheckapikeynonce' ).val() )
.done( function( response ) {
if ( ! response.success ) {
$( '#imagify-check-api-container' ).html( '<span class="dashicons dashicons-no"></span> ' + response.data );
} else {
// Success, the API key is valid.
$( '#imagify-check-api-container' ).remove();
swal( {
title: imagifyOptions.labels.ApiKeyCheckSuccessTitle,
html: imagifyOptions.labels.ApiKeyCheckSuccessText,
type: 'success',
padding: 0,
customClass: 'imagify-sweet-alert'
} ).then( function() {
location.reload();
} );
}
busy = false;
} );
} );
/**
* Check the boxes by clicking "labels" (aria-describedby items).
*/
$( '.imagify-options-line' ).css( 'cursor', 'pointer' ).on( 'click.imagify', function( e ) {
if ( 'INPUT' === e.target.nodeName ) {
return;
}
$( 'input[aria-describedby="' + $( this ).attr( 'id' ) + '"]' ).trigger( 'click.imagify' );
} );
$( '.imagify-settings th span' ).on( 'click.imagify', function() {
var $input = $( this ).parent().next( 'td' ).find( ':checkbox' );
if ( 1 === $input.length ) {
$input.trigger( 'click.imagify' );
}
} );
/**
* Auto check on options-line input value change.
*/
$( '.imagify-options-line' ).find( 'input' ).on( 'change.imagify focus.imagify', function() {
var $checkbox;
if ( 'checkbox' === this.type && ! this.checked ) {
return;
}
$checkbox = $( this ).closest( '.imagify-options-line' ).prev( 'label' ).prev( ':checkbox' );
if ( $checkbox.length && ! $checkbox[0].checked ) {
$checkbox.prop( 'checked', true );
}
} );
/**
* Imagify Backup alert.
*/
$( '[name="imagify_settings[backup]"]' ).on( 'change.imagify', function() {
var $_this = $( this ),
$backupMessage = $_this.siblings( '#backup-dir-is-writable' ),
params = {
'action': 'imagify_check_backup_dir_is_writable',
'_wpnonce': $backupMessage.data( 'nonce' )
};
if ( $_this.is( ':checked' ) ) {
$.getJSON( ajaxurl, params )
.done( function( r ) {
if ( $.isPlainObject( r ) && r.success ) {
if ( r.data.is_writable ) {
// Hide the error message.
$backupMessage.addClass( 'hidden' );
} else {
// Show the error message.
$backupMessage.removeClass( 'hidden' );
}
}
} );
return;
}
// Are you sure? No backup?
swal( {
title: imagifyOptions.labels.noBackupTitle,
html: imagifyOptions.labels.noBackupText,
type: 'warning',
customClass: 'imagify-sweet-alert',
padding: 0,
showCancelButton: true,
cancelButtonText: imagifySwal.labels.cancelButtonText,
reverseButtons: true
} ).then(
function() {
// Leave it unchecked, hide the error message.
$backupMessage.addClass( 'hidden' );
},
function() {
// Re-check.
$_this.prop( 'checked', true );
}
);
} );
/**
* Fade CDN URL field.
*/
$( '[name="imagify_settings[display_webp_method]"]' ).on( 'change.imagify init.imagify', function( e ) {
if ( 'picture' === e.target.value ) {
$( e.target ).closest( '.imagify-radio-group' ).next( '.imagify-options-line' ).removeClass( 'imagify-faded' );
} else {
$( e.target ).closest( '.imagify-radio-group' ).next( '.imagify-options-line' ).addClass( 'imagify-faded' );
}
} ).filter( ':checked' ).trigger( 'init.imagify' );
} )(jQuery, document, window);
// Display Imagify User data =======================================================================
(function(w, d, $, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
if ( ! w.imagifyUser ) {
return;
}
$.getJSON( ajaxurl, w.imagifyUser )
.done( function( r ) {
if ( $.isPlainObject( r ) && r.success ) {
r.data.id = null;
r.data.plan_id = null;
r.data.is = [];
$.each( r.data, function( k, v ) {
var htmlClass = '.imagify-user-' + k.replace( /_/g, '-' );
if ( k.indexOf( 'is_' ) === 0 ) {
if ( v ) {
r.data.is.push( htmlClass );
}
} else if ( 'is' !== k ) {
$( htmlClass ).text( v );
}
} );
r.data.is.push( 'best-plan' );
$( r.data.is.join( ',' ) ).removeClass( 'hidden' );
}
} );
} )(window, document, jQuery);
// Files tree for "custom folders" =================================================================
(function(w, d, $, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
if ( ! imagifyOptions.getFilesTree ) {
return;
}
function imagifyInsertFolderRow( value ) {
var added = false,
prevPath = null,
valueTest, template, $wrap, $rows, $field;
if ( ! value ) {
return;
}
$wrap = $( '#imagify-custom-folders-selected' );
$rows = $wrap.find( '.imagify-custom-folder-line' );
$field = $rows.find( '[value="' + value + '"]' );
if ( $field.length ) {
// Shouldn't happen.
return;
}
// Path #///# Label.
value = value.split( '#///#' );
valueTest = value[1].replace( /\/+$/,'' ).toLowerCase();
template = w.imagify.template( 'imagify-custom-folder' );
$rows.each( function() {
var $this = $( this ),
thisValueTest = $this.data( 'path' ).replace( /\/+$/,'' ).toLowerCase();
if ( '' !== thisValueTest && valueTest.indexOf( thisValueTest ) === 0 ) {
// We try to add a sub-folder of an already selected folder. It shouldn't happen though, since it can't be selected.
added = true;
return false;
} else if ( valueTest < thisValueTest ) {
$this.before( template( {
value: value[0],
label: value[1]
} ) );
$rows = $wrap.find( '.imagify-custom-folder-line' );
added = true;
return false;
}
} );
if ( ! added ) {
$wrap.append( template( {
value: value[0],
label: value[1]
} ) );
$rows = $wrap.find( '.imagify-custom-folder-line' );
}
// Remove sub-paths: if 'a/b/' and 'a/b/c/' are in the array, we keep only the "parent" 'a/b/'.
if ( '' !== valueTest ) {
$rows.each( function() {
var $this = $( this ),
thisPath = $this.data( 'path' ).toLowerCase();
if ( null !== prevPath && thisPath.indexOf( prevPath ) === 0 ) {
$this.find( '.imagify-custom-folders-remove' ).trigger( 'click.imagify' );
} else {
prevPath = thisPath;
}
} );
}
// Display a message.
$wrap.next( '.hidden' ).removeClass( 'hidden' );
}
// Clicking the main button: fetch site's root folders and files, then display them in a modal.
$( '#imagify-add-custom-folder' ).on( 'click.imagify', function() {
var $button = $( this ),
selected = [],
$folders;
if ( $button.prop('disabled') ) {
return;
}
$button.prop( 'disabled', true ).next( 'img' ).attr( 'aria-hidden', 'false' );
$folders = $( '#imagify-custom-folders-selected' );
$folders.find( 'input' ).each( function() {
selected.push( this.value );
} );
$.post( imagifyOptions.getFilesTree, {
folder: '/',
selected: selected
}, null, 'json' )
.done( function( response ) {
if ( ! response.success ) {
swal( {
title: imagifyOptions.labels.error,
html: response.data || '',
type: 'error',
padding: 0,
customClass: 'imagify-sweet-alert'
} );
return;
}
swal( {
title: imagifyOptions.labels.filesTreeTitle,
html: '<div class="imagify-swal-subtitle">' + imagifyOptions.labels.filesTreeSubTitle + '</div><div class="imagify-swal-content"><p class="imagify-folders-information"><i class="dashicons dashicons-info" aria-hidden="true"></i>' + imagifyOptions.labels.cleaningInfo + '</p><ul id="imagify-folders-tree" class="imagify-folders-tree">' + response.data + '</ul></div>',
type: '',
customClass: 'imagify-sweet-alert imagify-swal-has-subtitle imagify-folders-selection',
showCancelButton: true,
padding: 0,
confirmButtonText: imagifyOptions.labels.confirmFilesTreeBtn,
cancelButtonText: imagifySwal.labels.cancelButtonText,
reverseButtons: true
} ).then( function() {
var values = $( '#imagify-folders-tree input' ).serializeArray(); // Don't do `$( '#imagify-folders-tree' ).find( 'input' )`, it won't work.
if ( ! values.length ) {
return;
}
$.each( values, function( i, v ) {
imagifyInsertFolderRow( v.value );
} );
} ).catch( swal.noop );
} )
.fail( function() {
swal( {
title: imagifyOptions.labels.error,
type: 'error',
customClass: 'imagify-sweet-alert',
padding: 0
} );
} )
.always( function(){
$button.prop( 'disabled', false ).next( 'img' ).attr( 'aria-hidden', 'true' );
} );
} );
// Clicking a folder icon in the modal: fetch the folder's sub-folders and files, then display them.
$( d ).on( 'click.imagify', '#imagify-folders-tree [data-folder]', function() {
var $button = $( this ),
$tree = $button.nextAll( '.imagify-folders-sub-tree' ),
selected = [];
if ( $button.prop('disabled') || $button.siblings( ':checkbox' ).is( ':checked' ) ) {
return;
}
$button.prop( 'disabled', true ).addClass( 'imagify-loading' );
if ( $tree.length ) {
if ( $button.hasClass( 'imagify-is-open' ) ) {
$tree.addClass( 'hidden' );
$button.removeClass(' imagify-is-open' );
} else {
$tree.removeClass( 'hidden' );
$button.addClass( 'imagify-is-open' );
}
$button.prop( 'disabled', false ).removeClass( 'imagify-loading' );
return;
}
$( '#imagify-custom-folders-selected' ).find( 'input' ).each( function() {
selected.push( this.value );
} );
$.post( imagifyOptions.getFilesTree, {
folder: $button.data( 'folder' ),
selected: selected
}, null, 'json' )
.done( function( response ) {
if ( ! response.success ) {
swal( {
title: imagifyOptions.labels.error,
html: response.data || '',
type: 'error',
padding: 0,
customClass: 'imagify-sweet-alert'
} );
return;
}
$button.addClass( 'imagify-is-open' ).parent().append( '<ul class="imagify-folders-sub-tree">' + response.data + '</ul>' );
} )
.fail( function(){
swal( {
title: imagifyOptions.labels.error,
type: 'error',
padding: 0,
customClass: 'imagify-sweet-alert'
} );
} )
.always( function(){
$button.prop( 'disabled', false ).removeClass( 'imagify-loading' );
} );
} );
// Clicking a Remove folder button make it disappear.
$( '#imagify-custom-folders' ).on( 'click.imagify', '.imagify-custom-folders-remove', function() {
var $row = $( this ).closest( '.imagify-custom-folder-line' ).addClass( 'imagify-will-remove' );
w.setTimeout( function() {
$row.remove();
// Display a message.
$( '#imagify-custom-folders-selected' ).siblings( '.imagify-success.hidden' ).removeClass( 'hidden' );
}, 750 );
} );
// Clicking the "add themes to folders" button.
$( '#imagify-add-themes-to-custom-folder' ).on( 'click.imagify', function() {
var $this = $( this );
imagifyInsertFolderRow( $this.data( 'theme' ) );
imagifyInsertFolderRow( $this.data( 'theme-parent' ) );
// Remove clicked button.
$this.replaceWith( '<p>' + imagifyOptions.labels.themesAdded + '</p>' );
} );
} )(window, document, jQuery);
// Generate missing WebP versions ==================================================================
/* eslint-disable no-underscore-dangle, consistent-this */
(function(w, d, $, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
if ( ! imagifyOptions.bulk ) {
return;
}
w.imagify.optionsBulk = {
// Properties ==============================================================================
/**
* The current error ID or message.
*
* @var {string|bool} error False if no error.
*/
error: false,
/**
* Set to true at the beginning of the process.
*
* @var {bool} working
*/
working: false,
/**
* Set to true to stop the whole thing.
*
* @var {bool} processIsStopped
*/
processIsStopped: false,
/**
* The button.
*
* @var {jQuery}
*/
$button: null,
/**
* The progress bar wrapper.
*
* @var {jQuery}
*/
$progressWrap: null,
/**
* The progress bar.
*
* @var {jQuery}
*/
$progressBar: null,
/**
* The progress bar text (the %).
*
* @var {jQuery}
*/
$progressText: null,
// Methods =================================================================================
/*
* Init.
*/
init: function () {
var processed, progress;
this.$missingWebpElement = $('.generate-missing-webp');
this.$missingWebpMessage = $('.generate-missing-webp p');
this.$button = $( '#imagify-generate-webp-versions' );
this.$progressWrap = this.$button.siblings( '.imagify-progress' );
this.$progressBar = this.$progressWrap.find( '.bar' );
this.$progressText = this.$progressBar.find( '.percent' );
// Enable/Disable the button when the "Convert to WebP" checkbox is checked/unchecked.
$( '#imagify_convert_to_webp' )
.on( 'change.imagify init.imagify', { imagifyOptionsBulk: this }, this.toggleButton )
.trigger( 'init.imagify' );
// Launch optimization.
this.$button.on( 'click.imagify', { imagifyOptionsBulk: this }, this.maybeLaunchMissingWebpProcess );
// Imagifybeat for optimization queue.
$( d )
.on( 'imagifybeat-send', { imagifyOptionsBulk: this }, this.addQueueImagifybeat )
.on( 'imagifybeat-tick', { imagifyOptionsBulk: this }, this.processQueueImagifybeat )
// Imagifybeat for requirements.
.on( 'imagifybeat-send', this.addRequirementsImagifybeat )
.on( 'imagifybeat-tick', { imagifyOptionsBulk: this }, this.processRequirementsImagifybeat );
if ( false !== imagifyOptions.bulk.progress_webp.total && false !== imagifyOptions.bulk.progress_webp.remaining ) {
// Reset properties.
w.imagify.optionsBulk.error = false;
w.imagify.optionsBulk.working = true;
w.imagify.optionsBulk.processIsStopped = false;
// Disable the button.
this.$button.prop( 'disabled', true ).find( '.dashicons' ).addClass( 'rotate' );
// Fasten Imagifybeat: 1 tick every 15 seconds, and disable suspend.
w.imagify.beat.interval( 15 );
w.imagify.beat.disableSuspend();
this.$missingWebpMessage.hide().attr('aria-hidden', 'true');
processed = imagifyOptions.bulk.progress_webp.total - imagifyOptions.bulk.progress_webp.remaining;
progress = Math.floor( processed / imagifyOptions.bulk.progress_webp.total * 100 );
this.$progressBar.css( 'width', progress + '%' );
this.$progressText.text( processed + '/' + imagifyOptions.bulk.progress_webp.total );
this.$progressWrap.slideDown().attr( 'aria-hidden', 'false' ).removeClass( 'hidden' );
}
},
// Event callbacks =========================================================================
/**
* Enable/Disable the button when the "Convert to WebP" checkbox is checked/unchecked.
*
* @param {object} e Event object.
*/
toggleButton: function ( e ) {
if ( ! this.checked ) {
e.data.imagifyOptionsBulk.$button.prop( 'disabled', true );
} else {
e.data.imagifyOptionsBulk.$button.prop( 'disabled', false );
}
},
/*
* Maybe launch the missing WebP generation process.
*
* @param {object} e Event object.
*/
maybeLaunchMissingWebpProcess: function ( e ) {
if ( ! e.data.imagifyOptionsBulk || e.data.imagifyOptionsBulk.working ) {
return;
}
if ( e.data.imagifyOptionsBulk.hasBlockingError( true ) ) {
return;
}
// Reset properties.
e.data.imagifyOptionsBulk.error = false;
e.data.imagifyOptionsBulk.working = true;
e.data.imagifyOptionsBulk.processIsStopped = false;
// Disable the button.
e.data.imagifyOptionsBulk.$button.prop( 'disabled', true ).find( '.dashicons' ).addClass( 'rotate' );
// Fasten Imagifybeat: 1 tick every 15 seconds, and disable suspend.
w.imagify.beat.interval( 15 );
w.imagify.beat.disableSuspend();
// Launch missing WebP generation process
e.data.imagifyOptionsBulk.launchProcess();
},
// Imagifybeat =============================================================================
/**
* Add a Imagifybeat ID on "imagifybeat-send" event to sync the optimization queue.
*
* @param {object} e Event object.
* @param {object} data Object containing all Imagifybeat IDs.
*/
addQueueImagifybeat: function ( e, data ) {
data[ imagifyOptions.bulk.imagifybeatIDs.progress ] = imagifyOptions.bulk.contexts;
},
/**
* Listen for the custom event "imagifybeat-tick" on $(document).
* It allows to update various data periodically.
*
* @param {object} e Event object.
* @param {object} data Object containing all Imagifybeat IDs.
*/
processQueueImagifybeat: function ( e, data ) {
var images_status, processed, progress;
if ( e.data.imagifyOptionsBulk && typeof data[ imagifyOptions.bulk.imagifybeatIDs.progress ] === 'undefined' ) {
return;
}
if ( e.data.imagifyOptionsBulk.processIsStopped ) {
e.data.imagifyOptionsBulk.processFinished();
return;
}
images_status = data[ imagifyOptions.bulk.imagifybeatIDs.progress ];
if ( images_status.remaining === 0 ) {
e.data.imagifyOptionsBulk.processFinished();
return;
}
processed = images_status.total - images_status.remaining;
progress = Math.floor( processed / images_status.total * 100 );
e.data.imagifyOptionsBulk.$progressBar.css( 'width', progress + '%' );
e.data.imagifyOptionsBulk.$progressText.text( processed + '/' + images_status.total );
},
/**
* Add a Imagifybeat ID for requirements on "imagifybeat-send" event.
*
* @param {object} e Event object.
* @param {object} data Object containing all Imagifybeat IDs.
*/
addRequirementsImagifybeat: function ( e, data ) {
data[ imagifyOptions.bulk.imagifybeatIDs.requirements ] = 1;
},
/**
* Listen for the custom event "imagifybeat-tick" on $(document).
* It allows to update requirements status periodically.
*
* @param {object} e Event object.
* @param {object} data Object containing all Imagifybeat IDs.
*/
processRequirementsImagifybeat: function ( e, data ) {
if ( e.data.imagifyOptionsBulk && typeof data[ imagifyOptions.bulk.imagifybeatIDs.requirements ] === 'undefined' ) {
return;
}
data = data[ imagifyOptions.bulk.imagifybeatIDs.requirements ];
imagifyOptions.bulk.curlMissing = data.curl_missing;
imagifyOptions.bulk.editorMissing = data.editor_missing;
imagifyOptions.bulk.extHttpBlocked = data.external_http_blocked;
imagifyOptions.bulk.apiDown = data.api_down;
imagifyOptions.bulk.keyIsValid = data.key_is_valid;
imagifyOptions.bulk.isOverQuota = data.is_over_quota;
},
// Optimization ============================================================================
/*
* launch the missing WebP generation process.
*/
launchProcess: function () {
var _this;
if ( this.processIsStopped ) {
return;
}
_this = this;
$.get( this.getAjaxUrl( 'MissingWebp', imagifyOptions.bulk.contexts ) )
.done( function( response ) {
var errorMessage;
if ( _this.processIsStopped ) {
return;
}
if ( response.data && response.data.message ) {
errorMessage = response.data.message;
} else {
errorMessage = imagifyOptions.bulk.ajaxErrorText;
}
if ( ! response.success ) {
// Error.
if ( ! _this.error ) {
_this.stopProcess( errorMessage );
}
return;
}
if ( 0 === response.data.total ) {
// No media to process.
_this.stopProcess( 'no-images' );
return;
}
_this.$missingWebpMessage.hide().attr('aria-hidden', 'true');
// Reset and display the progress bar.
_this.$progressText.text( '0' + ( response.data.total ? '/' + response.data.total : '' ) );
_this.$progressWrap.slideDown().attr( 'aria-hidden', 'false' ).removeClass( 'hidden' );
} )
.fail( function() {
// Error.
if ( ! _this.error ) {
_this.stopProcess( 'get-unoptimized-images' );
}
} );
},
/*
* End.
*/
processFinished: function () {
var errorArgs = {};
// Maybe display an error.
if ( false !== this.error ) {
if ( 'invalid-api-key' === this.error ) {
errorArgs = {
title: imagifyOptions.bulk.labels.invalidAPIKeyTitle,
type: 'info'
};
}
else if ( 'over-quota' === this.error ) {
errorArgs = {
title: imagifyOptions.bulk.labels.overQuotaTitle,
html: $( '#tmpl-imagify-overquota-alert' ).html(),
type: 'info',
customClass: 'imagify-swal-has-subtitle imagify-swal-error-header',
showConfirmButton: false
};
}
else if ( 'get-unoptimized-images' === this.error ) {
errorArgs = {
title: imagifyOptions.bulk.labels.getUnoptimizedImagesErrorTitle,
html: imagifyOptions.bulk.labels.getUnoptimizedImagesErrorText,
type: 'info'
};
}
else if ( 'no-images' === this.error ) {
errorArgs = {
title: imagifyOptions.bulk.labels.nothingToDoTitle,
html: imagifyOptions.bulk.labels.nothingToDoText,
type: 'info'
};
}
else if ( 'no-backup' === this.error ) {
errorArgs = {
title: imagifyOptions.bulk.labels.nothingToDoTitle,
html: imagifyOptions.bulk.labels.nothingToDoNoBackupText,
type: 'info'
};
} else {
errorArgs = {
title: imagifyOptions.bulk.labels.error,
html: this.error,
type: 'info'
};
}
this.displayError( errorArgs );
// Reset the error.
this.error = false;
}
// Reset.
this.working = false;
this.processIsStopped = false;
// Reset Imagifybeat interval and enable suspend.
w.imagify.beat.resetInterval();
w.imagify.beat.enableSuspend();
// Reset the progress bar.
this.$progressWrap.slideUp().attr( 'aria-hidden', 'true' ).addClass( 'hidden' );
this.$progressText.text( '0' );
this.$missingWebpElement.hide().attr('aria-hidden', 'true');
this.$button.find( '.dashicons' ).removeClass( 'rotate' );
},
// Tools ===================================================================================
/*
* Tell if we have a blocking error. Can also display an error message in a swal.
*
* @param {bool} displayErrorMessage False to not display any error message.
* @return {bool}
*/
hasBlockingError: function ( displayErrorMessage ) {
displayErrorMessage = undefined !== displayErrorMessage && displayErrorMessage;
if ( imagifyOptions.bulk.curlMissing ) {
if ( displayErrorMessage ) {
this.displayError( {
html: imagifyOptions.bulk.labels.curlMissing
} );
}
return true;
}
if ( imagifyOptions.bulk.editorMissing ) {
if ( displayErrorMessage ) {
this.displayError( {
html: imagifyOptions.bulk.labels.editorMissing
} );
}
return true;
}
if ( imagifyOptions.bulk.extHttpBlocked ) {
if ( displayErrorMessage ) {
this.displayError( {
html: imagifyOptions.bulk.labels.extHttpBlocked
} );
}
return true;
}
if ( imagifyOptions.bulk.apiDown ) {
if ( displayErrorMessage ) {
this.displayError( {
html: imagifyOptions.bulk.labels.apiDown
} );
}
return true;
}
if ( ! imagifyOptions.bulk.keyIsValid ) {
if ( displayErrorMessage ) {
this.displayError( {
title: imagifyOptions.bulk.labels.invalidAPIKeyTitle,
type: 'info'
} );
}
return true;
}
if ( imagifyOptions.bulk.isOverQuota ) {
if ( displayErrorMessage ) {
this.displayError( {
title: imagifyOptions.bulk.labels.overQuotaTitle,
html: $( '#tmpl-imagify-overquota-alert' ).html(),
type: 'info',
customClass: 'imagify-swal-has-subtitle imagify-swal-error-header',
showConfirmButton: false
} );
}
return true;
}
return false;
},
/*
* Display an error message in a modal.
*
* @param {string} title The modal title.
* @param {string} text The modal text.
* @param {object} args Other less common args.
*/
displayError: function ( title, text, args ) {
var def = {
title: '',
html: '',
type: 'error',
customClass: '',
width: 620,
padding: 0,
showCloseButton: true,
showConfirmButton: true
};
if ( $.isPlainObject( title ) ) {
args = $.extend( {}, def, title );
} else {
args = args || {};
args = $.extend( {}, def, {
title: title || '',
html: text || ''
}, args );
}
args.title = args.title || imagifyOptions.bulk.labels.error;
args.customClass += ' imagify-sweet-alert';
swal( args ).catch( swal.noop );
},
/*
* Get the URL used for ajax requests.
*
* @param {string} action An ajax action, or part of it.
* @param {array} context The contexts.
* @return {string}
*/
getAjaxUrl: function ( action, contexts ) {
var url;
url = ajaxurl + w.imagify.concat + '_wpnonce=' + imagifyOptions.bulk.ajaxNonce;
url += '&action=' + imagifyOptions.bulk.ajaxActions[ action ];
url += '&context=' + contexts.join('_');
return url;
},
/*
* Stop everything and set an error.
*
* @param {string} errorId An error ID.
*/
stopProcess: function ( errorId ) {
this.processIsStopped = true;
this.error = errorId;
this.processFinished();
}
};
w.imagify.optionsBulk.init();
} )(window, document, jQuery);
/* eslint-enable no-underscore-dangle, consistent-this */
// "Select all" checkboxes =========================================================================
(function(w, d, $, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
var jqPropHookChecked = $.propHooks.checked;
// Force `.prop()` to trigger a `change` event.
$.propHooks.checked = {
set: function( elem, value, name ) {
var ret;
if ( undefined === jqPropHookChecked ) {
ret = ( elem[ name ] = value );
} else {
ret = jqPropHookChecked( elem, value, name );
}
$( elem ).trigger( 'change.imagify' );
return ret;
}
};
// Check all checkboxes.
$( '.imagify-select-all' ).on( 'click.imagify', function() {
var $_this = $(this),
action = $_this.data( 'action' ),
$btns = $_this.closest( '.imagify-select-all-buttons' ),
$group = $btns.prev( '.imagify-check-group' ),
inactive = 'imagify-is-inactive';
if ( $_this.hasClass( inactive ) ) {
return false;
}
$btns.find( '.imagify-select-all' ).removeClass( inactive ).attr( 'aria-disabled', 'false' );
$_this.addClass( inactive ).attr( 'aria-disabled', 'true' );
$group.find( '.imagify-row-check' )
.prop( 'checked', function() {
var $this = $( this );
if ( $this.is( ':hidden,:disabled' ) ) {
return false;
}
if ( action === 'select' ) {
return true;
}
return false;
} );
} );
// Change buttons status on checkboxes interation.
$( '.imagify-check-group .imagify-row-check' ).on( 'change.imagify', function() {
var $group = $( this ).closest( '.imagify-check-group' ),
$checks = $group.find( '.imagify-row-check' ),
could_be = $checks.filter( ':visible:enabled' ).length,
are_checked = $checks.filter( ':visible:enabled:checked' ).length,
$btns = $group.next( '.imagify-select-all-buttons' ),
inactive = 'imagify-is-inactive';
// Toggle status of "check all" buttons.
if ( are_checked === 0 ) {
$btns.find( '[data-action="unselect"]' ).addClass( inactive ).attr( 'aria-disabled', 'true' );
}
if ( are_checked === could_be ) {
$btns.find( '[data-action="select"]' ).addClass( inactive ).attr( 'aria-disabled', 'true' );
}
if ( are_checked !== could_be && are_checked > 0 ) {
$btns.find( '.imagify-select-all' ).removeClass( inactive ).attr( 'aria-disabled', 'false' );
}
} );
} )(window, document, jQuery);

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long