Make WordPress Core

Ticket #22124: mce-view.js.diff

File mce-view.js.diff, 34.1 KB (added by timbeks, 13 years ago)

Fix for the upload of an image with option Full

  • wp-includes/js/mce-view.js

     
    1 // Ensure the global `wp` object exists.
    2 window.wp = window.wp || {};
    3 
    4 // HTML utility functions
    5 // ----------------------
    6 (function(){
    7         wp.html = _.extend( wp.html || {}, {
    8                 // ### Parse HTML attributes.
    9                 //
    10                 // Converts `content` to a set of parsed HTML attributes.
    11                 // Utilizes `wp.shortcode.attrs( content )`, which is a valid superset of
    12                 // the HTML attribute specification. Reformats the attributes into an
    13                 // object that contains the `attrs` with `key:value` mapping, and a record
    14                 // of the attributes that were entered using `empty` attribute syntax (i.e.
    15                 // with no value).
    16                 attrs: function( content ) {
    17                         var result, attrs;
    18 
    19                         // If `content` ends in a slash, strip it.
    20                         if ( '/' === content[ content.length - 1 ] )
    21                                 content = content.slice( 0, -1 );
    22 
    23                         result = wp.shortcode.attrs( content );
    24                         attrs  = result.named;
    25 
    26                         _.each( result.numeric, function( key ) {
    27                                 if ( /\s/.test( key ) )
    28                                         return;
    29 
    30                                 attrs[ key ] = '';
    31                         });
    32 
    33                         return attrs;
    34                 },
    35 
    36                 // ### Convert an HTML-representation of an object to a string.
    37                 string: function( options ) {
    38                         var text = '<' + options.tag,
    39                                 content = options.content || '';
    40 
    41                         _.each( options.attrs, function( value, attr ) {
    42                                 text += ' ' + attr;
    43 
    44                                 // Use empty attribute notation where possible.
    45                                 if ( '' === value )
    46                                         return;
    47 
    48                                 // Convert boolean values to strings.
    49                                 if ( _.isBoolean( value ) )
    50                                         value = value ? 'true' : 'false';
    51 
    52                                 text += '="' + value + '"';
    53                         });
    54 
    55                         // Return the result if it is a self-closing tag.
    56                         if ( options.single )
    57                                 return text + ' />';
    58 
    59                         // Complete the opening tag.
    60                         text += '>';
    61 
    62                         // If `content` is an object, recursively call this function.
    63                         text += _.isObject( content ) ? wp.html.string( content ) : content;
    64 
    65                         return text + '</' + options.tag + '>';
    66                 }
    67         });
    68 }());
    69 
    70 (function($){
    71         var views = {},
    72                 instances = {};
    73 
    74         // Create the `wp.mce` object if necessary.
    75         wp.mce = wp.mce || {};
    76 
    77         // wp.mce.view
    78         // -----------
    79         // A set of utilities that simplifies adding custom UI within a TinyMCE editor.
    80         // At its core, it serves as a series of converters, transforming text to a
    81         // custom UI, and back again.
    82         wp.mce.view = {
    83                 // ### defaults
    84                 defaults: {
    85                         // The default properties used for objects with the `pattern` key in
    86                         // `wp.mce.view.add()`.
    87                         pattern: {
    88                                 view: Backbone.View,
    89                                 text: function( instance ) {
    90                                         return instance.options.original;
    91                                 },
    92 
    93                                 toView: function( content ) {
    94                                         if ( ! this.pattern )
    95                                                 return;
    96 
    97                                         this.pattern.lastIndex = 0;
    98                                         var match = this.pattern.exec( content );
    99 
    100                                         if ( ! match )
    101                                                 return;
    102 
    103                                         return {
    104                                                 index:   match.index,
    105                                                 content: match[0],
    106                                                 options: {
    107                                                         original: match[0],
    108                                                         results:  match
    109                                                 }
    110                                         };
    111                                 }
    112                         },
    113 
    114                         // The default properties used for objects with the `shortcode` key in
    115                         // `wp.mce.view.add()`.
    116                         shortcode: {
    117                                 view: Backbone.View,
    118                                 text: function( instance ) {
    119                                         return instance.options.shortcode.string();
    120                                 },
    121 
    122                                 toView: function( content ) {
    123                                         var match = wp.shortcode.next( this.shortcode, content );
    124 
    125                                         if ( ! match )
    126                                                 return;
    127 
    128                                         return {
    129                                                 index:   match.index,
    130                                                 content: match.content,
    131                                                 options: {
    132                                                         shortcode: match.shortcode
    133                                                 }
    134                                         };
    135                                 }
    136                         }
    137                 },
    138 
    139                 // ### add( id, options )
    140                 // Registers a new TinyMCE view.
    141                 //
    142                 // Accepts a unique `id` and an `options` object.
    143                 //
    144                 // `options` accepts the following properties:
    145                 //
    146                 // * `pattern` is the regular expression used to scan the content and
    147                 // detect matching views.
    148                 //
    149                 // * `view` is a `Backbone.View` constructor. If a plain object is
    150                 // provided, it will automatically extend the parent constructor
    151                 // (usually `Backbone.View`). Views are instantiated when the `pattern`
    152                 // is successfully matched. The instance's `options` object is provided
    153                 // with the `original` matched value, the match `results` including
    154                 // capture groups, and the `viewType`, which is the constructor's `id`.
    155                 //
    156                 // * `extend` an existing view by passing in its `id`. The current
    157                 // view will inherit all properties from the parent view, and if
    158                 // `view` is set to a plain object, it will extend the parent `view`
    159                 // constructor.
    160                 //
    161                 // * `text` is a method that accepts an instance of the `view`
    162                 // constructor and transforms it into a text representation.
    163                 add: function( id, options ) {
    164                         var parent, remove, base, properties;
    165 
    166                         // Fetch the parent view or the default options.
    167                         if ( options.extend )
    168                                 parent = wp.mce.view.get( options.extend );
    169                         else if ( options.shortcode )
    170                                 parent = wp.mce.view.defaults.shortcode;
    171                         else
    172                                 parent = wp.mce.view.defaults.pattern;
    173 
    174                         // Extend the `options` object with the parent's properties.
    175                         _.defaults( options, parent );
    176                         options.id = id;
    177 
    178                         // Create properties used to enhance the view for use in TinyMCE.
    179                         properties = {
    180                                 // Ensure the wrapper element and references to the view are
    181                                 // removed. Otherwise, removed views could randomly restore.
    182                                 remove: function() {
    183                                         delete instances[ this.el.id ];
    184                                         this.$el.parent().remove();
    185 
    186                                         // Trigger the inherited `remove` method.
    187                                         if ( remove )
    188                                                 remove.apply( this, arguments );
    189 
    190                                         return this;
    191                                 }
    192                         };
    193 
    194                         // If the `view` provided was an object, use the parent's
    195                         // `view` constructor as a base. If a `view` constructor
    196                         // was provided, treat that as the base.
    197                         if ( _.isFunction( options.view ) ) {
    198                                 base = options.view;
    199                         } else {
    200                                 base   = parent.view;
    201                                 remove = options.view.remove;
    202                                 _.defaults( properties, options.view );
    203                         }
    204 
    205                         // If there's a `remove` method on the `base` view that wasn't
    206                         // created by this method, inherit it.
    207                         if ( ! remove && ! base._mceview )
    208                                 remove = base.prototype.remove;
    209 
    210                         // Automatically create the new `Backbone.View` constructor.
    211                         options.view = base.extend( properties, {
    212                                 // Flag that the new view has been created by `wp.mce.view`.
    213                                 _mceview: true
    214                         });
    215 
    216                         views[ id ] = options;
    217                 },
    218 
    219                 // ### get( id )
    220                 // Returns a TinyMCE view options object.
    221                 get: function( id ) {
    222                         return views[ id ];
    223                 },
    224 
    225                 // ### remove( id )
    226                 // Unregisters a TinyMCE view.
    227                 remove: function( id ) {
    228                         delete views[ id ];
    229                 },
    230 
    231                 // ### toViews( content )
    232                 // Scans a `content` string for each view's pattern, replacing any
    233                 // matches with wrapper elements, and creates a new view instance for
    234                 // every match.
    235                 //
    236                 // To render the views, call `wp.mce.view.render( scope )`.
    237                 toViews: function( content ) {
    238                         var pieces = [ { content: content } ],
    239                                 current;
    240 
    241                         _.each( views, function( view, viewType ) {
    242                                 current = pieces.slice();
    243                                 pieces  = [];
    244 
    245                                 _.each( current, function( piece ) {
    246                                         var remaining = piece.content,
    247                                                 result;
    248 
    249                                         // Ignore processed pieces, but retain their location.
    250                                         if ( piece.processed ) {
    251                                                 pieces.push( piece );
    252                                                 return;
    253                                         }
    254 
    255                                         // Iterate through the string progressively matching views
    256                                         // and slicing the string as we go.
    257                                         while ( remaining && (result = view.toView( remaining )) ) {
    258                                                 // Any text before the match becomes an unprocessed piece.
    259                                                 if ( result.index )
    260                                                         pieces.push({ content: remaining.substring( 0, result.index ) });
    261 
    262                                                 // Add the processed piece for the match.
    263                                                 pieces.push({
    264                                                         content:   wp.mce.view.toView( viewType, result.options ),
    265                                                         processed: true
    266                                                 });
    267 
    268                                                 // Update the remaining content.
    269                                                 remaining = remaining.slice( result.index + result.content.length );
    270                                         }
    271 
    272                                         // There are no additional matches. If any content remains,
    273                                         // add it as an unprocessed piece.
    274                                         if ( remaining )
    275                                                 pieces.push({ content: remaining });
    276                                 });
    277                         });
    278 
    279                         return _.pluck( pieces, 'content' ).join('');
    280                 },
    281 
    282                 toView: function( viewType, options ) {
    283                         var view = wp.mce.view.get( viewType ),
    284                                 instance, id, tag;
    285 
    286                         if ( ! view )
    287                                 return '';
    288 
    289                         // Create a new view instance.
    290                         instance = new view.view( _.extend( options || {}, {
    291                                 viewType: viewType
    292                         }) );
    293 
    294                         // Use the view's `id` if it already exists. Otherwise,
    295                         // create a new `id`.
    296                         id = instance.el.id = instance.el.id || _.uniqueId('__wpmce-');
    297                         instances[ id ] = instance;
    298 
    299                         // If the view is a span, wrap it in a span.
    300                         tag = 'span' === instance.tagName ? 'span' : 'div';
    301 
    302                         return '<' + tag + ' class="wp-view-wrap" data-wp-view="' + id + '" contenteditable="false"></' + tag + '>';
    303                 },
    304 
    305                 // ### render( scope )
    306                 // Renders any view instances inside a DOM node `scope`.
    307                 //
    308                 // View instances are detected by the presence of wrapper elements.
    309                 // To generate wrapper elements, pass your content through
    310                 // `wp.mce.view.toViews( content )`.
    311                 render: function( scope ) {
    312                         $( '.wp-view-wrap', scope ).each( function() {
    313                                 var wrapper = $(this),
    314                                         id = wrapper.data('wp-view'),
    315                                         view = instances[ id ];
    316 
    317                                 if ( ! view )
    318                                         return;
    319 
    320                                 // Render the view.
    321                                 view.render();
    322                                 // Detach the view element to ensure events are not unbound.
    323                                 view.$el.detach();
    324 
    325                                 // Empty the wrapper, attach the view element to the wrapper,
    326                                 // and add an ending marker to the wrapper to help regexes
    327                                 // scan the HTML string.
    328                                 wrapper.empty().append( view.el ).append('<span data-wp-view-end></span>');
    329                         });
    330                 },
    331 
    332                 // ### toText( content )
    333                 // Scans an HTML `content` string and replaces any view instances with
    334                 // their respective text representations.
    335                 toText: function( content ) {
    336                         return content.replace( /<(?:div|span)[^>]+data-wp-view="([^"]+)"[^>]*>.*?<span data-wp-view-end[^>]*><\/span><\/(?:div|span)>/g, function( match, id ) {
    337                                 var instance = instances[ id ],
    338                                         view;
    339 
    340                                 if ( instance )
    341                                         view = wp.mce.view.get( instance.options.viewType );
    342 
    343                                 return instance && view ? view.text( instance ) : '';
    344                         });
    345                 },
    346 
    347                 // ### Remove internal TinyMCE attributes.
    348                 removeInternalAttrs: function( attrs ) {
    349                         var result = {};
    350                         _.each( attrs, function( value, attr ) {
    351                                 if ( -1 === attr.indexOf('data-mce') )
    352                                         result[ attr ] = value;
    353                         });
    354                         return result;
    355                 },
    356 
    357                 // ### Parse an attribute string and removes internal TinyMCE attributes.
    358                 attrs: function( content ) {
    359                         return wp.mce.view.removeInternalAttrs( wp.html.attrs( content ) );
    360                 },
    361 
    362                 // Link any localized strings.
    363                 l10n: _.isUndefined( _wpMceViewL10n ) ? {} : _wpMceViewL10n
    364         };
    365 
    366 }(jQuery));
    367 
    368 // Default TinyMCE Views
    369 // ---------------------
    370 (function($){
    371         var mceview = wp.mce.view;
    372 
    373         wp.media.string = {};
    374         wp.media.string.image = function( attachment, props ) {
    375                 var classes, img, options, size;
    376 
    377                 attachment = attachment.toJSON();
    378 
    379                 props = _.defaults( props || {}, {
    380                         img:   {},
    381                         align: getUserSetting( 'align', 'none' ),
    382                         size:  getUserSetting( 'imgsize', 'medium' ),
    383                         link:  getUserSetting( 'urlbutton', 'post' )
    384                 });
    385 
    386                 img     = _.clone( props.img );
    387                 classes = img['class'] ? img['class'].split(/\s+/) : [];
    388                 size    = attachment.sizes ? attachment.sizes[ props.size ] : {};
    389 
    390                 if ( ! size )
    391                         delete props.size;
    392 
    393                 img.width  = size.width  || attachment.width;
    394                 img.height = size.height || attachment.height;
    395                 img.src    = size.url    || attachment.url;
    396 
    397                 // Update `img` classes.
    398                 if ( props.align )
    399                         classes.push( 'align' + props.align );
    400 
    401                 if ( props.size )
    402                         classes.push( 'size-' + props.size );
    403 
    404                 classes.push( 'wp-image-' + attachment.id );
    405 
    406                 img['class'] = _.compact( classes ).join(' ');
    407 
    408                 // Generate `img` tag options.
    409                 options = {
    410                         tag:    'img',
    411                         attrs:  img,
    412                         single: true
    413                 };
    414 
    415                 // Generate the `a` element options, if they exist.
    416                 if ( props.anchor ) {
    417                         options = {
    418                                 tag:     'a',
    419                                 attrs:   props.anchor,
    420                                 content: options
    421                         };
    422                 }
    423 
    424                 return wp.html.string( options );
    425         };
    426 
    427         mceview.add( 'attachment', {
    428                 pattern: new RegExp( '(?:<a([^>]*)>)?<img([^>]*class=(?:"[^"]*|\'[^\']*)\\bwp-image-(\\d+)[^>]*)>(?:</a>)?' ),
    429 
    430                 text: function( instance ) {
    431                         var props = _.pick( instance, 'align', 'size', 'link', 'img', 'anchor' );
    432                         return wp.media.string.image( instance.model, props );
    433                 },
    434 
    435                 view: {
    436                         className: 'editor-attachment',
    437                         template:  media.template('editor-attachment'),
    438 
    439                         events: {
    440                                 'click .close': 'remove'
    441                         },
    442 
    443                         initialize: function() {
    444                                 var view    = this,
    445                                         results = this.options.results,
    446                                         id      = results[3],
    447                                         className;
    448 
    449                                 this.model = wp.media.model.Attachment.get( id );
    450 
    451                                 if ( results[1] )
    452                                         this.anchor = mceview.attrs( results[1] );
    453 
    454                                 this.img  = mceview.attrs( results[2] );
    455                                 className = this.img['class'];
    456 
    457                                 // Strip ID class.
    458                                 className = className.replace( /(?:^|\s)wp-image-\d+/, '' );
    459 
    460                                 // Calculate thumbnail `size` and remove class.
    461                                 className = className.replace( /(?:^|\s)size-(\S+)/, function( match, size ) {
    462                                         view.size = size;
    463                                         return '';
    464                                 });
    465 
    466                                 // Calculate `align` and remove class.
    467                                 className = className.replace( /(?:^|\s)align(left|center|right|none)(?:\s|$)/, function( match, align ) {
    468                                         view.align = align;
    469                                         return '';
    470                                 });
    471 
    472                                 this.img['class'] = className;
    473 
    474                                 this.$el.addClass('spinner');
    475                                 this.model.fetch().done( _.bind( this.render, this ) );
    476                         },
    477 
    478                         render: function() {
    479                                 var attachment = this.model.toJSON(),
    480                                         options;
    481 
    482                                 // If we don't have the attachment data, bail.
    483                                 if ( ! attachment.url )
    484                                         return;
    485 
    486                                 options = {
    487                                         url: 'image' === attachment.type ? attachment.url : attachment.icon,
    488                                         uploading: attachment.uploading
    489                                 };
    490 
    491                                 _.extend( options, wp.media.fit({
    492                                         width:    attachment.width,
    493                                         height:   attachment.height,
    494                                         maxWidth: mceview.l10n.contentWidth
    495                                 }) );
    496 
    497                                 // Use the specified size if it exists.
    498                                 if ( this.size && attachment.sizes && attachment.sizes[ this.size ] )
    499                                         _.extend( options, _.pick( attachment.sizes[ this.size ], 'url', 'width', 'height' ) );
    500 
    501                                 this.$el.html( this.template( options ) );
    502                         }
    503                 }
    504         });
    505 
    506         mceview.add( 'gallery', {
    507                 shortcode: 'gallery',
    508 
    509                 gallery: (function() {
    510                         var galleries = {};
    511 
    512                         return {
    513                                 attachments: function( shortcode, parent ) {
    514                                         var shortcodeString = shortcode.string(),
    515                                                 result = galleries[ shortcodeString ],
    516                                                 attrs, args;
    517 
    518                                         delete galleries[ shortcodeString ];
    519 
    520                                         if ( result )
    521                                                 return result;
    522 
    523                                         attrs = shortcode.attrs.named;
    524                                         args  = _.pick( attrs, 'orderby', 'order' );
    525 
    526                                         args.type    = 'image';
    527                                         args.perPage = -1;
    528 
    529                                         // Map the `ids` param to the correct query args.
    530                                         if ( attrs.ids ) {
    531                                                 args.post__in = attrs.ids.split(',');
    532                                                 args.orderby  = 'post__in';
    533                                         } else if ( attrs.include ) {
    534                                                 args.post__in = attrs.include.split(',');
    535                                         }
    536 
    537                                         if ( attrs.exclude )
    538                                                 args.post__not_in = attrs.exclude.split(',');
    539 
    540                                         if ( ! args.post__in )
    541                                                 args.parent = attrs.id || parent;
    542 
    543                                         return media.query( args );
    544                                 },
    545 
    546                                 shortcode: function( attachments ) {
    547                                         var attrs = _.pick( attachments.props.toJSON(), 'include', 'exclude', 'orderby', 'order' ),
    548                                                 shortcode;
    549 
    550                                         attrs.ids = attachments.pluck('id');
    551 
    552                                         shortcode = new wp.shortcode({
    553                                                 tag:    'gallery',
    554                                                 attrs:  attrs,
    555                                                 type:   'single'
    556                                         });
    557 
    558                                         galleries[ shortcode.string() ] = attachments;
    559                                         return shortcode;
    560                                 }
    561                         };
    562                 }()),
    563 
    564                 view: {
    565                         className: 'editor-gallery',
    566                         template:  media.template('editor-gallery'),
    567 
    568                         // The fallback post ID to use as a parent for galleries that don't
    569                         // specify the `ids` or `include` parameters.
    570                         //
    571                         // Uses the hidden input on the edit posts page by default.
    572                         parent: $('#post_ID').val(),
    573 
    574                         events: {
    575                                 'click .close': 'remove'
    576                         },
    577 
    578                         initialize: function() {
    579                                 var     view      = mceview.get('gallery'),
    580                                         shortcode = this.options.shortcode;
    581 
    582                                 this.attachments = view.gallery.attachments( shortcode, this.parent );
    583                                 this.attachments.more().done( _.bind( this.render, this ) );
    584                         },
    585 
    586                         render: function() {
    587                                 var options, thumbnail, size;
    588 
    589                                 if ( ! this.attachments.length )
    590                                         return;
    591 
    592                                 thumbnail = this.attachments.first().toJSON();
    593                                 size = thumbnail.sizes && thumbnail.sizes.thumbnail ? thumbnail.sizes.thumbnail : thumbnail;
    594 
    595                                 options = {
    596                                         url:         size.url,
    597                                         orientation: size.orientation,
    598                                         count:       this.attachments.length
    599                                 };
    600 
    601                                 this.$el.html( this.template( options ) );
    602                         }
    603                 }
    604         });
     1// Ensure the global `wp` object exists.
     2window.wp = window.wp || {};
     3
     4// HTML utility functions
     5// ----------------------
     6(function(){
     7        wp.html = _.extend( wp.html || {}, {
     8                // ### Parse HTML attributes.
     9                //
     10                // Converts `content` to a set of parsed HTML attributes.
     11                // Utilizes `wp.shortcode.attrs( content )`, which is a valid superset of
     12                // the HTML attribute specification. Reformats the attributes into an
     13                // object that contains the `attrs` with `key:value` mapping, and a record
     14                // of the attributes that were entered using `empty` attribute syntax (i.e.
     15                // with no value).
     16                attrs: function( content ) {
     17                        var result, attrs;
     18
     19                        // If `content` ends in a slash, strip it.
     20                        if ( '/' === content[ content.length - 1 ] )
     21                                content = content.slice( 0, -1 );
     22
     23                        result = wp.shortcode.attrs( content );
     24                        attrs  = result.named;
     25
     26                        _.each( result.numeric, function( key ) {
     27                                if ( /\s/.test( key ) )
     28                                        return;
     29
     30                                attrs[ key ] = '';
     31                        });
     32
     33                        return attrs;
     34                },
     35
     36                // ### Convert an HTML-representation of an object to a string.
     37                string: function( options ) {
     38                        var text = '<' + options.tag,
     39                                content = options.content || '';
     40
     41                        _.each( options.attrs, function( value, attr ) {
     42                                text += ' ' + attr;
     43
     44                                // Use empty attribute notation where possible.
     45                                if ( '' === value )
     46                                        return;
     47
     48                                // Convert boolean values to strings.
     49                                if ( _.isBoolean( value ) )
     50                                        value = value ? 'true' : 'false';
     51
     52                                text += '="' + value + '"';
     53                        });
     54
     55                        // Return the result if it is a self-closing tag.
     56                        if ( options.single )
     57                                return text + ' />';
     58
     59                        // Complete the opening tag.
     60                        text += '>';
     61
     62                        // If `content` is an object, recursively call this function.
     63                        text += _.isObject( content ) ? wp.html.string( content ) : content;
     64
     65                        return text + '</' + options.tag + '>';
     66                }
     67        });
     68}());
     69
     70(function($){
     71        var views = {},
     72                instances = {};
     73
     74        // Create the `wp.mce` object if necessary.
     75        wp.mce = wp.mce || {};
     76
     77        // wp.mce.view
     78        // -----------
     79        // A set of utilities that simplifies adding custom UI within a TinyMCE editor.
     80        // At its core, it serves as a series of converters, transforming text to a
     81        // custom UI, and back again.
     82        wp.mce.view = {
     83                // ### defaults
     84                defaults: {
     85                        // The default properties used for objects with the `pattern` key in
     86                        // `wp.mce.view.add()`.
     87                        pattern: {
     88                                view: Backbone.View,
     89                                text: function( instance ) {
     90                                        return instance.options.original;
     91                                },
     92
     93                                toView: function( content ) {
     94                                        if ( ! this.pattern )
     95                                                return;
     96
     97                                        this.pattern.lastIndex = 0;
     98                                        var match = this.pattern.exec( content );
     99
     100                                        if ( ! match )
     101                                                return;
     102
     103                                        return {
     104                                                index:   match.index,
     105                                                content: match[0],
     106                                                options: {
     107                                                        original: match[0],
     108                                                        results:  match
     109                                                }
     110                                        };
     111                                }
     112                        },
     113
     114                        // The default properties used for objects with the `shortcode` key in
     115                        // `wp.mce.view.add()`.
     116                        shortcode: {
     117                                view: Backbone.View,
     118                                text: function( instance ) {
     119                                        return instance.options.shortcode.string();
     120                                },
     121
     122                                toView: function( content ) {
     123                                        var match = wp.shortcode.next( this.shortcode, content );
     124
     125                                        if ( ! match )
     126                                                return;
     127
     128                                        return {
     129                                                index:   match.index,
     130                                                content: match.content,
     131                                                options: {
     132                                                        shortcode: match.shortcode
     133                                                }
     134                                        };
     135                                }
     136                        }
     137                },
     138
     139                // ### add( id, options )
     140                // Registers a new TinyMCE view.
     141                //
     142                // Accepts a unique `id` and an `options` object.
     143                //
     144                // `options` accepts the following properties:
     145                //
     146                // * `pattern` is the regular expression used to scan the content and
     147                // detect matching views.
     148                //
     149                // * `view` is a `Backbone.View` constructor. If a plain object is
     150                // provided, it will automatically extend the parent constructor
     151                // (usually `Backbone.View`). Views are instantiated when the `pattern`
     152                // is successfully matched. The instance's `options` object is provided
     153                // with the `original` matched value, the match `results` including
     154                // capture groups, and the `viewType`, which is the constructor's `id`.
     155                //
     156                // * `extend` an existing view by passing in its `id`. The current
     157                // view will inherit all properties from the parent view, and if
     158                // `view` is set to a plain object, it will extend the parent `view`
     159                // constructor.
     160                //
     161                // * `text` is a method that accepts an instance of the `view`
     162                // constructor and transforms it into a text representation.
     163                add: function( id, options ) {
     164                        var parent, remove, base, properties;
     165
     166                        // Fetch the parent view or the default options.
     167                        if ( options.extend )
     168                                parent = wp.mce.view.get( options.extend );
     169                        else if ( options.shortcode )
     170                                parent = wp.mce.view.defaults.shortcode;
     171                        else
     172                                parent = wp.mce.view.defaults.pattern;
     173
     174                        // Extend the `options` object with the parent's properties.
     175                        _.defaults( options, parent );
     176                        options.id = id;
     177
     178                        // Create properties used to enhance the view for use in TinyMCE.
     179                        properties = {
     180                                // Ensure the wrapper element and references to the view are
     181                                // removed. Otherwise, removed views could randomly restore.
     182                                remove: function() {
     183                                        delete instances[ this.el.id ];
     184                                        this.$el.parent().remove();
     185
     186                                        // Trigger the inherited `remove` method.
     187                                        if ( remove )
     188                                                remove.apply( this, arguments );
     189
     190                                        return this;
     191                                }
     192                        };
     193
     194                        // If the `view` provided was an object, use the parent's
     195                        // `view` constructor as a base. If a `view` constructor
     196                        // was provided, treat that as the base.
     197                        if ( _.isFunction( options.view ) ) {
     198                                base = options.view;
     199                        } else {
     200                                base   = parent.view;
     201                                remove = options.view.remove;
     202                                _.defaults( properties, options.view );
     203                        }
     204
     205                        // If there's a `remove` method on the `base` view that wasn't
     206                        // created by this method, inherit it.
     207                        if ( ! remove && ! base._mceview )
     208                                remove = base.prototype.remove;
     209
     210                        // Automatically create the new `Backbone.View` constructor.
     211                        options.view = base.extend( properties, {
     212                                // Flag that the new view has been created by `wp.mce.view`.
     213                                _mceview: true
     214                        });
     215
     216                        views[ id ] = options;
     217                },
     218
     219                // ### get( id )
     220                // Returns a TinyMCE view options object.
     221                get: function( id ) {
     222                        return views[ id ];
     223                },
     224
     225                // ### remove( id )
     226                // Unregisters a TinyMCE view.
     227                remove: function( id ) {
     228                        delete views[ id ];
     229                },
     230
     231                // ### toViews( content )
     232                // Scans a `content` string for each view's pattern, replacing any
     233                // matches with wrapper elements, and creates a new view instance for
     234                // every match.
     235                //
     236                // To render the views, call `wp.mce.view.render( scope )`.
     237                toViews: function( content ) {
     238                        var pieces = [ { content: content } ],
     239                                current;
     240
     241                        _.each( views, function( view, viewType ) {
     242                                current = pieces.slice();
     243                                pieces  = [];
     244
     245                                _.each( current, function( piece ) {
     246                                        var remaining = piece.content,
     247                                                result;
     248
     249                                        // Ignore processed pieces, but retain their location.
     250                                        if ( piece.processed ) {
     251                                                pieces.push( piece );
     252                                                return;
     253                                        }
     254
     255                                        // Iterate through the string progressively matching views
     256                                        // and slicing the string as we go.
     257                                        while ( remaining && (result = view.toView( remaining )) ) {
     258                                                // Any text before the match becomes an unprocessed piece.
     259                                                if ( result.index )
     260                                                        pieces.push({ content: remaining.substring( 0, result.index ) });
     261
     262                                                // Add the processed piece for the match.
     263                                                pieces.push({
     264                                                        content:   wp.mce.view.toView( viewType, result.options ),
     265                                                        processed: true
     266                                                });
     267
     268                                                // Update the remaining content.
     269                                                remaining = remaining.slice( result.index + result.content.length );
     270                                        }
     271
     272                                        // There are no additional matches. If any content remains,
     273                                        // add it as an unprocessed piece.
     274                                        if ( remaining )
     275                                                pieces.push({ content: remaining });
     276                                });
     277                        });
     278
     279                        return _.pluck( pieces, 'content' ).join('');
     280                },
     281
     282                toView: function( viewType, options ) {
     283                        var view = wp.mce.view.get( viewType ),
     284                                instance, id, tag;
     285
     286                        if ( ! view )
     287                                return '';
     288
     289                        // Create a new view instance.
     290                        instance = new view.view( _.extend( options || {}, {
     291                                viewType: viewType
     292                        }) );
     293
     294                        // Use the view's `id` if it already exists. Otherwise,
     295                        // create a new `id`.
     296                        id = instance.el.id = instance.el.id || _.uniqueId('__wpmce-');
     297                        instances[ id ] = instance;
     298
     299                        // If the view is a span, wrap it in a span.
     300                        tag = 'span' === instance.tagName ? 'span' : 'div';
     301
     302                        return '<' + tag + ' class="wp-view-wrap" data-wp-view="' + id + '" contenteditable="false"></' + tag + '>';
     303                },
     304
     305                // ### render( scope )
     306                // Renders any view instances inside a DOM node `scope`.
     307                //
     308                // View instances are detected by the presence of wrapper elements.
     309                // To generate wrapper elements, pass your content through
     310                // `wp.mce.view.toViews( content )`.
     311                render: function( scope ) {
     312                        $( '.wp-view-wrap', scope ).each( function() {
     313                                var wrapper = $(this),
     314                                        id = wrapper.data('wp-view'),
     315                                        view = instances[ id ];
     316
     317                                if ( ! view )
     318                                        return;
     319
     320                                // Render the view.
     321                                view.render();
     322                                // Detach the view element to ensure events are not unbound.
     323                                view.$el.detach();
     324
     325                                // Empty the wrapper, attach the view element to the wrapper,
     326                                // and add an ending marker to the wrapper to help regexes
     327                                // scan the HTML string.
     328                                wrapper.empty().append( view.el ).append('<span data-wp-view-end></span>');
     329                        });
     330                },
     331
     332                // ### toText( content )
     333                // Scans an HTML `content` string and replaces any view instances with
     334                // their respective text representations.
     335                toText: function( content ) {
     336                        return content.replace( /<(?:div|span)[^>]+data-wp-view="([^"]+)"[^>]*>.*?<span data-wp-view-end[^>]*><\/span><\/(?:div|span)>/g, function( match, id ) {
     337                                var instance = instances[ id ],
     338                                        view;
     339
     340                                if ( instance )
     341                                        view = wp.mce.view.get( instance.options.viewType );
     342
     343                                return instance && view ? view.text( instance ) : '';
     344                        });
     345                },
     346
     347                // ### Remove internal TinyMCE attributes.
     348                removeInternalAttrs: function( attrs ) {
     349                        var result = {};
     350                        _.each( attrs, function( value, attr ) {
     351                                if ( -1 === attr.indexOf('data-mce') )
     352                                        result[ attr ] = value;
     353                        });
     354                        return result;
     355                },
     356
     357                // ### Parse an attribute string and removes internal TinyMCE attributes.
     358                attrs: function( content ) {
     359                        return wp.mce.view.removeInternalAttrs( wp.html.attrs( content ) );
     360                },
     361
     362                // Link any localized strings.
     363                l10n: _.isUndefined( _wpMceViewL10n ) ? {} : _wpMceViewL10n
     364        };
     365
     366}(jQuery));
     367
     368// Default TinyMCE Views
     369// ---------------------
     370(function($){
     371        var mceview = wp.mce.view;
     372
     373        wp.media.string = {};
     374        wp.media.string.image = function( attachment, props ) {
     375                var classes, img, options, size;
     376
     377                attachment = attachment.toJSON();
     378
     379                props = _.defaults( props || {}, {
     380                        img:   {},
     381                        align: getUserSetting( 'align', 'none' ),
     382                        size:  getUserSetting( 'imgsize', 'medium' ),
     383                        link:  getUserSetting( 'urlbutton', 'post' )
     384                });
     385
     386                img     = _.clone( props.img );
     387                classes = img['class'] ? img['class'].split(/\s+/) : [];
     388                size    = attachment.sizes ? attachment.sizes[ props.size ] : {};
     389
     390                if ( ! size )
     391                        delete props.size;
     392
     393                if( undefined !== size ) {
     394                        img.width = size.width;
     395                        img.height = size.height;
     396                        img.src    = size.url;
     397                } else {
     398                        img.width  = attachment.width;
     399                        img.height = attachment.height;
     400                        img.src    = attachment.url;
     401                }
     402
     403                // Update `img` classes.
     404                if ( props.align )
     405                        classes.push( 'align' + props.align );
     406
     407                if ( props.size )
     408                        classes.push( 'size-' + props.size );
     409
     410                classes.push( 'wp-image-' + attachment.id );
     411
     412                img['class'] = _.compact( classes ).join(' ');
     413
     414                // Generate `img` tag options.
     415                options = {
     416                        tag:    'img',
     417                        attrs:  img,
     418                        single: true
     419                };
     420
     421                // Generate the `a` element options, if they exist.
     422                if ( props.anchor ) {
     423                        options = {
     424                                tag:     'a',
     425                                attrs:   props.anchor,
     426                                content: options
     427                        };
     428                }
     429
     430                return wp.html.string( options );
     431        };
     432
     433        mceview.add( 'attachment', {
     434                pattern: new RegExp( '(?:<a([^>]*)>)?<img([^>]*class=(?:"[^"]*|\'[^\']*)\\bwp-image-(\\d+)[^>]*)>(?:</a>)?' ),
     435
     436                text: function( instance ) {
     437                        var props = _.pick( instance, 'align', 'size', 'link', 'img', 'anchor' );
     438                        return wp.media.string.image( instance.model, props );
     439                },
     440
     441                view: {
     442                        className: 'editor-attachment',
     443                        template:  media.template('editor-attachment'),
     444
     445                        events: {
     446                                'click .close': 'remove'
     447                        },
     448
     449                        initialize: function() {
     450                                var view    = this,
     451                                        results = this.options.results,
     452                                        id      = results[3],
     453                                        className;
     454
     455                                this.model = wp.media.model.Attachment.get( id );
     456
     457                                if ( results[1] )
     458                                        this.anchor = mceview.attrs( results[1] );
     459
     460                                this.img  = mceview.attrs( results[2] );
     461                                className = this.img['class'];
     462
     463                                // Strip ID class.
     464                                className = className.replace( /(?:^|\s)wp-image-\d+/, '' );
     465
     466                                // Calculate thumbnail `size` and remove class.
     467                                className = className.replace( /(?:^|\s)size-(\S+)/, function( match, size ) {
     468                                        view.size = size;
     469                                        return '';
     470                                });
     471
     472                                // Calculate `align` and remove class.
     473                                className = className.replace( /(?:^|\s)align(left|center|right|none)(?:\s|$)/, function( match, align ) {
     474                                        view.align = align;
     475                                        return '';
     476                                });
     477
     478                                this.img['class'] = className;
     479
     480                                this.$el.addClass('spinner');
     481                                this.model.fetch().done( _.bind( this.render, this ) );
     482                        },
     483
     484                        render: function() {
     485                                var attachment = this.model.toJSON(),
     486                                        options;
     487
     488                                // If we don't have the attachment data, bail.
     489                                if ( ! attachment.url )
     490                                        return;
     491
     492                                options = {
     493                                        url: 'image' === attachment.type ? attachment.url : attachment.icon,
     494                                        uploading: attachment.uploading
     495                                };
     496
     497                                _.extend( options, wp.media.fit({
     498                                        width:    attachment.width,
     499                                        height:   attachment.height,
     500                                        maxWidth: mceview.l10n.contentWidth
     501                                }) );
     502
     503                                // Use the specified size if it exists.
     504                                if ( this.size && attachment.sizes && attachment.sizes[ this.size ] )
     505                                        _.extend( options, _.pick( attachment.sizes[ this.size ], 'url', 'width', 'height' ) );
     506
     507                                this.$el.html( this.template( options ) );
     508                        }
     509                }
     510        });
     511
     512        mceview.add( 'gallery', {
     513                shortcode: 'gallery',
     514
     515                gallery: (function() {
     516                        var galleries = {};
     517
     518                        return {
     519                                attachments: function( shortcode, parent ) {
     520                                        var shortcodeString = shortcode.string(),
     521                                                result = galleries[ shortcodeString ],
     522                                                attrs, args;
     523
     524                                        delete galleries[ shortcodeString ];
     525
     526                                        if ( result )
     527                                                return result;
     528
     529                                        attrs = shortcode.attrs.named;
     530                                        args  = _.pick( attrs, 'orderby', 'order' );
     531
     532                                        args.type    = 'image';
     533                                        args.perPage = -1;
     534
     535                                        // Map the `ids` param to the correct query args.
     536                                        if ( attrs.ids ) {
     537                                                args.post__in = attrs.ids.split(',');
     538                                                args.orderby  = 'post__in';
     539                                        } else if ( attrs.include ) {
     540                                                args.post__in = attrs.include.split(',');
     541                                        }
     542
     543                                        if ( attrs.exclude )
     544                                                args.post__not_in = attrs.exclude.split(',');
     545
     546                                        if ( ! args.post__in )
     547                                                args.parent = attrs.id || parent;
     548
     549                                        return media.query( args );
     550                                },
     551
     552                                shortcode: function( attachments ) {
     553                                        var attrs = _.pick( attachments.props.toJSON(), 'include', 'exclude', 'orderby', 'order' ),
     554                                                shortcode;
     555
     556                                        attrs.ids = attachments.pluck('id');
     557
     558                                        shortcode = new wp.shortcode({
     559                                                tag:    'gallery',
     560                                                attrs:  attrs,
     561                                                type:   'single'
     562                                        });
     563
     564                                        galleries[ shortcode.string() ] = attachments;
     565                                        return shortcode;
     566                                }
     567                        };
     568                }()),
     569
     570                view: {
     571                        className: 'editor-gallery',
     572                        template:  media.template('editor-gallery'),
     573
     574                        // The fallback post ID to use as a parent for galleries that don't
     575                        // specify the `ids` or `include` parameters.
     576                        //
     577                        // Uses the hidden input on the edit posts page by default.
     578                        parent: $('#post_ID').val(),
     579
     580                        events: {
     581                                'click .close': 'remove'
     582                        },
     583
     584                        initialize: function() {
     585                                var     view      = mceview.get('gallery'),
     586                                        shortcode = this.options.shortcode;
     587
     588                                this.attachments = view.gallery.attachments( shortcode, this.parent );
     589                                this.attachments.more().done( _.bind( this.render, this ) );
     590                        },
     591
     592                        render: function() {
     593                                var options, thumbnail, size;
     594
     595                                if ( ! this.attachments.length )
     596                                        return;
     597
     598                                thumbnail = this.attachments.first().toJSON();
     599                                size = thumbnail.sizes && thumbnail.sizes.thumbnail ? thumbnail.sizes.thumbnail : thumbnail;
     600
     601                                options = {
     602                                        url:         size.url,
     603                                        orientation: size.orientation,
     604                                        count:       this.attachments.length
     605                                };
     606
     607                                this.$el.html( this.template( options ) );
     608                        }
     609                }
     610        });
    605611}(jQuery));
     612 No newline at end of file