diff --git a/src/wp-admin/js/image-edit.js b/src/wp-admin/js/image-edit.js
index 9eaf51bf88..af908d553f 100644
--- a/src/wp-admin/js/image-edit.js
+++ b/src/wp-admin/js/image-edit.js
@@ -5,6 +5,7 @@ var imageEdit = window.imageEdit = {
iasapi : {},
hold : {},
postid : '',
+ _view : false,
intval : function(f) {
return f | 0;
@@ -241,11 +242,18 @@ var imageEdit = window.imageEdit = {
$.post(ajaxurl, data, function(r) {
$('#image-editor-' + postid).empty().append(r);
t.toggleEditor(postid, 0);
+ // refresh the attachment model so that changes propagate
+ if ( this._view ) {
+ this._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;
@@ -283,11 +291,17 @@ var imageEdit = window.imageEdit = {
$('#imgedit-response-' + postid).html('
');
}
- imageEdit.close(postid);
+ if ( self._view ) {
+ self._view.save();
+ } else {
+ imageEdit.close(postid);
+ }
});
},
- open : function(postid, nonce) {
+ open : function( postid, nonce, view ) {
+ this._view = view;
+
var data, elem = $('#image-editor-' + postid), head = $('#media-head-' + postid),
btn = $('#imgedit-open-btn-' + postid), spin = btn.siblings('.spinner');
@@ -319,8 +333,10 @@ var imageEdit = window.imageEdit = {
},
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,
@@ -330,7 +346,13 @@ var imageEdit = window.imageEdit = {
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;
@@ -397,10 +419,22 @@ var imageEdit = window.imageEdit = {
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');
+ $(this).empty();
+ });
+ }
+
+
},
notsaved : function(postid) {
diff --git a/src/wp-includes/css/media-views.css b/src/wp-includes/css/media-views.css
index b653be51e5..3e9a5125e5 100644
--- a/src/wp-includes/css/media-views.css
+++ b/src/wp-includes/css/media-views.css
@@ -1431,6 +1431,22 @@
overflow: hidden;
}
+/**
+ * Image Editor
+ */
+
+.media-frame .image-editor {
+ padding: 16px;
+}
+
+.media-frame .imgedit-wrap table td {
+ vertical-align: top;
+ padding-top: 0;
+}
+
+.media-frame .imgedit-wrap table td.imgedit-settings {
+ width: 250px;
+}
/**
* Embed from URL and Image Details
*/
@@ -1490,6 +1506,10 @@
display: block;
}
+.media-embed .edit-attachment {
+ margin-left: 10px;
+}
+
.media-embed .thumbnail:after {
content: '';
display: block;
diff --git a/src/wp-includes/js/media-editor.js b/src/wp-includes/js/media-editor.js
index a0cb52ea03..f065c0e8b0 100644
--- a/src/wp-includes/js/media-editor.js
+++ b/src/wp-includes/js/media-editor.js
@@ -698,7 +698,7 @@
this._frame = wp.media({
state: 'featured-image',
- states: [ new wp.media.controller.FeaturedImage() ]
+ states: [ new wp.media.controller.FeaturedImage() , new wp.media.controller.EditImage() ]
});
this._frame.on( 'toolbar:create:featured-image', function( toolbar ) {
@@ -710,6 +710,17 @@
});
}, this._frame );
+ this._frame.on( 'content:render:edit-image', function() {
+ var selection = this.state('featured-image').get('selection'),
+ view = new wp.media.view.EditImage( { model: selection.single(), controller: this } ).render();
+
+ this.content.set( view );
+
+ // after bringing in the frame, load the actual editor via an ajax call
+ view.loadEditor();
+
+ }, this._frame );
+
this._frame.state('featured-image').on( 'select', this.select );
return this._frame;
},
diff --git a/src/wp-includes/js/media-models.js b/src/wp-includes/js/media-models.js
index adbbca249f..ea3fed8093 100644
--- a/src/wp-includes/js/media-models.js
+++ b/src/wp-includes/js/media-models.js
@@ -371,6 +371,7 @@ window.wp = window.wp || {};
bindAttachmentListeners: function() {
this.listenTo( this.attachment, 'sync', this.setLinkTypeFromUrl );
+ this.listenTo( this.attachment, 'change', this.updateSize );
},
changeAttachment: function( attachment, props ) {
diff --git a/src/wp-includes/js/media-views.js b/src/wp-includes/js/media-views.js
index 1a9328d1ef..375ad42c05 100644
--- a/src/wp-includes/js/media-views.js
+++ b/src/wp-includes/js/media-views.js
@@ -483,6 +483,54 @@
};
});
+ media.selectionSync = {
+ syncSelection: function() {
+ var selection = this.get('selection'),
+ manager = this.frame._selection;
+
+ if ( ! this.get('syncSelection') || ! manager || ! selection ) {
+ return;
+ }
+
+ // If the selection supports multiple items, validate the stored
+ // attachments based on the new selection's conditions. Record
+ // the attachments that are not included; we'll maintain a
+ // reference to those. Other attachments are considered in flux.
+ if ( selection.multiple ) {
+ selection.reset( [], { silent: true });
+ selection.validateAll( manager.attachments );
+ manager.difference = _.difference( manager.attachments.models, selection.models );
+ }
+
+ // Sync the selection's single item with the master.
+ selection.single( manager.single );
+ },
+
+ /**
+ * Record the currently active attachments, which is a combination
+ * of the selection's attachments and the set of selected
+ * attachments that this specific selection considered invalid.
+ * Reset the difference and record the single attachment.
+ */
+ recordSelection: function() {
+ var selection = this.get('selection'),
+ manager = this.frame._selection;
+
+ if ( ! this.get('syncSelection') || ! manager || ! selection ) {
+ return;
+ }
+
+ if ( selection.multiple ) {
+ manager.attachments.reset( selection.toArray().concat( manager.difference ) );
+ manager.difference = [];
+ } else {
+ manager.attachments.add( selection.toArray() );
+ }
+
+ manager.single = selection._single;
+ }
+ };
+
/**
* wp.media.controller.Library
*
@@ -635,51 +683,6 @@
return _.contains( media.view.settings.embedExts, attachment.get('filename').split('.').pop() );
},
- syncSelection: function() {
- var selection = this.get('selection'),
- manager = this.frame._selection;
-
- if ( ! this.get('syncSelection') || ! manager || ! selection ) {
- return;
- }
-
- // If the selection supports multiple items, validate the stored
- // attachments based on the new selection's conditions. Record
- // the attachments that are not included; we'll maintain a
- // reference to those. Other attachments are considered in flux.
- if ( selection.multiple ) {
- selection.reset( [], { silent: true });
- selection.validateAll( manager.attachments );
- manager.difference = _.difference( manager.attachments.models, selection.models );
- }
-
- // Sync the selection's single item with the master.
- selection.single( manager.single );
- },
-
- /**
- * Record the currently active attachments, which is a combination
- * of the selection's attachments and the set of selected
- * attachments that this specific selection considered invalid.
- * Reset the difference and record the single attachment.
- */
- recordSelection: function() {
- var selection = this.get('selection'),
- manager = this.frame._selection;
-
- if ( ! this.get('syncSelection') || ! manager || ! selection ) {
- return;
- }
-
- if ( selection.multiple ) {
- manager.attachments.reset( selection.toArray().concat( manager.difference ) );
- manager.difference = [];
- } else {
- manager.attachments.add( selection.toArray() );
- }
-
- manager.single = selection._single;
- },
/**
* If the state is active, no items are selected, and the current
@@ -734,6 +737,8 @@
}
});
+ _.extend( media.controller.Library.prototype, media.selectionSync );
+
/**
* wp.media.controller.ImageDetails
*
@@ -989,7 +994,7 @@
toolbar: 'featured-image',
title: l10n.setFeaturedImageTitle,
priority: 60,
- syncSelection: false
+ syncSelection: true
}, media.controller.Library.prototype.defaults ),
initialize: function() {
@@ -1070,7 +1075,7 @@
toolbar: 'replace',
title: l10n.replaceImageTitle,
priority: 60,
- syncSelection: false
+ syncSelection: true
}, media.controller.Library.prototype.defaults ),
initialize: function( options ) {
@@ -1120,6 +1125,63 @@
}
});
+ /**
+ * wp.media.controller.EditImage
+ *
+ * @constructor
+ * @augments wp.media.controller.State
+ * @augments Backbone.Model
+ */
+ media.controller.EditImage = media.controller.State.extend({
+ defaults: {
+ id: 'edit-image',
+ url: '',
+ menu: false,
+ toolbar: 'edit-image',
+ title: l10n.editImage,
+ content: 'edit-image',
+ syncSelection: true
+ },
+
+ activate: function() {
+ if ( ! this.get('selection') ) {
+ this.set( 'selection', new media.model.Selection() );
+ }
+ this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar );
+ this.syncSelection();
+ },
+
+ deactivate: function() {
+ this.stopListening( this.frame );
+ },
+
+ toolbar: function() {
+ var frame = this.frame,
+ lastState = frame.lastState(),
+ previous = lastState && lastState.id;
+
+ frame.toolbar.set( new media.view.Toolbar({
+ controller: frame,
+ items: {
+ back: {
+ style: 'primary',
+ text: l10n.back,
+ priority: 20,
+ click: function() {
+ if ( previous ) {
+ frame.setState( previous );
+ } else {
+ frame.close();
+ }
+ }
+ }
+ }
+ }) );
+ }
+ });
+
+ _.extend( media.controller.EditImage.prototype, media.selectionSync );
+
/**
* wp.media.controller.ReplaceVideo
*
@@ -1928,6 +1990,8 @@
// Embed states.
new media.controller.Embed(),
+ new media.controller.EditImage( { selection: options.selection } ),
+
// Gallery states.
new media.controller.CollectionEdit({
type: 'image',
@@ -2043,6 +2107,7 @@
content: {
'embed': 'embedContent',
+ 'edit-image': 'editImageContent',
'edit-selection': 'editSelectionContent'
},
@@ -2195,6 +2260,17 @@
this.content.set( view );
},
+ editImageContent: function() {
+ var selection = this.state().get('selection'),
+ view = new media.view.EditImage( { model: selection.single(), controller: this } ).render();
+
+ this.content.set( view );
+
+ // after creating the wrapper view, load the actual editor via an ajax call
+ view.loadEditor();
+
+ },
+
// Toolbars
/**
@@ -2537,6 +2613,7 @@
media.view.MediaFrame.Select.prototype.bindHandlers.apply( this, arguments );
this.on( 'menu:create:image-details', this.createMenu, this );
this.on( 'content:render:image-details', this.renderImageDetailsContent, this );
+ this.on( 'content:render:edit-image', this.editImageContent, this );
this.on( 'menu:render:image-details', this.renderMenu, this );
this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );
// override the select toolbar
@@ -2560,7 +2637,11 @@
toolbar: 'replace',
priority: 80,
displaySettings: true
- })
+ }),
+ new media.controller.EditImage( {
+ image: this.image,
+ selection: this.options.selection
+ } )
]);
},
@@ -2575,6 +2656,32 @@
},
+ editImageContent: function() {
+ var state = this.state(),
+ attachment = state.get('image').attachment,
+ model,
+ view;
+
+ if ( ! attachment ) {
+ return;
+ }
+
+ model = state.get('selection').single();
+
+ if ( ! model ) {
+ model = attachment;
+ }
+
+ view = new media.view.EditImage( { model: model, controller: this } ).render();
+
+ this.content.set( view );
+
+ // after bringing in the frame, load the actual editor via an ajax call
+ view.loadEditor();
+
+ },
+
+
renderMenu: function( view ) {
var lastState = this.lastState(),
previous = lastState && lastState.id,
@@ -5864,8 +5971,9 @@
}
},
- editAttachment: function() {
- this.$el.addClass('needs-refresh');
+ editAttachment: function( event ) {
+ event.preventDefault();
+ this.controller.setState( 'edit-image' );
},
/**
* @param {Object} event
@@ -5875,6 +5983,7 @@
event.preventDefault();
this.model.fetch();
}
+
});
/**
@@ -6158,10 +6267,15 @@
media.view.ImageDetails = media.view.Settings.AttachmentDisplay.extend({
className: 'image-details',
template: media.template('image-details'),
-
+ events: _.defaults( media.view.Settings.AttachmentDisplay.prototype.events, {
+ 'click .edit-attachment': 'editAttachment'
+ } ),
initialize: function() {
// used in AttachmentDisplay.prototype.updateLinkTo
this.options.attachment = this.model.attachment;
+ if ( this.model.attachment ) {
+ this.listenTo( this.model.attachment, 'change:url', this.updateUrl );
+ }
media.view.Settings.AttachmentDisplay.prototype.initialize.apply( this, arguments );
},
@@ -6177,7 +6291,6 @@
}, this.options );
},
-
render: function() {
var self = this,
args = arguments;
@@ -6198,9 +6311,64 @@
resetFocus: function() {
this.$( '.caption textarea' ).focus();
this.$( '.embed-image-settings' ).scrollTop( 0 );
+ },
+
+ updateUrl: function() {
+ this.$( '.thumbnail img' ).attr( 'src', this.model.get('url' ) );
+ this.$( '.url' ).val( this.model.get('url' ) );
+ },
+
+ editAttachment: function( event ) {
+ event.preventDefault();
+ this.controller.setState( 'edit-image' );
}
});
+
+ media.view.EditImage = media.View.extend({
+
+ className: 'image-editor',
+ template: media.template('image-editor'),
+
+ initialize: function( options ) {
+ this.editor = window.imageEdit;
+ this.controller = options.controller;
+ media.View.prototype.initialize.apply( this, arguments );
+ },
+
+ prepare: function() {
+ return this.model.toJSON();
+ },
+
+ render: function() {
+ media.View.prototype.render.apply( this, arguments );
+ return this;
+ },
+
+ loadEditor: function() {
+ this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this );
+ },
+
+ back: function() {
+ var lastState = this.controller.lastState();
+ this.controller.setState( lastState );
+ },
+
+ refresh: function() {
+ this.model.fetch();
+ },
+
+ save: function() {
+ var self = this,
+ lastState = this.controller.lastState();
+
+ this.model.fetch().done( function() {
+ self.controller.setState( lastState );
+ });
+ }
+
+ });
+
/**
* wp.media.view.AudioDetails
*
diff --git a/src/wp-includes/media-template.php b/src/wp-includes/media-template.php
index 8fcbb0831e..54b9780559 100644
--- a/src/wp-includes/media-template.php
+++ b/src/wp-includes/media-template.php
@@ -559,6 +559,9 @@ function wp_print_media_templates() {
+ <# if ( data.attachment ) { #>
+
+ <# } #>
@@ -649,6 +652,11 @@ function wp_print_media_templates() {
+
+