WordPress.org

Make WordPress Core

Changeset 22012


Ignore:
Timestamp:
09/26/12 14:12:54 (19 months 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.