Make WordPress Core

Changeset 22012


Ignore:
Timestamp:
09/26/2012 02:12:54 PM (14 years ago)
Author:
koopersmith
Message:

First pass on TinyMCE attachment in-editor UI.

  • Adds in-editor UI for image attachments. Most of this UI should be able to migrate to all images in a future commit.
  • Removes the wpeditimage TinyMCE plugin from the default plugins array.
  • Add wp.media.fit, a helper method to constrain dimensions based upon a maximum width and/or height.
  • Add html attribute parsing and string generation utilities (currently stored in mce-views).
  • Calling remove on a TinyMCE views now ensures that the the parent and references are removed as well.
  • Fixes a bug where we weren't sending the full array of results to matches in wp.mce.view.

see #21390, #21812, #21813.

Location:
trunk/wp-includes
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/wp-includes/class-wp-editor.php

    r22007 r22012  
    192192                                self::$mce_locale = $mce_locale = ( '' == get_locale() ) ? 'en' : strtolower( substr(get_locale(), 0, 2) ); // only ISO 639-1
    193193                                $no_captions = (bool) apply_filters( 'disable_captions', '' );
    194                                 $plugins = array( 'inlinepopups', 'spellchecker', 'tabfocus', 'paste', 'media', 'fullscreen', 'wordpress', 'wpeditimage', 'wpgallery', 'wplink', 'wpdialogs', 'wpview' );
     194                                $plugins = array( 'inlinepopups', 'spellchecker', 'tabfocus', 'paste', 'media', 'fullscreen', 'wordpress', 'wpgallery', 'wplink', 'wpdialogs', 'wpview' );
    195195                                $first_run = true;
    196196                                $ext_plugins = '';
  • trunk/wp-includes/js/mce-view.js

    r22004 r22012  
    4040                                        options: {
    4141                                                original: match[0],
    42                                                 results:  _.toArray( arguments )
     42                                                results:  match
    4343                                        }
    4444                                };
     
    9393                // constructor and transforms it into a text representation.
    9494                add: function( id, options ) {
     95                        var parent, remove, base, properties;
     96
    9597                        // Fetch the parent view or the default options.
    96                         var parent = options.extend ? wp.mce.view.get( options.extend ) : wp.mce.view.defaults;
     98                        parent = options.extend ? wp.mce.view.get( options.extend ) : wp.mce.view.defaults;
    9799
    98100                        // Extend the `options` object with the parent's properties.
     
    100102                        options.id = id;
    101103
    102                         // If the `view` provided was an object, automatically create
    103                         // a new `Backbone.View` constructor, using the parent's `view`
    104                         // constructor as a base.
    105                         if ( ! _.isFunction( options.view ) )
    106                                 options.view = parent.view.extend( options.view );
     104                        // Create properties used to enhance the view for use in TinyMCE.
     105                        properties = {
     106                                // Ensure the wrapper element and references to the view are
     107                                // removed. Otherwise, removed views could randomly restore.
     108                                remove: function() {
     109                                        delete instances[ this.el.id ];
     110                                        this.$el.parent().remove();
     111
     112                                        // Trigger the inherited `remove` method.
     113                                        if ( remove )
     114                                                remove.apply( this, arguments );
     115
     116                                        return this;
     117                                }
     118                        };
     119
     120                        // If the `view` provided was an object, use the parent's
     121                        // `view` constructor as a base. If a `view` constructor
     122                        // was provided, treat that as the base.
     123                        if ( _.isFunction( options.view ) ) {
     124                                base = options.view;
     125                        } else {
     126                                base   = parent.view;
     127                                remove = options.view.remove;
     128                                _.defaults( properties, options.view );
     129                        }
     130
     131                        // If there's a `remove` method on the `base` view that wasn't
     132                        // created by this method, inherit it.
     133                        if ( ! remove && ! base._mceview )
     134                                remove = base.prototype.remove;
     135
     136                        // Automatically create the new `Backbone.View` constructor.
     137                        options.view = base.extend( properties, {
     138                                // Flag that the new view has been created by `wp.mce.view`.
     139                                _mceview: true
     140                        });
    107141
    108142                        views[ id ] = options;
     
    235269                                return instance && view ? view.text( instance ) : '';
    236270                        });
     271                },
     272
     273                // Link any localized strings.
     274                l10n: _.isUndefined( _wpMceViewL10n ) ? {} : _wpMceViewL10n
     275        };
     276
     277}(jQuery));
     278
     279// Default TinyMCE Views
     280// ---------------------
     281(function($){
     282        var mceview = wp.mce.view,
     283                attrs;
     284
     285        wp.html = _.extend( wp.html || {}, {
     286                // ### Parse HTML attributes.
     287                //
     288                // Converts `content` to a set of parsed HTML attributes.
     289                // Utilizes `wp.shortcode.attrs( content )`, which is a valid superset of
     290                // the HTML attribute specification. Reformats the attributes into an
     291                // object that contains the `attrs` with `key:value` mapping, and a record
     292                // of the attributes that were entered using `empty` attribute syntax (i.e.
     293                // with no value).
     294                attrs: function( content ) {
     295                        var result, attrs;
     296
     297                        // If `content` ends in a slash, strip it.
     298                        if ( '/' === content[ content.length - 1 ] )
     299                                content = content.slice( 0, -1 );
     300
     301                        result = wp.shortcode.attrs( content );
     302                        attrs  = result.named;
     303
     304                        _.each( result.numeric, function( key ) {
     305                                if ( /\s/.test( key ) )
     306                                        return;
     307
     308                                attrs[ key ] = '';
     309                        });
     310
     311                        return attrs;
     312                },
     313
     314                string: function( options ) {
     315                        var text = '<' + options.tag,
     316                                content = options.content || '';
     317
     318                        _.each( options.attrs, function( value, attr ) {
     319                                text += ' ' + attr;
     320
     321                                // Use empty attribute notation where possible.
     322                                if ( '' === value )
     323                                        return;
     324
     325                                // Convert boolean values to strings.
     326                                if ( _.isBoolean( value ) )
     327                                        value = value ? 'true' : 'false';
     328
     329                                text += '="' + value + '"';
     330                        });
     331
     332                        // Return the result if it is a self-closing tag.
     333                        if ( options.single )
     334                                return text + ' />';
     335
     336                        // Complete the opening tag.
     337                        text += '>';
     338
     339                        // If `content` is an object, recursively call this function.
     340                        text += _.isObject( content ) ? wp.html.string( content ) : content;
     341
     342                        return text + '</' + options.tag + '>';
    237343                }
    238         };
    239 
     344        });
     345
     346        mceview.add( 'attachment', {
     347                pattern: new RegExp( '(?:<a([^>]*)>)?<img([^>]*class=(?:"[^"]*|\'[^\']*)\\bwp-image-(\\d+)[^>]*)>(?:</a>)?' ),
     348
     349                text: function( instance ) {
     350                        var img     = _.clone( instance.img ),
     351                                classes = img['class'].split(/\s+/),
     352                                options;
     353
     354                        // Update `img` classes.
     355                        if ( instance.align )
     356                                classes.push( 'align' + instance.align );
     357
     358                        if ( instance.size )
     359                                classes.push( 'size-' + instance.size );
     360
     361                        classes.push( 'wp-image-' + instance.model.id );
     362
     363                        img['class'] = _.compact( classes ).join(' ');
     364
     365                        // Generate `img` tag options.
     366                        options = {
     367                                tag:    'img',
     368                                attrs:  img,
     369                                single: true
     370                        };
     371
     372                        // Generate the `a` element options, if they exist.
     373                        if ( instance.anchor ) {
     374                                options = {
     375                                        tag:     'a',
     376                                        attrs:   instance.anchor,
     377                                        content: options
     378                                };
     379                        }
     380
     381                        return wp.html.string( options );
     382                },
     383
     384                view: {
     385                        className: 'editor-attachment',
     386                        template:  media.template('editor-attachment'),
     387
     388                        events: {
     389                                'click .close': 'remove'
     390                        },
     391
     392                        initialize: function() {
     393                                var view    = this,
     394                                        results = this.options.results,
     395                                        id      = results[3],
     396                                        className;
     397
     398                                this.model = wp.media.model.Attachment.get( id );
     399
     400                                if ( results[1] )
     401                                        this.anchor = wp.html.attrs( results[1] );
     402
     403                                this.img = wp.html.attrs( results[2] );
     404                                className = this.img['class'];
     405
     406                                // Strip ID class.
     407                                className = className.replace( /(?:^|\s)wp-image-\d+/, '' );
     408
     409                                // Calculate thumbnail `size` and remove class.
     410                                className = className.replace( /(?:^|\s)size-(\S+)/, function( match, size ) {
     411                                        view.size = size;
     412                                        return '';
     413                                });
     414
     415                                // Calculate `align` and remove class.
     416                                className = className.replace( /(?:^|\s)align(left|center|right|none)(?:\s|$)/, function( match, align ) {
     417                                        view.align = align;
     418                                        return '';
     419                                });
     420
     421                                this.img['class'] = className;
     422
     423                                this.$el.addClass('spinner');
     424                                this.model.fetch().done( _.bind( this.render, this ) );
     425                        },
     426
     427                        render: function() {
     428                                var attachment = this.model.toJSON(),
     429                                        options;
     430
     431                                // If we don't have the attachment data, bail.
     432                                if ( ! attachment.url )
     433                                        return;
     434
     435                                options = {
     436                                        url: 'image' === attachment.type ? attachment.url : attachment.icon,
     437                                        uploading: attachment.uploading
     438                                };
     439
     440                                _.extend( options, wp.media.fit({
     441                                        width:    attachment.width,
     442                                        height:   attachment.height,
     443                                        maxWidth: mceview.l10n.contentWidth
     444                                }) );
     445
     446                                // Use the specified size if it exists.
     447                                if ( this.size && attachment.sizes && attachment.sizes[ this.size ] )
     448                                        _.extend( options, _.pick( attachment.sizes[ this.size ], 'url', 'width', 'height' ) );
     449
     450                                this.$el.html( this.template( options ) );
     451                        }
     452                }
     453        });
    240454}(jQuery));
  • trunk/wp-includes/js/media-models.js

    r21909 r22012  
    122122                                });
    123123                        }).promise();
     124                },
     125
     126                // Scales a set of dimensions to fit within bounding dimensions.
     127                fit: function( dimensions ) {
     128                        var width     = dimensions.width,
     129                                height    = dimensions.height,
     130                                maxWidth  = dimensions.maxWidth,
     131                                maxHeight = dimensions.maxHeight,
     132                                constraint;
     133
     134                        // Compare ratios between the two values to determine which
     135                        // max to constrain by. If a max value doesn't exist, then the
     136                        // opposite side is the constraint.
     137                        if ( ! _.isUndefined( maxWidth ) && ! _.isUndefined( maxHeight ) ) {
     138                                constraint = ( width / height > maxWidth / maxHeight ) ? 'width' : 'height';
     139                        } else if ( _.isUndefined( maxHeight ) ) {
     140                                constraint = 'width';
     141                        } else if (  _.isUndefined( maxWidth ) && height > maxHeight ) {
     142                                constraint = 'height';
     143                        }
     144
     145                        // If the value of the constrained side is larger than the max,
     146                        // then scale the values. Otherwise return the originals; they fit.
     147                        if ( 'width' === constraint && width > maxWidth ) {
     148                                return {
     149                                        width : maxWidth,
     150                                        height: maxWidth * height / width
     151                                };
     152                        } else if ( 'height' === constraint && height > maxHeight ) {
     153                                return {
     154                                        width : maxHeight * width / height,
     155                                        height: maxHeight
     156                                };
     157                        } else {
     158                                return {
     159                                        width : width,
     160                                        height: height
     161                                };
     162                        }
    124163                }
    125164        });
  • trunk/wp-includes/js/tinymce/themes/advanced/skins/wp_theme/content.css

    r22004 r22012  
    147147        display: inline-block;
    148148}
     149
     150.spinner {
     151        background: #fff url("../../../../../../../wp-admin/images/wpspin_light.gif") no-repeat center center;
     152        border: 1px solid #dfdfdf;
     153        margin-top: 10px;
     154        margin-right: 10px;
     155}
     156
     157.editor-attachment {
     158        position: relative;
     159        padding: 5px;
     160}
     161
     162.editor-attachment,
     163.editor-attachment img {
     164        min-height: 100px;
     165        min-width: 100px;
     166}
     167
     168.editor-attachment img {
     169        display: block;
     170        border: 0;
     171        padding: 0;
     172        margin: 0;
     173}
     174
     175.close {
     176        display: none;
     177        position: absolute;
     178        top: 0;
     179        right: 0;
     180        height: 26px;
     181        width: 26px;
     182        font-size: 26px;
     183        line-height: 22px;
     184        text-align: center;
     185        cursor: pointer;
     186        color: #464646;
     187        background: #fff;
     188}
     189
     190.editor-attachment:hover .close {
     191        display: block;
     192}
  • trunk/wp-includes/media.php

    r21999 r22012  
    14151415                <a class="clear-selection" href="#"><?php _e('Clear selection'); ?></a>
    14161416        </script>
     1417
     1418        <script type="text/html" id="tmpl-editor-attachment">
     1419                <% if ( url ) { %>
     1420                        <img src="<%- url %>" width="<%- width %>" height="<%- height %>" draggable="false" />
     1421                <% } %>
     1422
     1423                <% if ( uploading ) { %>
     1424                        <div class="media-progress-bar"><div></div></div>
     1425                <% } %>
     1426                <div class="close">&times;</div>
     1427                <div class="describe"></div>
     1428        </script>
    14171429        <?php
    14181430}
  • trunk/wp-includes/script-loader.php

    r22004 r22012  
    324324
    325325        $scripts->add( 'shortcode', "/wp-includes/js/shortcode$suffix.js", array( 'underscore' ), false, 1 );
    326         $scripts->add( 'mce-view', "/wp-includes/js/mce-view$suffix.js", array( 'shortcode', 'backbone', 'jquery' ), false, 1 );
     326        $scripts->add( 'mce-view', "/wp-includes/js/mce-view$suffix.js", array( 'shortcode', 'media-models' ), false, 1 );
     327        did_action( 'init' ) && $scripts->localize( 'mce-view', '_wpMceViewL10n', array(
     328                'contentWidth' => isset( $GLOBALS['content_width'] ) ? $GLOBALS['content_width'] : 800,
     329        ) );
    327330
    328331        if ( is_admin() ) {
Note: See TracChangeset for help on using the changeset viewer.