WordPress.org

Make WordPress Core

Ticket #22124: mce-view.js.diff

File mce-view.js.diff, 34.1 KB (added by timbeks, 19 months 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