iasapi : {},
hold : {},
postid : '',
+ _view : false,
intval : function(f) {
+ /*
+ * Bitwise OR operator: one of the obscure ways to truncate floating point figures,
+ * worth reminding JavaScript doesn't have a distinct "integer" type.
+ */
return f | 0;
},
- setDisabled : function(el, s) {
+ setDisabled : function( el, s ) {
+ /*
+ * `el` can be a single form element or a fieldset. Before #28864, the disabled state on
+ * some text fields was handled targeting $('input', el). Now we need to handle the
+ * disabled state on buttons too so we can just target `el` regardless if it's a single
+ * element or a fieldset because when a fieldset is disabled, its descendants are disabled too.
+ */
if ( s ) {
- el.removeClass('disabled');
- $('input', el).removeAttr('disabled');
+ el.removeClass( 'disabled' ).prop( 'disabled', false );
} else {
- el.addClass('disabled');
- $('input', el).prop('disabled', true);
+ el.addClass( 'disabled' ).prop( 'disabled', true );
}
},
var wait = $('#imgedit-wait-' + postid);
if ( toggle ) {
- wait.height( $('#imgedit-panel-' + postid).height() ).fadeIn('fast');
+ wait.fadeIn( 'fast' );
} else {
wait.fadeOut('fast');
}
},
toggleHelp : function(el) {
- $(el).siblings('.imgedit-help').slideToggle('fast');
+ var $el = $( el );
+ $el
+ .attr( 'aria-expanded', 'false' === $el.attr( 'aria-expanded' ) ? 'true' : 'false' )
+ .parents( '.imgedit-group-top' ).toggleClass( 'imgedit-help-toggled' ).find( '.imgedit-help' ).slideToggle( 'fast' );
+
return false;
},
return $('input[name="imgedit-target-' + postid + '"]:checked', '#imgedit-save-target-' + postid).val() || 'full';
},
- scaleChanged : function(postid, x) {
+ scaleChanged : function( postid, x, el ) {
var w = $('#imgedit-scale-width-' + postid), h = $('#imgedit-scale-height-' + postid),
warn = $('#imgedit-scale-warn-' + postid), w1 = '', h1 = '';
+ if ( false === this.validateNumeric( el ) ) {
+ return;
+ }
+
if ( x ) {
h1 = ( w.val() !== '' ) ? Math.round( w.val() / this.hold.xy_ratio ) : '';
h.val( h1 );
'rand': t.intval(Math.random() * 1000000)
};
- img = $('<img id="image-preview-' + postid + '" />')
- .on('load', function() {
- var max1, max2, parent = $('#imgedit-crop-' + postid), t = imageEdit;
+ img = $( '<img id="image-preview-' + postid + '" alt="" />' )
+ .on( 'load', { history: data.history }, function( event ) {
+ var max1, max2,
+ parent = $( '#imgedit-crop-' + postid ),
+ t = imageEdit,
+ historyObj;
+
+ if ( '' !== event.data.history ) {
+ historyObj = JSON.parse( event.data.history );
+ // If last executed action in history is a crop action.
+ if ( historyObj[historyObj.length - 1].hasOwnProperty( 'c' ) ) {
+ /*
+ * A crop action has completed and the crop button gets disabled
+ * ensure the undo button is enabled.
+ */
+ t.setDisabled( $( '#image-undo-' + postid) , true );
+ // Move focus to the undo button to avoid a focus loss.
+ $( '#image-undo-' + postid ).focus();
+ }
+ }
parent.empty().append(img);
$.post(ajaxurl, data, function(r) {
$('#image-editor-' + postid).empty().append(r);
t.toggleEditor(postid, 0);
+ // refresh the attachment model so that changes propagate
+ if ( t._view ) {
+ t._view.refresh();
+ }
});
},
save : function(postid, nonce) {
- var data, target = this.getTarget(postid), history = this.filterHistory(postid, 0);
+ var data,
+ target = this.getTarget(postid),
+ history = this.filterHistory(postid, 0),
+ self = this;
if ( '' === history ) {
return false;
var ret = JSON.parse(r);
if ( ret.error ) {
- $('#imgedit-response-' + postid).html('<div class="error"><p>' + ret.error + '</p><div>');
+ $('#imgedit-response-' + postid).html('<div class="error"><p>' + ret.error + '</p></div>');
imageEdit.close(postid);
return;
}
$('#imgedit-response-' + postid).html('<div class="updated"><p>' + ret.msg + '</p></div>');
}
- imageEdit.close(postid);
+ if ( self._view ) {
+ self._view.save();
+ } else {
+ imageEdit.close(postid);
+ }
});
},
- open : function(postid, nonce) {
- var data, elem = $('#image-editor-' + postid), head = $('#media-head-' + postid),
+ open : function( postid, nonce, view ) {
+ this._view = view;
+
+ var dfd, data, elem = $('#image-editor-' + postid), head = $('#media-head-' + postid),
btn = $('#imgedit-open-btn-' + postid), spin = btn.siblings('.spinner');
- btn.prop('disabled', true);
- spin.show();
+ /*
+ * Instead of disabling the button, which causes a focus loss and makes screen
+ * readers announce "unavailable", return if the button was already clicked.
+ */
+ if ( btn.hasClass( 'button-activated' ) ) {
+ return;
+ }
+
+ spin.addClass( 'is-active' );
data = {
'action': 'image-editor',
'do': 'open'
};
- elem.load(ajaxurl, data, function() {
- elem.fadeIn('fast');
+ dfd = $.ajax({
+ url: ajaxurl,
+ type: 'post',
+ data: data,
+ beforeSend: function() {
+ btn.addClass( 'button-activated' );
+ }
+ }).done(function( html ) {
+ elem.html( html );
head.fadeOut('fast', function(){
- btn.removeAttr('disabled');
- spin.hide();
+ elem.fadeIn('fast');
+ btn.removeClass( 'button-activated' );
+ spin.removeClass( 'is-active' );
});
+ // Initialise the Image Editor now that everything is ready.
+ imageEdit.init( postid );
});
+
+ return dfd;
},
imgLoaded : function(postid) {
this.initCrop(postid, img, parent);
this.setCropSelection(postid, 0);
this.toggleEditor(postid, 0);
+ // Editor is ready, move focus to the first focusable element.
+ $( '.imgedit-wrap .imgedit-help-toggle' ).eq( 0 ).focus();
},
initCrop : function(postid, image, parent) {
- var t = this, selW = $('#imgedit-sel-width-' + postid),
- selH = $('#imgedit-sel-height-' + postid);
+ var t = this,
+ selW = $('#imgedit-sel-width-' + postid),
+ selH = $('#imgedit-sel-height-' + postid),
+ $img;
t.iasapi = $(image).imgAreaSelect({
parent: parent,
minWidth: 3,
minHeight: 3,
- onInit: function() {
+ onInit: function( img ) {
+ // Ensure that the imgareaselect wrapper elements are position:absolute
+ // (even if we're in a position:fixed modal)
+ $img = $( img );
+ $img.next().css( 'position', 'absolute' )
+ .nextAll( '.imgareaselect-outer' ).css( 'position', 'absolute' );
+
parent.children().mousedown(function(e){
var ratio = false, sel, defRatio;
},
setCropSelection : function(postid, c) {
- var sel, min = $('#imgedit-minthumb-' + postid).val() || '128:128',
- sizer = this.hold.sizer;
- min = min.split(':');
- c = c || 0;
+ var sel;
+
+ c = c || 0;
if ( !c || ( c.width < 3 && c.height < 3 ) ) {
this.setDisabled($('.imgedit-crop', '#imgedit-panel-' + postid), 0);
return false;
}
- if ( c.width < (min[0] * sizer) && c.height < (min[1] * sizer) ) {
- this.setDisabled($('.imgedit-crop', '#imgedit-panel-' + postid), 0);
- $('#imgedit-selection-' + postid).val('');
- return false;
- }
-
sel = { 'x': c.x1, 'y': c.y1, 'w': c.width, 'h': c.height };
this.setDisabled($('.imgedit-crop', '#imgedit-panel-' + postid), 1);
$('#imgedit-selection-' + postid).val( JSON.stringify(sel) );
this.iasapi = {};
this.hold = {};
- $('#image-editor-' + postid).fadeOut('fast', function() {
- $('#media-head-' + postid).fadeIn('fast');
- $(this).empty();
- });
+
+ // If we've loaded the editor in the context of a Media Modal, then switch to the previous view,
+ // whatever that might have been.
+ if ( this._view ){
+ this._view.back();
+ }
+
+ // In case we are not accessing the image editor in the context of a View, close the editor the old-skool way
+ else {
+ $('#image-editor-' + postid).fadeOut('fast', function() {
+ $( '#media-head-' + postid ).fadeIn( 'fast', function() {
+ // Move focus back to the Edit Image button. Runs also when saving.
+ $( '#imgedit-open-btn-' + postid ).focus();
+ });
+ $(this).empty();
+ });
+ }
+
+
},
notsaved : function(postid) {
addStep : function(op, postid, nonce) {
var t = this, elem = $('#imgedit-history-' + postid),
- history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [],
- undone = $('#imgedit-undone-' + postid),
- pop = t.intval(undone.val());
+ history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [],
+ undone = $( '#imgedit-undone-' + postid ),
+ pop = t.intval( undone.val() );
while ( pop > 0 ) {
history.pop();
elem.val(pop);
t.refreshEditor(postid, nonce, function() {
var elem = $('#imgedit-history-' + postid),
- history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [];
+ history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [];
t.setDisabled($('#image-redo-' + postid), true);
t.setDisabled(button, pop < history.length);
+ // When undo gets disabled, move focus to the redo button to avoid a focus loss.
+ if ( history.length === pop ) {
+ $( '#image-redo-' + postid ).focus();
+ }
});
},
t.refreshEditor(postid, nonce, function() {
t.setDisabled($('#image-undo-' + postid), true);
t.setDisabled(button, pop > 0);
+ // When redo gets disabled, move focus to the undo button to avoid a focus loss.
+ if ( 0 === pop ) {
+ $( '#image-undo-' + postid ).focus();
+ }
});
},
- setNumSelection : function(postid) {
+ setNumSelection : function( postid, el ) {
var sel, elX = $('#imgedit-sel-width-' + postid), elY = $('#imgedit-sel-height-' + postid),
x = this.intval( elX.val() ), y = this.intval( elY.val() ),
img = $('#image-preview-' + postid), imgh = img.height(), imgw = img.width(),
sizer = this.hold.sizer, x1, y1, x2, y2, ias = this.iasapi;
+ if ( false === this.validateNumeric( el ) ) {
+ return;
+ }
+
if ( x < 1 ) {
elX.val('');
return false;
y = this.intval( $('#imgedit-crop-height-' + postid).val() ),
h = $('#image-preview-' + postid).height();
- if ( !this.intval( $(el).val() ) ) {
- $(el).val('');
+ if ( false === this.validateNumeric( el ) ) {
return;
}
this.iasapi.update();
}
}
+ },
+
+ validateNumeric: function( el ) {
+ if ( ! this.intval( $( el ).val() ) ) {
+ $( el ).val( '' );
+ return false;
+ }
}
};
})(jQuery);