Make WordPress Core

Ticket #24409: 24409-03.patch

File 24409-03.patch, 25.2 KB (added by gcorne, 11 years ago)
  • src/wp-includes/class-wp-editor.php

    diff --git src/wp-includes/class-wp-editor.php src/wp-includes/class-wp-editor.php
    index dd59239..fb36424 100644
    final class _WP_Editors { 
    228228                                                'paste',
    229229                                                'tabfocus',
    230230                                                'textcolor',
    231                                                 'image',
    232231                                                'fullscreen',
    233232                                                'wordpress',
    234233                                                'wpeditimage',
  • src/wp-includes/css/media-views.css

    diff --git src/wp-includes/css/media-views.css src/wp-includes/css/media-views.css
    index 7fb7ffa..78a48ea 100644
     
    8686   color: #a9a9a9;
    8787}
    8888
     89.media-frame .hidden {
     90        display: none;
     91}
     92
    8993/* Enable draggable on IE10 touch events until it's rolled into jQuery UI core */
    9094.ui-sortable,
    9195.ui-draggable {
     
    14111415}
    14121416
    14131417/**
    1414  * Embed from URL
     1418 * Embed from URL and Image Details
    14151419 */
    14161420.embed-url {
    14171421        display: block;
     
    14521456        overflow: auto;
    14531457}
    14541458
     1459.image-details .embed-image-settings {
     1460        top: 0;
     1461}
     1462
    14551463.media-embed .thumbnail {
    14561464        max-width: 100%;
    14571465        max-height: 200px;
     
    14841492        clear: both;
    14851493}
    14861494
     1495.media-embed .setting .hidden {
     1496        display: none;
     1497}
     1498
    14871499.media-embed .setting span {
    14881500        display: block;
    14891501        width: 200px;
     
    19261938        .media-frame .spinner {
    19271939                background-image: url('../images/spinner-2x.gif');
    19281940        }
    1929 }
    1930  No newline at end of file
     1941}
  • src/wp-includes/js/media-models.js

    diff --git src/wp-includes/js/media-models.js src/wp-includes/js/media-models.js
    index 2144f33..6ea947f 100644
     
    22window.wp = window.wp || {};
    33
    44(function($){
    5         var Attachment, Attachments, Query, compare, l10n, media;
     5        var Attachment, Attachments, Query, PostImage, compare, l10n, media;
    66
    77        /**
    88         * wp.media( attributes )
    window.wp = window.wp || {}; 
    3030                        frame = new MediaFrame.Select( attributes );
    3131                } else if ( 'post' === attributes.frame && MediaFrame.Post ) {
    3232                        frame = new MediaFrame.Post( attributes );
     33                } else if ( 'image' === attributes.frame && MediaFrame.ImageDetails ) {
     34                        frame = new MediaFrame.ImageDetails( attributes );
    3335                }
    3436
    3537                delete attributes.frame;
    window.wp = window.wp || {}; 
    340342        });
    341343
    342344        /**
     345         * wp.media.model.Attachment
     346         *
     347         * @constructor
     348         * @augments Backbone.Model
     349         *
     350         **/
     351        PostImage = media.model.PostImage = Backbone.Model.extend({
     352
     353                initialize: function( attributes ) {
     354                        this.attachment = false;
     355
     356                        if ( attributes.attachment_id ) {
     357                                this.attachment = media.model.Attachment.get( attributes.attachment_id );
     358                                this.dfd = this.attachment.fetch();
     359                                this.bindAttachmentListeners();
     360                        }
     361
     362                        // keep url in sync with changes to the type of link
     363                        this.on( 'change:link', this.updateLinkUrl, this );
     364                        this.on( 'change:size', this.updateSize, this );
     365
     366                        this.setLinkTypeFromUrl();
     367
     368                },
     369
     370                bindAttachmentListeners: function() {
     371                        this.listenTo( this.attachment, 'sync', this.setLinkTypeFromUrl );
     372                },
     373
     374                changeAttachment: function( attachment, props ) {
     375                        this.stopListening( this.attachment );
     376                        this.attachment = attachment;
     377                        this.bindAttachmentListeners();
     378
     379                        this.set( 'attachment_id', this.attachment.get( 'id' ) );
     380                        this.set( 'caption', this.attachment.get( 'caption' ) );
     381                        this.set( 'alt', this.attachment.get( 'alt' ) );
     382                        this.set( 'size', props.get( 'size' ) );
     383                        this.set( 'align', props.get( 'align' ) );
     384                        this.set( 'link', props.get( 'link' ) );
     385                        this.updateLinkUrl();
     386                        this.updateSize();
     387                },
     388
     389                setLinkTypeFromUrl: function() {
     390                        var linkUrl = this.get( 'linkUrl' ),
     391                                type;
     392
     393                        if ( ! linkUrl ) {
     394                                this.set( 'link', 'none' );
     395                                return;
     396                        }
     397
     398                        // default to custom if there is a linkUrl
     399                        type = 'custom';
     400
     401                        if ( this.attachment ) {
     402                                if ( this.attachment.get( 'url' ) === linkUrl ) {
     403                                        type = 'file';
     404                                } else if ( this.attachment.get( 'link' ) === linkUrl ) {
     405                                        type = 'post';
     406                                }
     407                        } else {
     408                                if ( this.get( 'url' ) === linkUrl ) {
     409                                        type = 'file';
     410                                }
     411                        }
     412
     413                        this.set( 'link', type );
     414
     415                },
     416
     417
     418                updateLinkUrl: function() {
     419                        var link = this.get( 'link' ),
     420                                url;
     421
     422                        switch( link ) {
     423                                case 'file':
     424                                        if ( this.attachment ) {
     425                                                url = this.attachment.get( 'url' );
     426                                        } else {
     427                                                url = this.get( 'url' );
     428                                        }
     429                                        this.set( 'linkUrl', url );
     430                                        break;
     431                                case 'post':
     432                                        this.set( 'linkUrl', this.attachment.get( 'link' ) );
     433                                        break;
     434                                case 'none':
     435                                        this.set( 'linkUrl', '' );
     436                                        break;
     437
     438                        }
     439
     440                },
     441
     442                updateSize: function() {
     443                        var size;
     444
     445                        if ( ! this.attachment ) {
     446                                return;
     447                        }
     448
     449                        size = this.attachment.get( 'sizes' )[ this.get( 'size' ) ];
     450                        this.set( 'url', size.url );
     451                        this.set( 'width', size.width );
     452                        this.set( 'height', size.height );
     453
     454                }
     455
     456
     457        });
     458
     459        /**
    343460         * wp.media.model.Attachments
    344461         *
    345462         * @constructor
    window.wp = window.wp || {}; 
    11701287                window.wp = null;
    11711288        });
    11721289
    1173 }(jQuery));
    1174  No newline at end of file
     1290}(jQuery));
  • src/wp-includes/js/media-views.js

    diff --git src/wp-includes/js/media-views.js src/wp-includes/js/media-views.js
    index 07b49c9..3fa9ecd 100644
     
    980980                }
    981981        });
    982982
     983
     984        media.controller.ImageDetails = media.controller.State.extend({
     985
     986                defaults: _.defaults({
     987                        id: 'image-details',
     988                        toolbar: 'image-details',
     989                        title: l10n.imageDetailsTitle,
     990                        content: 'image-details',
     991                        menu: 'image-details',
     992                        router: false,
     993                        attachment: false,
     994                        priority: 60,
     995                        editing: false
     996                }, media.controller.Library.prototype.defaults ),
     997
     998                initialize: function( options ) {
     999                        this.image = options.image;
     1000                        media.controller.State.prototype.initialize.apply( this, arguments );
     1001                }
     1002        });
     1003
     1004        /**
     1005         * wp.media.controller.ReplaceImage
     1006         *
     1007         * Replace a selected single image
     1008         *
     1009         **/
     1010        media.controller.ReplaceImage = media.controller.Library.extend({
     1011                defaults: _.defaults({
     1012                        id:         'replace-image',
     1013                        filterable: 'uploaded',
     1014                        multiple:   false,
     1015                        toolbar:    'replace',
     1016                        title:      l10n.replaceImageTitle,
     1017                        priority:   60,
     1018                        syncSelection: false
     1019                }, media.controller.Library.prototype.defaults ),
     1020
     1021                initialize: function( options ) {
     1022                        var library, comparator;
     1023
     1024                        this.image = options.image;
     1025
     1026                        // If we haven't been provided a `library`, create a `Selection`.
     1027                        if ( ! this.get('library') ) {
     1028                                this.set( 'library', media.query({ type: 'image' }) );
     1029                        }
     1030                        /**
     1031                         * call 'initialize' directly on the parent class
     1032                         */
     1033                        media.controller.Library.prototype.initialize.apply( this, arguments );
     1034
     1035                        library    = this.get('library');
     1036                        comparator = library.comparator;
     1037
     1038                        // Overload the library's comparator to push items that are not in
     1039                        // the mirrored query to the front of the aggregate collection.
     1040                        library.comparator = function( a, b ) {
     1041                                var aInQuery = !! this.mirroring.get( a.cid ),
     1042                                        bInQuery = !! this.mirroring.get( b.cid );
     1043
     1044                                if ( ! aInQuery && bInQuery ) {
     1045                                        return -1;
     1046                                } else if ( aInQuery && ! bInQuery ) {
     1047                                        return 1;
     1048                                } else {
     1049                                        return comparator.apply( this, arguments );
     1050                                }
     1051                        };
     1052
     1053                        // Add all items in the selection to the library, so any featured
     1054                        // images that are not initially loaded still appear.
     1055                        library.observe( this.get('selection') );
     1056                },
     1057
     1058                activate: function() {
     1059                        this.updateSelection();
     1060                        /**
     1061                         * call 'activate' directly on the parent class
     1062                         */
     1063                        media.controller.Library.prototype.activate.apply( this, arguments );
     1064                },
     1065
     1066                deactivate: function() {
     1067                        /**
     1068                         * call 'deactivate' directly on the parent class
     1069                         */
     1070                        media.controller.Library.prototype.deactivate.apply( this, arguments );
     1071                },
     1072
     1073                updateSelection: function() {
     1074                        var selection = this.get('selection'),
     1075                                attachment = this.image.attachment;
     1076
     1077                        selection.reset( attachment ? [ attachment ] : [] );
     1078
     1079                }
     1080
     1081
     1082        });
     1083
    9831084        /**
    9841085         * wp.media.controller.Embed
    9851086         *
     
    19242025                                }
    19252026                        }) );
    19262027                }
     2028
     2029        });
     2030
     2031        media.view.MediaFrame.ImageDetails = media.view.MediaFrame.Select.extend({
     2032                defaults: {
     2033                        id:      'image',
     2034                        url:     '',
     2035                        menu:    'image-details',
     2036                        content: 'image-details',
     2037                        toolbar: 'image-details',
     2038                        type:    'link',
     2039                        title:    l10n.imageDetailsTitle,
     2040                        priority: 120
     2041                },
     2042
     2043                initialize: function( options ) {
     2044                        this.image = new media.model.PostImage( options.metadata );
     2045                        this.options.selection = new media.model.Selection( this.image.attachment, { multiple: false } );
     2046                        media.view.MediaFrame.Select.prototype.initialize.apply( this, arguments );
     2047                },
     2048
     2049                bindHandlers: function() {
     2050                        media.view.MediaFrame.Select.prototype.bindHandlers.apply( this, arguments );
     2051                        this.on( 'menu:create:image-details', this.createMenu, this );
     2052                        this.on( 'content:render:image-details', this.renderImageDetailsContent, this );
     2053                        this.on( 'menu:render:image-details', this.renderMenu, this );
     2054                        this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );
     2055                        // override the select toolbar
     2056                        this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this );
     2057                },
     2058
     2059                createStates: function() {
     2060                        this.states.add([
     2061                                new media.controller.ImageDetails({
     2062                                        image: this.image,
     2063                                        editable: false,
     2064                                        menu: 'image-details'
     2065                                }),
     2066                                new media.controller.ReplaceImage({
     2067                                        id: 'replace-image',
     2068                                        library:   media.query( { type: 'image' } ),
     2069                                        image: this.image,
     2070                                        multiple:  false,
     2071                                        title:     l10n.imageReplaceTitle,
     2072                                        menu: 'image-details',
     2073                                        toolbar: 'replace',
     2074                                        priority:  80,
     2075                                        displaySettings: true
     2076                                })
     2077                        ]);
     2078                },
     2079
     2080                renderImageDetailsContent: function() {
     2081                        var view = new media.view.ImageDetails({
     2082                                controller: this,
     2083                                model: this.state().image,
     2084                                attachment: this.state().image.attachment
     2085                        }).render();
     2086
     2087                        this.content.set( view );
     2088
     2089                },
     2090
     2091                renderMenu: function( view ) {
     2092                        var lastState = this.lastState(),
     2093                                previous = lastState && lastState.id,
     2094                                frame = this;
     2095
     2096                        view.set({
     2097                                cancel: {
     2098                                        text:     l10n.imageDetailsCancel,
     2099                                        priority: 20,
     2100                                        click:    function() {
     2101                                                if ( previous ) {
     2102                                                        frame.setState( previous );
     2103                                                } else {
     2104                                                        frame.close();
     2105                                                }
     2106                                        }
     2107                                },
     2108                                separateCancel: new media.View({
     2109                                        className: 'separator',
     2110                                        priority: 40
     2111                                })
     2112                        });
     2113
     2114                },
     2115
     2116                renderImageDetailsToolbar: function() {
     2117                        this.toolbar.set( new media.view.Toolbar({
     2118                                controller: this,
     2119                                items: {
     2120                                        select: {
     2121                                                style:    'primary',
     2122                                                text:     l10n.update,
     2123                                                priority: 80,
     2124
     2125                                                click: function() {
     2126                                                        var controller = this.controller,
     2127                                                                state = controller.state();
     2128
     2129                                                        controller.close();
     2130
     2131                                                        // not sure if we want to use wp.media.string.image which will create a shortcode or
     2132                                                        // perhaps wp.html.string to at least to build the <img />
     2133                                                        state.trigger( 'update', controller.image.toJSON() );
     2134
     2135                                                        // Restore and reset the default state.
     2136                                                        controller.setState( controller.options.state );
     2137                                                        controller.reset();
     2138                                                }
     2139                                        }
     2140                                }
     2141                        }) );
     2142                },
     2143
     2144                renderReplaceImageToolbar: function() {
     2145                        this.toolbar.set( new media.view.Toolbar({
     2146                                controller: this,
     2147                                items: {
     2148                                        replace: {
     2149                                                style:    'primary',
     2150                                                text:     l10n.replace,
     2151                                                priority: 80,
     2152
     2153                                                click: function() {
     2154                                                        var controller = this.controller,
     2155                                                                state = controller.state(),
     2156                                                                selection = state.get( 'selection' ),
     2157                                                                attachment = selection.single();
     2158
     2159                                                        controller.close();
     2160
     2161                                                        controller.image.changeAttachment( attachment, state.display( attachment ) );
     2162
     2163                                                        // not sure if we want to use wp.media.string.image which will create a shortcode or
     2164                                                        // perhaps wp.html.string to at least to build the <img />
     2165                                                        state.trigger( 'replace', controller.image.toJSON() );
     2166
     2167                                                        // Restore and reset the default state.
     2168                                                        controller.setState( controller.options.state );
     2169                                                        controller.reset();
     2170                                                }
     2171                                        }
     2172                                }
     2173                        }) );
     2174                }
     2175
    19272176        });
    19282177
     2178
    19292179        /**
    19302180         * wp.media.view.Modal
    19312181         *
     
    45554805                                attachment = this.options.attachment;
    45564806
    45574807                        if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) {
    4558                                 $input.hide();
     4808                                $input.addClass( 'hidden' );
    45594809                                return;
    45604810                        }
    45614811
     
    45714821                                $input.prop( 'readonly', 'custom' !== linkTo );
    45724822                        }
    45734823
    4574                         $input.show();
     4824                        $input.removeClass( 'hidden' );
    45754825
    45764826                        // If the input is visible, focus and select its contents.
    45774827                        if ( $input.is(':visible') ) {
     
    49325182                        this.$('img').attr( 'src', this.model.get('url') );
    49335183                }
    49345184        });
    4935 }(jQuery));
    4936  No newline at end of file
     5185
     5186        media.view.ImageDetails = media.view.Settings.AttachmentDisplay.extend({
     5187                className: 'image-details',
     5188                template:  media.template('image-details'),
     5189
     5190                initialize: function() {
     5191                        // used in AttachmentDisplay.prototype.updateLinkTo
     5192                        this.options.attachment = this.model.attachment;
     5193                        media.view.Settings.AttachmentDisplay.prototype.initialize.apply( this, arguments );
     5194                },
     5195
     5196                prepare: function() {
     5197                        var attachment = false;
     5198
     5199                        if ( this.model.attachment ) {
     5200                                attachment = this.model.attachment.toJSON();
     5201                        }
     5202                        return _.defaults({
     5203                                model: this.model.toJSON(),
     5204                                attachment: attachment
     5205                        }, this.options );
     5206                },
     5207
     5208
     5209                render: function() {
     5210                        var self = this,
     5211                                args = arguments;
     5212                        if ( this.model.attachment && 'pending' === this.model.dfd.state() ) {
     5213                                // should instead show a spinner when the attachment is new and then add a listener that updates on change
     5214                                this.model.dfd.done( function() {
     5215                                        media.view.Settings.AttachmentDisplay.prototype.render.apply( self, args );
     5216                                        self.resetFocus();
     5217                                } );
     5218                        } else {
     5219                                media.view.Settings.AttachmentDisplay.prototype.render.apply( this, arguments );
     5220                                setTimeout( function() { self.resetFocus(); }, 10 );
     5221                        }
     5222
     5223                        return this;
     5224                },
     5225
     5226                resetFocus: function() {
     5227                        this.$( '.caption textarea' ).focus();
     5228                        this.$( '.embed-image-settings' ).scrollTop( 0 );
     5229                }
     5230        });
     5231}(jQuery));
  • src/wp-includes/js/tinymce/plugins/wpeditimage/plugin.js

    diff --git src/wp-includes/js/tinymce/plugins/wpeditimage/plugin.js src/wp-includes/js/tinymce/plugins/wpeditimage/plugin.js
    index 3f4ca10..9139239 100644
    tinymce.PluginManager.add( 'wpeditimage', function( editor ) { 
    101101                });
    102102        }
    103103
     104        function extractImageData( imageNode ) {
     105                var classes, metadata, captionBlock, caption;
     106
     107                // default attributes
     108                metadata = {
     109                        attachment_id: false,
     110                        url: false,
     111                        height: '',
     112                        width: '',
     113                        size: 'none',
     114                        caption: '',
     115                        alt: '',
     116                        align: 'none',
     117                        link: false,
     118                        linkUrl: ''
     119                };
     120
     121                metadata.url = editor.dom.getAttrib( imageNode, 'src' );
     122                metadata.alt = editor.dom.getAttrib( imageNode, 'alt' );
     123                metadata.width = parseInt( editor.dom.getAttrib( imageNode, 'width' ), 10 );
     124                metadata.height = parseInt( editor.dom.getAttrib( imageNode, 'height' ), 10 );
     125
     126                //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
     127                // maybe use getAttribs()
     128
     129                // extract meta data from classes (candidate for turning into a method)
     130                classes = imageNode.className.split( ' ' );
     131                tinymce.each( classes, function( name ) {
     132
     133                        if ( /^wp-image/.test( name ) ) {
     134                                metadata.attachment_id = parseInt( name.replace( 'wp-image-', '' ), 10 );
     135                        }
     136
     137                        if ( /^align/.test( name ) ) {
     138                                metadata.align = name.replace( 'align', '' );
     139                        }
     140
     141                        if ( /^size/.test( name ) ) {
     142                                metadata.size = name.replace( 'size-', '' );
     143                        }
     144                } );
     145
     146
     147                // extract caption
     148                captionBlock = editor.dom.getParents( imageNode, '.wp-caption' );
     149
     150                if ( captionBlock.length ) {
     151                        captionBlock = captionBlock[0];
     152
     153                        classes = captionBlock.className.split( ' ' );
     154                        tinymce.each( classes, function( name ) {
     155                                if ( /^align/.test( name ) ) {
     156                                        metadata.align = name.replace( 'align', '' );
     157                                }
     158                        } );
     159                        caption = editor.dom.select( 'dd.wp-caption-dd', captionBlock );
     160                        if ( caption.length ) {
     161                                caption = caption[0];
     162                                // need to do some more thinking about this
     163                                metadata.caption = editor.serializer.serialize( caption )
     164                                        .replace( /<br[^>]*>/g, '$&\n' ).replace( /^<p>/, '' ).replace( /<\/p>$/, '' );
     165
     166                        }
     167                }
     168
     169                // extract linkTo
     170                if ( imageNode.parentNode.nodeName === 'A' ) {
     171                        metadata.linkUrl = editor.dom.getAttrib( imageNode.parentNode, 'href' );
     172                }
     173
     174                return metadata;
     175
     176        }
     177
     178        function updateImage( imageNode, imageData ) {
     179                var className, width, node, html, captionNode, nodeToReplace, uid;
     180
     181                if ( imageData.caption ) {
     182
     183                        html = createImageAndLink( imageData, 'html' );
     184
     185                        width = imageData.width + 10;
     186                        className = 'align' + imageData.align;
     187
     188                        //TODO: shouldn't add the id attribute if it isn't an attachment
     189
     190                        // should create a new function for genrating the caption markup
     191                        html =  '<dl id="'+ imageData.attachment_id +'" class="wp-caption '+ className +'" style="width: '+ width +'px">' +
     192                                '<dt class="wp-caption-dt">'+ html + '</dt><dd class="wp-caption-dd">'+ imageData.caption +'</dd></dl>';
     193
     194                        node = editor.dom.create( 'div', { 'class': 'mceTemp', draggable: 'true' }, html );
     195                } else {
     196                        node = createImageAndLink( imageData, 'node' );
     197                }
     198
     199                nodeToReplace = imageNode;
     200
     201                captionNode = editor.dom.getParent( imageNode, '.mceTemp' );
     202
     203                if ( captionNode ) {
     204                        nodeToReplace = captionNode;
     205                } else {
     206                        if ( imageNode.parentNode.nodeName === 'A' ) {
     207                                nodeToReplace = imageNode.parentNode;
     208                        }
     209                }
     210                // uniqueId isn't super exciting, so maybe we want to use something else
     211                uid = editor.dom.uniqueId( 'wp_' );
     212                editor.dom.setAttrib( node, 'data-wp-replace-id', uid );
     213                editor.dom.replace( node, nodeToReplace );
     214
     215                // find the updated node
     216                node = editor.dom.select( '[data-wp-replace-id="' + uid + '"]' )[0];
     217
     218                editor.dom.setAttrib( node, 'data-wp-replace-id', '' );
     219
     220                if ( node.nodeName === 'IMG' ) {
     221                        editor.selection.select( node );
     222                } else {
     223                        editor.selection.select( editor.dom.select( 'img', node )[0] );
     224                }
     225                editor.nodeChanged();
     226
     227        }
     228
     229        function createImageAndLink( imageData, mode ) {
     230                var classes = [],
     231                        props;
     232
     233                mode = mode ? mode : 'node';
     234
     235
     236                if ( ! imageData.caption ) {
     237                        classes.push( 'align' + imageData.align );
     238                }
     239
     240                if ( imageData.attachment_id ) {
     241                        classes.push( 'wp-image-' + imageData.attachment_id );
     242                        if ( imageData.size ) {
     243                                classes.push( 'size-' + imageData.size );
     244                        }
     245                }
     246
     247                props = {
     248                        src: imageData.url,
     249                        width: imageData.width,
     250                        height: imageData.height,
     251                        alt: imageData.alt
     252                };
     253
     254                if ( classes.length ) {
     255                        props['class'] = classes.join( ' ' );
     256                }
     257
     258                if ( imageData.linkUrl ) {
     259                        if ( mode === 'node' ) {
     260                                return editor.dom.create( 'a', { href: imageData.linkUrl }, editor.dom.createHTML( 'img', props ) );
     261                        } else if ( mode === 'html' ) {
     262                                return editor.dom.createHTML( 'a', { href: imageData.linkUrl }, editor.dom.createHTML( 'img', props ) );
     263                        }
     264                } else {
     265                        if ( mode === 'node' ) {
     266                                return editor.dom.create( 'img', props );
     267                        } else if ( mode === 'html' ) {
     268                                return editor.dom.createHTML( 'img', props );
     269                        }
     270
     271                }
     272        }
     273
    104274        editor.on( 'init', function() {
    105275                var dom = editor.dom;
    106276
    tinymce.PluginManager.add( 'wpeditimage', function( editor ) { 
    452622                }
    453623        });
    454624
     625        editor.on( 'mousedown', function( e ) {
     626                var imageNode, frame, callback;
     627                if ( e.target.nodeName === 'IMG' && editor.selection.getNode() === e.target ) {
     628                        // Don't trigger on right-click
     629                        if ( e.button !== 2 ) {
     630
     631                                // Don't attempt to edit placeholders
     632                                if ( editor.dom.hasClass( e.target, 'mceItem' ) || '1' === editor.dom.getAttrib( e.target, 'data-mce-placeholder' ) ) {
     633                                        return;
     634                                }
     635
     636                                imageNode = e.target;
     637
     638                                frame = wp.media({
     639                                        frame: 'image',
     640                                        state: 'image-details',
     641                                        metadata: extractImageData( imageNode )
     642                                } );
     643
     644                                callback = function( imageData ) {
     645                                        updateImage( imageNode, imageData );
     646                                        editor.focus();
     647                                };
     648
     649                                frame.state('image-details').on( 'update', callback );
     650                                frame.state('replace-image').on( 'replace', callback );
     651
     652                                frame.open();
     653
     654
     655                        }
     656                }
     657        } );
     658
    455659        editor.wpSetImgCaption = function( content ) {
    456660                return parseShortcode( content );
    457661        };
  • src/wp-includes/media-template.php

    diff --git src/wp-includes/media-template.php src/wp-includes/media-template.php
    index 57d1c00..71e9c82 100644
    function wp_print_media_templates() { 
    500500                        }
    501501                </style>
    502502        </script>
     503
     504        <script type="text/html" id="tmpl-image-details">
     505                <?php // reusing .media-embed to pick up the styles for now ?>
     506                <div class="media-embed">
     507                        <div class="embed-image-settings">
     508                                <div class="thumbnail">
     509                                        <img src="{{ data.model.url }}" draggable="false" />
     510                                </div>
     511
     512                                <div class="setting url">
     513                                        <?php // might want to make the url editable if it isn't an attachment ?>
     514                                        <input type="text" disabled="disabled" value="{{ data.model.url }}" />
     515                                </div>
     516
     517                                <?php
     518                                /** This filter is documented in wp-admin/includes/media.php */
     519                                if ( ! apply_filters( 'disable_captions', '' ) ) : ?>
     520                                        <label class="setting caption">
     521                                                <span><?php _e('Caption'); ?></span>
     522                                                <textarea data-setting="caption">{{ data.model.caption }}</textarea>
     523                                        </label>
     524                                <?php endif; ?>
     525
     526                                <label class="setting alt-text">
     527                                        <span><?php _e('Alt Text'); ?></span>
     528                                        <input type="text" data-setting="alt" value="{{ data.model.alt }}" />
     529                                </label>
     530
     531                                <div class="setting align">
     532                                        <span><?php _e('Align'); ?></span>
     533                                        <div class="button-group button-large" data-setting="align">
     534                                                <button class="button" value="left">
     535                                                        <?php esc_attr_e('Left'); ?>
     536                                                </button>
     537                                                <button class="button" value="center">
     538                                                        <?php esc_attr_e('Center'); ?>
     539                                                </button>
     540                                                <button class="button" value="right">
     541                                                        <?php esc_attr_e('Right'); ?>
     542                                                </button>
     543                                                <button class="button active" value="none">
     544                                                        <?php esc_attr_e('None'); ?>
     545                                                </button>
     546                                        </div>
     547                                </div>
     548                                <div class="setting link-to">
     549                                <span><?php _e('Link To'); ?></span>
     550
     551                                <# if ( data.attachment ) { #>
     552                                        <div class="button-group button-large" data-setting="link">
     553                                                <button class="button" value="file">
     554                                                        <?php esc_attr_e('Media File'); ?>
     555                                                </button>
     556                                                <button class="button" value="post">
     557                                                        <?php esc_attr_e('Attachment Page'); ?>
     558                                                </button>
     559                                                <button class="button" value="custom">
     560                                                        <?php esc_attr_e('Custom URL'); ?>
     561                                                </button>
     562                                                <button class="button active" value="none">
     563                                                        <?php esc_attr_e('None'); ?>
     564                                                </button>
     565                                        </div>
     566                                        <input type="text" class="link-to-custom" data-setting="linkUrl" />
     567
     568                                <# } else { #>
     569                                        <div class="button-group button-large" data-setting="link">
     570                                                <button class="button" value="file">
     571                                                        <?php esc_attr_e('Image URL'); ?>
     572                                                </button>
     573                                                <button class="button" value="custom">
     574                                                        <?php esc_attr_e('Custom URL'); ?>
     575                                                </button>
     576                                                <button class="button active" value="none">
     577                                                        <?php esc_attr_e('None'); ?>
     578                                                </button>
     579                                        </div>
     580                                        <input type="text" class="link-to-custom" data-setting="linkUrl" />
     581
     582                                <# } #>
     583                                </div>
     584
     585                                <# if ( data.attachment ) { #>
     586                                        <div class="setting size">
     587                                                <span><?php _e('Size'); ?></span>
     588                                                <div class="button-group button-large" data-setting="size">
     589                                                <?php
     590                                                        /** This filter is documented in wp-admin/includes/media.php */
     591                                                        $sizes = apply_filters( 'image_size_names_choose', array(
     592                                                                'thumbnail' => __('Thumbnail'),
     593                                                                'medium'    => __('Medium'),
     594                                                                'large'     => __('Large'),
     595                                                                'full'      => __('Full Size'),
     596                                                        ) );
     597
     598                                                        foreach ( $sizes as $value => $name ) : ?>
     599                                                                <#
     600                                                                var size = data.attachment.sizes['<?php echo esc_js( $value ); ?>'];
     601                                                                if ( size ) { #>
     602                                                                        <button class="button" value="<?php echo esc_attr( $value ); ?>">
     603                                                                                <?php echo esc_html( $name ); ?>
     604                                                                                </button>
     605                                                                <# } #>
     606                                                        <?php endforeach; ?>
     607                                                </div>
     608                                        </div>
     609                                <# } #>
     610                                </div>
     611                        </div>
     612                </div>
     613        </script>
    503614        <?php
    504615
    505616        /**
  • src/wp-includes/media.php

    diff --git src/wp-includes/media.php src/wp-includes/media.php
    index 4ce006e..e50fa60 100644
    function wp_enqueue_media( $args = array() ) { 
    19671967                'search'      => __( 'Search' ),
    19681968                'select'      => __( 'Select' ),
    19691969                'cancel'      => __( 'Cancel' ),
     1970                'update'      => __( 'Update' ),
     1971                'replace'     => __( 'Replace' ),
    19701972                /* translators: This is a would-be plural string used in the media manager.
    19711973                   If there is not a word you can use in your language to avoid issues with the
    19721974                   lack of plural support here, turn it into "selected: %d" then translate it.
    function wp_enqueue_media( $args = array() ) { 
    20052007                'addToGallery'       => __( 'Add to gallery' ),
    20062008                'addToGalleryTitle'  => __( 'Add to Gallery' ),
    20072009                'reverseOrder'       => __( 'Reverse order' ),
     2010
     2011
     2012                // Edit Image
     2013                'imageDetailsTitle'     => __( 'Image Details' ),
     2014                'imageReplaceTitle'     => __( 'Replace Image' ),
     2015                'imageDetailsCancel'     => __( 'Cancel Edit' )
    20082016        );
    20092017
    20102018        $settings = apply_filters( 'media_view_settings', $settings, $post );