<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[27050] trunk/src/wp-includes: Introduce Edit Image (single mode) in the media modal and use it to edit images inserted in the editor.</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://core.trac.wordpress.org/changeset/27050">27050</a></dd>
<dt>Author</dt> <dd>azaozz</dd>
<dt>Date</dt> <dd>2014-01-28 21:16:42 +0000 (Tue, 28 Jan 2014)</dd>
</dl>
<h3>Log Message</h3>
<pre>Introduce Edit Image (single mode) in the media modal and use it to edit images inserted in the editor. Adds new feature: replace an image in the editor. Props gcorne, see <a href="http://core.trac.wordpress.org/ticket/24409">#24409</a>.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesclasswpeditorphp">trunk/src/wp-includes/class-wp-editor.php</a></li>
<li><a href="#trunksrcwpincludescssmediaviewscss">trunk/src/wp-includes/css/media-views.css</a></li>
<li><a href="#trunksrcwpincludesjsmediamodelsjs">trunk/src/wp-includes/js/media-models.js</a></li>
<li><a href="#trunksrcwpincludesjsmediaviewsjs">trunk/src/wp-includes/js/media-views.js</a></li>
<li><a href="#trunksrcwpincludesjstinymcepluginswpeditimagepluginjs">trunk/src/wp-includes/js/tinymce/plugins/wpeditimage/plugin.js</a></li>
<li><a href="#trunksrcwpincludesmediatemplatephp">trunk/src/wp-includes/media-template.php</a></li>
<li><a href="#trunksrcwpincludesmediaphp">trunk/src/wp-includes/media.php</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesclasswpeditorphp"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/class-wp-editor.php (27049 => 27050)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/class-wp-editor.php 2014-01-28 08:25:00 UTC (rev 27049)
+++ trunk/src/wp-includes/class-wp-editor.php 2014-01-28 21:16:42 UTC (rev 27050)
</span><span class="lines">@@ -228,7 +228,6 @@
</span><span class="cx"> 'paste',
</span><span class="cx"> 'tabfocus',
</span><span class="cx"> 'textcolor',
</span><del>- 'image',
</del><span class="cx"> 'fullscreen',
</span><span class="cx"> 'wordpress',
</span><span class="cx"> 'wpeditimage',
</span></span></pre></div>
<a id="trunksrcwpincludescssmediaviewscss"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/css/media-views.css (27049 => 27050)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/css/media-views.css 2014-01-28 08:25:00 UTC (rev 27049)
+++ trunk/src/wp-includes/css/media-views.css 2014-01-28 21:16:42 UTC (rev 27050)
</span><span class="lines">@@ -86,6 +86,10 @@
</span><span class="cx"> color: #a9a9a9;
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+.media-frame .hidden {
+ display: none;
+}
+
</ins><span class="cx"> /* Enable draggable on IE10 touch events until it's rolled into jQuery UI core */
</span><span class="cx"> .ui-sortable,
</span><span class="cx"> .ui-draggable {
</span><span class="lines">@@ -1411,7 +1415,7 @@
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> /**
</span><del>- * Embed from URL
</del><ins>+ * Embed from URL and Image Details
</ins><span class="cx"> */
</span><span class="cx"> .embed-url {
</span><span class="cx"> display: block;
</span><span class="lines">@@ -1452,6 +1456,10 @@
</span><span class="cx"> overflow: auto;
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+.image-details .embed-image-settings {
+ top: 0;
+}
+
</ins><span class="cx"> .media-embed .thumbnail {
</span><span class="cx"> max-width: 100%;
</span><span class="cx"> max-height: 200px;
</span><span class="lines">@@ -1484,6 +1492,10 @@
</span><span class="cx"> clear: both;
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+.media-embed .setting .hidden {
+ display: none;
+}
+
</ins><span class="cx"> .media-embed .setting span {
</span><span class="cx"> display: block;
</span><span class="cx"> width: 200px;
</span><span class="lines">@@ -1926,4 +1938,4 @@
</span><span class="cx"> .media-frame .spinner {
</span><span class="cx"> background-image: url('../images/spinner-2x.gif');
</span><span class="cx"> }
</span><del>-}
</del><span class="cx">\ No newline at end of file
</span><ins>+}
</ins></span></pre></div>
<a id="trunksrcwpincludesjsmediamodelsjs"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/js/media-models.js (27049 => 27050)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/js/media-models.js 2014-01-28 08:25:00 UTC (rev 27049)
+++ trunk/src/wp-includes/js/media-models.js 2014-01-28 21:16:42 UTC (rev 27050)
</span><span class="lines">@@ -2,7 +2,7 @@
</span><span class="cx"> window.wp = window.wp || {};
</span><span class="cx">
</span><span class="cx"> (function($){
</span><del>- var Attachment, Attachments, Query, compare, l10n, media;
</del><ins>+ var Attachment, Attachments, Query, PostImage, compare, l10n, media;
</ins><span class="cx">
</span><span class="cx"> /**
</span><span class="cx"> * wp.media( attributes )
</span><span class="lines">@@ -30,6 +30,8 @@
</span><span class="cx"> frame = new MediaFrame.Select( attributes );
</span><span class="cx"> } else if ( 'post' === attributes.frame && MediaFrame.Post ) {
</span><span class="cx"> frame = new MediaFrame.Post( attributes );
</span><ins>+ } else if ( 'image' === attributes.frame && MediaFrame.ImageDetails ) {
+ frame = new MediaFrame.ImageDetails( attributes );
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> delete attributes.frame;
</span><span class="lines">@@ -340,6 +342,121 @@
</span><span class="cx"> });
</span><span class="cx">
</span><span class="cx"> /**
</span><ins>+ * wp.media.model.Attachment
+ *
+ * @constructor
+ * @augments Backbone.Model
+ *
+ **/
+ PostImage = media.model.PostImage = Backbone.Model.extend({
+
+ initialize: function( attributes ) {
+ this.attachment = false;
+
+ if ( attributes.attachment_id ) {
+ this.attachment = media.model.Attachment.get( attributes.attachment_id );
+ this.dfd = this.attachment.fetch();
+ this.bindAttachmentListeners();
+ }
+
+ // keep url in sync with changes to the type of link
+ this.on( 'change:link', this.updateLinkUrl, this );
+ this.on( 'change:size', this.updateSize, this );
+
+ this.setLinkTypeFromUrl();
+
+ },
+
+ bindAttachmentListeners: function() {
+ this.listenTo( this.attachment, 'sync', this.setLinkTypeFromUrl );
+ },
+
+ changeAttachment: function( attachment, props ) {
+ this.stopListening( this.attachment );
+ this.attachment = attachment;
+ this.bindAttachmentListeners();
+
+ this.set( 'attachment_id', this.attachment.get( 'id' ) );
+ this.set( 'caption', this.attachment.get( 'caption' ) );
+ this.set( 'alt', this.attachment.get( 'alt' ) );
+ this.set( 'size', props.get( 'size' ) );
+ this.set( 'align', props.get( 'align' ) );
+ this.set( 'link', props.get( 'link' ) );
+ this.updateLinkUrl();
+ this.updateSize();
+ },
+
+ setLinkTypeFromUrl: function() {
+ var linkUrl = this.get( 'linkUrl' ),
+ type;
+
+ if ( ! linkUrl ) {
+ this.set( 'link', 'none' );
+ return;
+ }
+
+ // default to custom if there is a linkUrl
+ type = 'custom';
+
+ if ( this.attachment ) {
+ if ( this.attachment.get( 'url' ) === linkUrl ) {
+ type = 'file';
+ } else if ( this.attachment.get( 'link' ) === linkUrl ) {
+ type = 'post';
+ }
+ } else {
+ if ( this.get( 'url' ) === linkUrl ) {
+ type = 'file';
+ }
+ }
+
+ this.set( 'link', type );
+
+ },
+
+
+ updateLinkUrl: function() {
+ var link = this.get( 'link' ),
+ url;
+
+ switch( link ) {
+ case 'file':
+ if ( this.attachment ) {
+ url = this.attachment.get( 'url' );
+ } else {
+ url = this.get( 'url' );
+ }
+ this.set( 'linkUrl', url );
+ break;
+ case 'post':
+ this.set( 'linkUrl', this.attachment.get( 'link' ) );
+ break;
+ case 'none':
+ this.set( 'linkUrl', '' );
+ break;
+
+ }
+
+ },
+
+ updateSize: function() {
+ var size;
+
+ if ( ! this.attachment ) {
+ return;
+ }
+
+ size = this.attachment.get( 'sizes' )[ this.get( 'size' ) ];
+ this.set( 'url', size.url );
+ this.set( 'width', size.width );
+ this.set( 'height', size.height );
+
+ }
+
+
+ });
+
+ /**
</ins><span class="cx"> * wp.media.model.Attachments
</span><span class="cx"> *
</span><span class="cx"> * @constructor
</span><span class="lines">@@ -1170,4 +1287,4 @@
</span><span class="cx"> window.wp = null;
</span><span class="cx"> });
</span><span class="cx">
</span><del>-}(jQuery));
</del><span class="cx">\ No newline at end of file
</span><ins>+}(jQuery));
</ins></span></pre></div>
<a id="trunksrcwpincludesjsmediaviewsjs"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/js/media-views.js (27049 => 27050)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/js/media-views.js 2014-01-28 08:25:00 UTC (rev 27049)
+++ trunk/src/wp-includes/js/media-views.js 2014-01-28 21:16:42 UTC (rev 27050)
</span><span class="lines">@@ -980,7 +980,108 @@
</span><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><ins>+
+ media.controller.ImageDetails = media.controller.State.extend({
+
+ defaults: _.defaults({
+ id: 'image-details',
+ toolbar: 'image-details',
+ title: l10n.imageDetailsTitle,
+ content: 'image-details',
+ menu: 'image-details',
+ router: false,
+ attachment: false,
+ priority: 60,
+ editing: false
+ }, media.controller.Library.prototype.defaults ),
+
+ initialize: function( options ) {
+ this.image = options.image;
+ media.controller.State.prototype.initialize.apply( this, arguments );
+ }
+ });
+
</ins><span class="cx"> /**
</span><ins>+ * wp.media.controller.ReplaceImage
+ *
+ * Replace a selected single image
+ *
+ **/
+ media.controller.ReplaceImage = media.controller.Library.extend({
+ defaults: _.defaults({
+ id: 'replace-image',
+ filterable: 'uploaded',
+ multiple: false,
+ toolbar: 'replace',
+ title: l10n.replaceImageTitle,
+ priority: 60,
+ syncSelection: false
+ }, media.controller.Library.prototype.defaults ),
+
+ initialize: function( options ) {
+ var library, comparator;
+
+ this.image = options.image;
+
+ // If we haven't been provided a `library`, create a `Selection`.
+ if ( ! this.get('library') ) {
+ this.set( 'library', media.query({ type: 'image' }) );
+ }
+ /**
+ * call 'initialize' directly on the parent class
+ */
+ media.controller.Library.prototype.initialize.apply( this, arguments );
+
+ library = this.get('library');
+ comparator = library.comparator;
+
+ // Overload the library's comparator to push items that are not in
+ // the mirrored query to the front of the aggregate collection.
+ library.comparator = function( a, b ) {
+ var aInQuery = !! this.mirroring.get( a.cid ),
+ bInQuery = !! this.mirroring.get( b.cid );
+
+ if ( ! aInQuery && bInQuery ) {
+ return -1;
+ } else if ( aInQuery && ! bInQuery ) {
+ return 1;
+ } else {
+ return comparator.apply( this, arguments );
+ }
+ };
+
+ // Add all items in the selection to the library, so any featured
+ // images that are not initially loaded still appear.
+ library.observe( this.get('selection') );
+ },
+
+ activate: function() {
+ this.updateSelection();
+ /**
+ * call 'activate' directly on the parent class
+ */
+ media.controller.Library.prototype.activate.apply( this, arguments );
+ },
+
+ deactivate: function() {
+ /**
+ * call 'deactivate' directly on the parent class
+ */
+ media.controller.Library.prototype.deactivate.apply( this, arguments );
+ },
+
+ updateSelection: function() {
+ var selection = this.get('selection'),
+ attachment = this.image.attachment;
+
+ selection.reset( attachment ? [ attachment ] : [] );
+
+ }
+
+
+ });
+
+ /**
</ins><span class="cx"> * wp.media.controller.Embed
</span><span class="cx"> *
</span><span class="cx"> * @constructor
</span><span class="lines">@@ -1924,8 +2025,157 @@
</span><span class="cx"> }
</span><span class="cx"> }) );
</span><span class="cx"> }
</span><ins>+
</ins><span class="cx"> });
</span><span class="cx">
</span><ins>+ media.view.MediaFrame.ImageDetails = media.view.MediaFrame.Select.extend({
+ defaults: {
+ id: 'image',
+ url: '',
+ menu: 'image-details',
+ content: 'image-details',
+ toolbar: 'image-details',
+ type: 'link',
+ title: l10n.imageDetailsTitle,
+ priority: 120
+ },
+
+ initialize: function( options ) {
+ this.image = new media.model.PostImage( options.metadata );
+ this.options.selection = new media.model.Selection( this.image.attachment, { multiple: false } );
+ media.view.MediaFrame.Select.prototype.initialize.apply( this, arguments );
+ },
+
+ bindHandlers: function() {
+ 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( 'menu:render:image-details', this.renderMenu, this );
+ this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );
+ // override the select toolbar
+ this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this );
+ },
+
+ createStates: function() {
+ this.states.add([
+ new media.controller.ImageDetails({
+ image: this.image,
+ editable: false,
+ menu: 'image-details'
+ }),
+ new media.controller.ReplaceImage({
+ id: 'replace-image',
+ library: media.query( { type: 'image' } ),
+ image: this.image,
+ multiple: false,
+ title: l10n.imageReplaceTitle,
+ menu: 'image-details',
+ toolbar: 'replace',
+ priority: 80,
+ displaySettings: true
+ })
+ ]);
+ },
+
+ renderImageDetailsContent: function() {
+ var view = new media.view.ImageDetails({
+ controller: this,
+ model: this.state().image,
+ attachment: this.state().image.attachment
+ }).render();
+
+ this.content.set( view );
+
+ },
+
+ renderMenu: function( view ) {
+ var lastState = this.lastState(),
+ previous = lastState && lastState.id,
+ frame = this;
+
+ view.set({
+ cancel: {
+ text: l10n.imageDetailsCancel,
+ priority: 20,
+ click: function() {
+ if ( previous ) {
+ frame.setState( previous );
+ } else {
+ frame.close();
+ }
+ }
+ },
+ separateCancel: new media.View({
+ className: 'separator',
+ priority: 40
+ })
+ });
+
+ },
+
+ renderImageDetailsToolbar: function() {
+ this.toolbar.set( new media.view.Toolbar({
+ controller: this,
+ items: {
+ select: {
+ style: 'primary',
+ text: l10n.update,
+ priority: 80,
+
+ click: function() {
+ var controller = this.controller,
+ state = controller.state();
+
+ controller.close();
+
+ // not sure if we want to use wp.media.string.image which will create a shortcode or
+ // perhaps wp.html.string to at least to build the <img />
+ state.trigger( 'update', controller.image.toJSON() );
+
+ // Restore and reset the default state.
+ controller.setState( controller.options.state );
+ controller.reset();
+ }
+ }
+ }
+ }) );
+ },
+
+ renderReplaceImageToolbar: function() {
+ this.toolbar.set( new media.view.Toolbar({
+ controller: this,
+ items: {
+ replace: {
+ style: 'primary',
+ text: l10n.replace,
+ priority: 80,
+
+ click: function() {
+ var controller = this.controller,
+ state = controller.state(),
+ selection = state.get( 'selection' ),
+ attachment = selection.single();
+
+ controller.close();
+
+ controller.image.changeAttachment( attachment, state.display( attachment ) );
+
+ // not sure if we want to use wp.media.string.image which will create a shortcode or
+ // perhaps wp.html.string to at least to build the <img />
+ state.trigger( 'replace', controller.image.toJSON() );
+
+ // Restore and reset the default state.
+ controller.setState( controller.options.state );
+ controller.reset();
+ }
+ }
+ }
+ }) );
+ }
+
+ });
+
+
</ins><span class="cx"> /**
</span><span class="cx"> * wp.media.view.Modal
</span><span class="cx"> *
</span><span class="lines">@@ -4555,7 +4805,7 @@
</span><span class="cx"> attachment = this.options.attachment;
</span><span class="cx">
</span><span class="cx"> if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) {
</span><del>- $input.hide();
</del><ins>+ $input.addClass( 'hidden' );
</ins><span class="cx"> return;
</span><span class="cx"> }
</span><span class="cx">
</span><span class="lines">@@ -4571,7 +4821,7 @@
</span><span class="cx"> $input.prop( 'readonly', 'custom' !== linkTo );
</span><span class="cx"> }
</span><span class="cx">
</span><del>- $input.show();
</del><ins>+ $input.removeClass( 'hidden' );
</ins><span class="cx">
</span><span class="cx"> // If the input is visible, focus and select its contents.
</span><span class="cx"> if ( $input.is(':visible') ) {
</span><span class="lines">@@ -4932,4 +5182,50 @@
</span><span class="cx"> this.$('img').attr( 'src', this.model.get('url') );
</span><span class="cx"> }
</span><span class="cx"> });
</span><del>-}(jQuery));
</del><span class="cx">\ No newline at end of file
</span><ins>+
+ media.view.ImageDetails = media.view.Settings.AttachmentDisplay.extend({
+ className: 'image-details',
+ template: media.template('image-details'),
+
+ initialize: function() {
+ // used in AttachmentDisplay.prototype.updateLinkTo
+ this.options.attachment = this.model.attachment;
+ media.view.Settings.AttachmentDisplay.prototype.initialize.apply( this, arguments );
+ },
+
+ prepare: function() {
+ var attachment = false;
+
+ if ( this.model.attachment ) {
+ attachment = this.model.attachment.toJSON();
+ }
+ return _.defaults({
+ model: this.model.toJSON(),
+ attachment: attachment
+ }, this.options );
+ },
+
+
+ render: function() {
+ var self = this,
+ args = arguments;
+ if ( this.model.attachment && 'pending' === this.model.dfd.state() ) {
+ // should instead show a spinner when the attachment is new and then add a listener that updates on change
+ this.model.dfd.done( function() {
+ media.view.Settings.AttachmentDisplay.prototype.render.apply( self, args );
+ self.resetFocus();
+ } );
+ } else {
+ media.view.Settings.AttachmentDisplay.prototype.render.apply( this, arguments );
+ setTimeout( function() { self.resetFocus(); }, 10 );
+ }
+
+ return this;
+ },
+
+ resetFocus: function() {
+ this.$( '.caption textarea' ).focus();
+ this.$( '.embed-image-settings' ).scrollTop( 0 );
+ }
+ });
+}(jQuery));
</ins></span></pre></div>
<a id="trunksrcwpincludesjstinymcepluginswpeditimagepluginjs"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/js/tinymce/plugins/wpeditimage/plugin.js (27049 => 27050)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/js/tinymce/plugins/wpeditimage/plugin.js 2014-01-28 08:25:00 UTC (rev 27049)
+++ trunk/src/wp-includes/js/tinymce/plugins/wpeditimage/plugin.js 2014-01-28 21:16:42 UTC (rev 27050)
</span><span class="lines">@@ -101,6 +101,176 @@
</span><span class="cx"> });
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ function extractImageData( imageNode ) {
+ var classes, metadata, captionBlock, caption;
+
+ // default attributes
+ metadata = {
+ attachment_id: false,
+ url: false,
+ height: '',
+ width: '',
+ size: 'none',
+ caption: '',
+ alt: '',
+ align: 'none',
+ link: false,
+ linkUrl: ''
+ };
+
+ metadata.url = editor.dom.getAttrib( imageNode, 'src' );
+ metadata.alt = editor.dom.getAttrib( imageNode, 'alt' );
+ metadata.width = parseInt( editor.dom.getAttrib( imageNode, 'width' ), 10 );
+ metadata.height = parseInt( editor.dom.getAttrib( imageNode, 'height' ), 10 );
+
+ //TODO: probably should capture attributes on both the <img /> and the <a /> so that they can be restored when the image and/or caption are updated
+ // maybe use getAttribs()
+
+ // extract meta data from classes (candidate for turning into a method)
+ classes = imageNode.className.split( ' ' );
+ tinymce.each( classes, function( name ) {
+
+ if ( /^wp-image/.test( name ) ) {
+ metadata.attachment_id = parseInt( name.replace( 'wp-image-', '' ), 10 );
+ }
+
+ if ( /^align/.test( name ) ) {
+ metadata.align = name.replace( 'align', '' );
+ }
+
+ if ( /^size/.test( name ) ) {
+ metadata.size = name.replace( 'size-', '' );
+ }
+ } );
+
+
+ // extract caption
+ captionBlock = editor.dom.getParents( imageNode, '.wp-caption' );
+
+ if ( captionBlock.length ) {
+ captionBlock = captionBlock[0];
+
+ classes = captionBlock.className.split( ' ' );
+ tinymce.each( classes, function( name ) {
+ if ( /^align/.test( name ) ) {
+ metadata.align = name.replace( 'align', '' );
+ }
+ } );
+ caption = editor.dom.select( 'dd.wp-caption-dd', captionBlock );
+ if ( caption.length ) {
+ caption = caption[0];
+ // need to do some more thinking about this
+ metadata.caption = editor.serializer.serialize( caption )
+ .replace( /<br[^>]*>/g, '$&\n' ).replace( /^<p>/, '' ).replace( /<\/p>$/, '' );
+
+ }
+ }
+
+ // extract linkTo
+ if ( imageNode.parentNode.nodeName === 'A' ) {
+ metadata.linkUrl = editor.dom.getAttrib( imageNode.parentNode, 'href' );
+ }
+
+ return metadata;
+
+ }
+
+ function updateImage( imageNode, imageData ) {
+ var className, width, node, html, captionNode, nodeToReplace, uid;
+
+ if ( imageData.caption ) {
+
+ html = createImageAndLink( imageData, 'html' );
+
+ width = imageData.width + 10;
+ className = 'align' + imageData.align;
+
+ //TODO: shouldn't add the id attribute if it isn't an attachment
+
+ // should create a new function for genrating the caption markup
+ html = '<dl id="'+ imageData.attachment_id +'" class="wp-caption '+ className +'" style="width: '+ width +'px">' +
+ '<dt class="wp-caption-dt">'+ html + '</dt><dd class="wp-caption-dd">'+ imageData.caption +'</dd></dl>';
+
+ node = editor.dom.create( 'div', { 'class': 'mceTemp', draggable: 'true' }, html );
+ } else {
+ node = createImageAndLink( imageData, 'node' );
+ }
+
+ nodeToReplace = imageNode;
+
+ captionNode = editor.dom.getParent( imageNode, '.mceTemp' );
+
+ if ( captionNode ) {
+ nodeToReplace = captionNode;
+ } else {
+ if ( imageNode.parentNode.nodeName === 'A' ) {
+ nodeToReplace = imageNode.parentNode;
+ }
+ }
+ // uniqueId isn't super exciting, so maybe we want to use something else
+ uid = editor.dom.uniqueId( 'wp_' );
+ editor.dom.setAttrib( node, 'data-wp-replace-id', uid );
+ editor.dom.replace( node, nodeToReplace );
+
+ // find the updated node
+ node = editor.dom.select( '[data-wp-replace-id="' + uid + '"]' )[0];
+
+ editor.dom.setAttrib( node, 'data-wp-replace-id', '' );
+
+ if ( node.nodeName === 'IMG' ) {
+ editor.selection.select( node );
+ } else {
+ editor.selection.select( editor.dom.select( 'img', node )[0] );
+ }
+ editor.nodeChanged();
+
+ }
+
+ function createImageAndLink( imageData, mode ) {
+ var classes = [],
+ props;
+
+ mode = mode ? mode : 'node';
+
+
+ if ( ! imageData.caption ) {
+ classes.push( 'align' + imageData.align );
+ }
+
+ if ( imageData.attachment_id ) {
+ classes.push( 'wp-image-' + imageData.attachment_id );
+ if ( imageData.size ) {
+ classes.push( 'size-' + imageData.size );
+ }
+ }
+
+ props = {
+ src: imageData.url,
+ width: imageData.width,
+ height: imageData.height,
+ alt: imageData.alt
+ };
+
+ if ( classes.length ) {
+ props['class'] = classes.join( ' ' );
+ }
+
+ if ( imageData.linkUrl ) {
+ if ( mode === 'node' ) {
+ return editor.dom.create( 'a', { href: imageData.linkUrl }, editor.dom.createHTML( 'img', props ) );
+ } else if ( mode === 'html' ) {
+ return editor.dom.createHTML( 'a', { href: imageData.linkUrl }, editor.dom.createHTML( 'img', props ) );
+ }
+ } else {
+ if ( mode === 'node' ) {
+ return editor.dom.create( 'img', props );
+ } else if ( mode === 'html' ) {
+ return editor.dom.createHTML( 'img', props );
+ }
+
+ }
+ }
+
</ins><span class="cx"> editor.on( 'init', function() {
</span><span class="cx"> var dom = editor.dom;
</span><span class="cx">
</span><span class="lines">@@ -452,6 +622,40 @@
</span><span class="cx"> }
</span><span class="cx"> });
</span><span class="cx">
</span><ins>+ editor.on( 'mousedown', function( e ) {
+ var imageNode, frame, callback;
+ if ( e.target.nodeName === 'IMG' && editor.selection.getNode() === e.target ) {
+ // Don't trigger on right-click
+ if ( e.button !== 2 ) {
+
+ // Don't attempt to edit placeholders
+ if ( editor.dom.hasClass( e.target, 'mceItem' ) || '1' === editor.dom.getAttrib( e.target, 'data-mce-placeholder' ) ) {
+ return;
+ }
+
+ imageNode = e.target;
+
+ frame = wp.media({
+ frame: 'image',
+ state: 'image-details',
+ metadata: extractImageData( imageNode )
+ } );
+
+ callback = function( imageData ) {
+ updateImage( imageNode, imageData );
+ editor.focus();
+ };
+
+ frame.state('image-details').on( 'update', callback );
+ frame.state('replace-image').on( 'replace', callback );
+
+ frame.open();
+
+
+ }
+ }
+ } );
+
</ins><span class="cx"> editor.wpSetImgCaption = function( content ) {
</span><span class="cx"> return parseShortcode( content );
</span><span class="cx"> };
</span></span></pre></div>
<a id="trunksrcwpincludesmediatemplatephp"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/media-template.php (27049 => 27050)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/media-template.php 2014-01-28 08:25:00 UTC (rev 27049)
+++ trunk/src/wp-includes/media-template.php 2014-01-28 21:16:42 UTC (rev 27050)
</span><span class="lines">@@ -500,6 +500,117 @@
</span><span class="cx"> }
</span><span class="cx"> </style>
</span><span class="cx"> </script>
</span><ins>+
+ <script type="text/html" id="tmpl-image-details">
+ <?php // reusing .media-embed to pick up the styles for now ?>
+ <div class="media-embed">
+ <div class="embed-image-settings">
+ <div class="thumbnail">
+ <img src="{{ data.model.url }}" draggable="false" />
+ </div>
+
+ <div class="setting url">
+ <?php // might want to make the url editable if it isn't an attachment ?>
+ <input type="text" disabled="disabled" value="{{ data.model.url }}" />
+ </div>
+
+ <?php
+ /** This filter is documented in wp-admin/includes/media.php */
+ if ( ! apply_filters( 'disable_captions', '' ) ) : ?>
+ <label class="setting caption">
+ <span><?php _e('Caption'); ?></span>
+ <textarea data-setting="caption">{{ data.model.caption }}</textarea>
+ </label>
+ <?php endif; ?>
+
+ <label class="setting alt-text">
+ <span><?php _e('Alt Text'); ?></span>
+ <input type="text" data-setting="alt" value="{{ data.model.alt }}" />
+ </label>
+
+ <div class="setting align">
+ <span><?php _e('Align'); ?></span>
+ <div class="button-group button-large" data-setting="align">
+ <button class="button" value="left">
+ <?php esc_attr_e('Left'); ?>
+ </button>
+ <button class="button" value="center">
+ <?php esc_attr_e('Center'); ?>
+ </button>
+ <button class="button" value="right">
+ <?php esc_attr_e('Right'); ?>
+ </button>
+ <button class="button active" value="none">
+ <?php esc_attr_e('None'); ?>
+ </button>
+ </div>
+ </div>
+ <div class="setting link-to">
+ <span><?php _e('Link To'); ?></span>
+
+ <# if ( data.attachment ) { #>
+ <div class="button-group button-large" data-setting="link">
+ <button class="button" value="file">
+ <?php esc_attr_e('Media File'); ?>
+ </button>
+ <button class="button" value="post">
+ <?php esc_attr_e('Attachment Page'); ?>
+ </button>
+ <button class="button" value="custom">
+ <?php esc_attr_e('Custom URL'); ?>
+ </button>
+ <button class="button active" value="none">
+ <?php esc_attr_e('None'); ?>
+ </button>
+ </div>
+ <input type="text" class="link-to-custom" data-setting="linkUrl" />
+
+ <# } else { #>
+ <div class="button-group button-large" data-setting="link">
+ <button class="button" value="file">
+ <?php esc_attr_e('Image URL'); ?>
+ </button>
+ <button class="button" value="custom">
+ <?php esc_attr_e('Custom URL'); ?>
+ </button>
+ <button class="button active" value="none">
+ <?php esc_attr_e('None'); ?>
+ </button>
+ </div>
+ <input type="text" class="link-to-custom" data-setting="linkUrl" />
+
+ <# } #>
+ </div>
+
+ <# if ( data.attachment ) { #>
+ <div class="setting size">
+ <span><?php _e('Size'); ?></span>
+ <div class="button-group button-large" data-setting="size">
+ <?php
+ /** This filter is documented in wp-admin/includes/media.php */
+ $sizes = apply_filters( 'image_size_names_choose', array(
+ 'thumbnail' => __('Thumbnail'),
+ 'medium' => __('Medium'),
+ 'large' => __('Large'),
+ 'full' => __('Full Size'),
+ ) );
+
+ foreach ( $sizes as $value => $name ) : ?>
+ <#
+ var size = data.attachment.sizes['<?php echo esc_js( $value ); ?>'];
+ if ( size ) { #>
+ <button class="button" value="<?php echo esc_attr( $value ); ?>">
+ <?php echo esc_html( $name ); ?>
+ </button>
+ <# } #>
+ <?php endforeach; ?>
+ </div>
+ </div>
+ <# } #>
+ </div>
+ </div>
+ </div>
+ </script>
</ins><span class="cx"> <?php
</span><span class="cx">
</span><span class="cx"> /**
</span></span></pre></div>
<a id="trunksrcwpincludesmediaphp"></a>
<div class="modfile"><h4>Modified: trunk/src/wp-includes/media.php (27049 => 27050)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/src/wp-includes/media.php 2014-01-28 08:25:00 UTC (rev 27049)
+++ trunk/src/wp-includes/media.php 2014-01-28 21:16:42 UTC (rev 27050)
</span><span class="lines">@@ -1967,6 +1967,8 @@
</span><span class="cx"> 'search' => __( 'Search' ),
</span><span class="cx"> 'select' => __( 'Select' ),
</span><span class="cx"> 'cancel' => __( 'Cancel' ),
</span><ins>+ 'update' => __( 'Update' ),
+ 'replace' => __( 'Replace' ),
</ins><span class="cx"> /* translators: This is a would-be plural string used in the media manager.
</span><span class="cx"> If there is not a word you can use in your language to avoid issues with the
</span><span class="cx"> lack of plural support here, turn it into "selected: %d" then translate it.
</span><span class="lines">@@ -2005,6 +2007,12 @@
</span><span class="cx"> 'addToGallery' => __( 'Add to gallery' ),
</span><span class="cx"> 'addToGalleryTitle' => __( 'Add to Gallery' ),
</span><span class="cx"> 'reverseOrder' => __( 'Reverse order' ),
</span><ins>+
+
+ // Edit Image
+ 'imageDetailsTitle' => __( 'Image Details' ),
+ 'imageReplaceTitle' => __( 'Replace Image' ),
+ 'imageDetailsCancel' => __( 'Cancel Edit' )
</ins><span class="cx"> );
</span><span class="cx">
</span><span class="cx"> $settings = apply_filters( 'media_view_settings', $settings, $post );
</span></span></pre>
</div>
</div>
</body>
</html>