Make WordPress Core

Ticket #29841: 29841.3.patch

File 29841.3.patch, 25.7 KB (added by iseulde, 10 years ago)
  • src/wp-includes/js/mce-view.js

     
    11/* global tinymce */
    2 /**
     2
     3/*
     4 * The TinyMCE view API.
     5 *
    36 * Note: this API is "experimental" meaning that it will probably change
    47 * in the next few releases based on feedback from 3.9.0.
    58 * If you decide to use it, please follow the development closely.
    69 */
    7 
    8 // Ensure the global `wp` object exists.
    9 window.wp = window.wp || {};
    10 
    1110( function( $ ) {
    1211        'use strict';
    1312
    1413        var views = {},
    1514                instances = {},
    16                 media = wp.media,
    17                 mediaWindows = [],
    18                 windowIdx = 0,
    19                 waitInterval = 50,
    20                 viewOptions = ['encodedText'];
     15                viewOptions = [ 'encodedText' ];
    2116
    22         // Create the `wp.mce` object if necessary.
     17        window.wp = window.wp || {};
    2318        wp.mce = wp.mce || {};
    2419
    2520        /**
    26          * wp.mce.View
     21         * wp.mce.views
    2722         *
     23         * A set of utilities that simplifies adding custom UI within a TinyMCE editor.
     24         * At its core, it serves as a series of converters, transforming text to a
     25         * custom UI, and back again.
     26         */
     27        wp.mce.views = {
     28
     29                /**
     30                 * Registers a new TinyMCE view.
     31                 *
     32                 * @param {String} The view type.
     33                 * @param {Object} A constructor.
     34                 */
     35                register: function( type, constructor ) {
     36                        var defaultConstructor = {
     37                                        type: type,
     38                                        View: {},
     39                                        toView: function( content ) {
     40                                                var match = wp.shortcode.next( this.type, content );
     41
     42                                                if ( ! match ) {
     43                                                        return;
     44                                                }
     45
     46                                                return {
     47                                                        index: match.index,
     48                                                        content: match.content,
     49                                                        options: {
     50                                                                shortcode: match.shortcode
     51                                                        }
     52                                                };
     53                                        }
     54                                };
     55
     56                        constructor = _.defaults( constructor, defaultConstructor );
     57                        constructor.View = wp.mce.View.extend( constructor.View );
     58
     59                        views[ type ] = constructor;
     60                },
     61
     62                /**
     63                 * Unregisters a TinyMCE view.
     64                 *
     65                 * @param {String} The view type.
     66                 */
     67                unregister: function( type ) {
     68                        delete views[ type ];
     69                },
     70
     71                /**
     72                 * Returns a TinyMCE view constructor.
     73                 *
     74                 * @param {String} The view type.
     75                 */
     76                get: function( type ) {
     77                        return views[ type ];
     78                },
     79
     80                /**
     81                 * The editor DOM is being rebuilt, run cleanup.
     82                 */
     83                unbind: function() {
     84                        _.each( instances, function( instance ) {
     85                                instance.unbind();
     86                        } );
     87                },
     88
     89                /**
     90                 * Scans a given string for each view's pattern,
     91                 * replacing any matches with markers,
     92                 * and creates a new instance for every match,
     93                 * which triggers the related data to be fetched.
     94                 *
     95                 * @param {String} String to scan.
     96                 */
     97                toViews: function( content ) {
     98                        var pieces = [ { content: content } ],
     99                                current;
     100
     101                        _.each( views, function( view, type ) {
     102                                current = pieces.slice();
     103                                pieces  = [];
     104
     105                                _.each( current, function( piece ) {
     106                                        var remaining = piece.content,
     107                                                result;
     108
     109                                        // Ignore processed pieces, but retain their location.
     110                                        if ( piece.processed ) {
     111                                                pieces.push( piece );
     112                                                return;
     113                                        }
     114
     115                                        // Iterate through the string progressively matching views
     116                                        // and slicing the string as we go.
     117                                        while ( remaining && ( result = view.toView( remaining ) ) ) {
     118                                                // Any text before the match becomes an unprocessed piece.
     119                                                if ( result.index ) {
     120                                                        pieces.push( { content: remaining.substring( 0, result.index ) } );
     121                                                }
     122
     123                                                // Add the processed piece for the match.
     124                                                pieces.push( {
     125                                                        content: wp.mce.views.toView( type, result.content, result.options ),
     126                                                        processed: true
     127                                                } );
     128
     129                                                // Update the remaining content.
     130                                                remaining = remaining.slice( result.index + result.content.length );
     131                                        }
     132
     133                                        // There are no additional matches. If any content remains,
     134                                        // add it as an unprocessed piece.
     135                                        if ( remaining ) {
     136                                                pieces.push( { content: remaining } );
     137                                        }
     138                                });
     139                        });
     140
     141                        return _.pluck( pieces, 'content' ).join('');
     142                },
     143
     144                /**
     145                 * Returns the marked text for a particular view type,
     146                 * and creates a new instance for that view type if it does not exist.
     147                 * The text will be returned unmarked if no view of that type is registered.
     148                 *
     149                 * @param {String} The view type.
     150                 * @param {String} The textual representation of the view.
     151                 * @param {Object} Options.
     152                 *
     153                 * @return {String} The markered text.
     154                 */
     155                toView: function( type, text, options ) {
     156                        var view = this.get( type ),
     157                                encodedText = window.encodeURIComponent( text );
     158
     159                        if ( ! view ) {
     160                                return text;
     161                        }
     162
     163                        if ( ! this.getInstance( encodedText ) ) {
     164                                instances[ encodedText ] = new view.View( _.extend( options, {
     165                                        encodedText: encodedText,
     166                                        type: type
     167                                } ) );
     168                        }
     169
     170                        return '<p data-wpview-marker="' + encodedText + '">' + text + '</p>';
     171                },
     172
     173                /**
     174                 * Refresh views after an update is made.
     175                 *
     176                 * @param {Object} The view to refresh.
     177                 * @param {String} The textual representation of the view.
     178                 * @param {Boolean} Whether or not to force rendering.
     179                 */
     180                refreshView: function( view, text, force ) {
     181                        var encodedText = window.encodeURIComponent( text );
     182
     183                        if ( ! this.getInstance( encodedText ) ) {
     184                                instances[ encodedText ] = new view.View( _.extend( view.toView( text ).options, {
     185                                        encodedText: encodedText,
     186                                        type: view.type
     187                                } ) );
     188                        }
     189
     190                        instances[ encodedText ].render( force );
     191                },
     192
     193                /**
     194                 * @param {String} The textual representation of the view, encoded.
     195                 */
     196                getInstance: function( encodedText ) {
     197                        return instances[ encodedText ];
     198                },
     199
     200                /**
     201                 * Renders any view instances.
     202                 *
     203                 * View instances are detected by the presence of markers.
     204                 * To generate markers, pass your content through `toViews`.
     205                 *
     206                 * @param {Boolean} Whether or not to force rendering.
     207                 */
     208                render: function( force ) {
     209                        _.each( instances, function( instance ) {
     210                                instance.render( force );
     211                        } );
     212                },
     213
     214                /**
     215                 * @param {HTMLElement} A view node.
     216                 */
     217                edit: function( node ) {
     218                        var type = $( node ).data( 'wpview-type' ),
     219                                view = this.get( type );
     220
     221                        if ( view ) {
     222                                view.edit( node );
     223                        }
     224                }
     225        };
     226
     227        /**
    28228         * A Backbone-like View constructor intended for use when rendering a TinyMCE View. The main difference is
    29229         * that the TinyMCE View is not tied to a particular DOM node.
    30230         *
    31          * @param {Object} [options={}]
     231         * @param {Object} Options.
    32232         */
    33233        wp.mce.View = function( options ) {
    34234                options = options || {};
     
    38238        };
    39239
    40240        _.extend( wp.mce.View.prototype, {
     241
     242                /**
     243                 * Whether or not to display a loader.
     244                 *
     245                 * @type {Boolean}
     246                 */
     247                loader: true,
     248
     249                /**
     250                 * Runs after the view instance is created.
     251                 */
    41252                initialize: function() {},
     253
     254                /**
     255                 * Retuns the HTML string to render in the view.
     256                 *
     257                 * @return {String} The HTML string.
     258                 */
    42259                getHtml: function() {
    43260                        return '';
    44261                },
    45                 loadingPlaceholder: function() {
    46                         return '' +
    47                                 '<div class="loading-placeholder">' +
    48                                         '<div class="dashicons dashicons-admin-media"></div>' +
    49                                         '<div class="wpview-loading"><ins></ins></div>' +
    50                                 '</div>';
    51                 },
    52                 render: function( force ) {
    53                         if ( force || ! this.rendered() ) {
    54                                 this.unbind();
    55262
    56                                 this.setContent(
    57                                         '<p class="wpview-selection-before">\u00a0</p>' +
    58                                         '<div class="wpview-body" contenteditable="false">' +
    59                                                 '<div class="toolbar mce-arrow-down">' +
    60                                                         ( _.isFunction( views[ this.type ].edit ) ? '<div class="dashicons dashicons-edit edit"></div>' : '' ) +
    61                                                         '<div class="dashicons dashicons-no remove"></div>' +
    62                                                 '</div>' +
    63                                                 '<div class="wpview-content wpview-type-' + this.type + '">' +
    64                                                         ( this.getHtml() || this.loadingPlaceholder() ) +
    65                                                 '</div>' +
    66                                                 ( this.overlay ? '<div class="wpview-overlay"></div>' : '' ) +
    67                                         '</div>' +
    68                                         '<p class="wpview-selection-after">\u00a0</p>',
    69                                         'wrap'
    70                                 );
     263                /**
     264                 * Retuns the HTML string to render in the view.
     265                 *
     266                 * @param {Boolean} Whether or not to rerender.
     267                 */
     268                render: function( rerender ) {
     269                        var i = 0;
    71270
    72                                 $( this ).trigger( 'ready' );
     271                        // If there's nothing to render an no loader needs to be shown, stop.
     272                        if ( ! this.loader && ! this.getHtml() ) {
     273                                return;
     274                        }
     275
     276                        // We're about to rerender all views of this instance, so unbind rendered views.
     277                        rerender && this.unbind();
     278
     279                        // Replace any left over markers.
     280                        this.replaceMarkers();
    73281
    74                                 this.rendered( true );
     282                        if ( this.getHtml() ) {
     283                                this.setContent( this.getHtml(), function( editor, node ) {
     284                                        $( node ).data( 'rendered', true );
     285                                        this.bindNodes.apply( this, arguments );
     286                                        i++;
     287                                }, rerender ? null : false );
     288
     289                                // Use `bindNodes` if possible.
     290                                i && $( this ).trigger( 'ready' );
     291                        } else {
     292                                this.setLoader();
    75293                        }
    76294                },
    77                 unbind: function() {},
     295
     296                /**
     297                 * Runs after a view is added to the DOM.
     298                 *
     299                 * @param {tinymce.Editor} The editor instance the view node is in.
     300                 * @param {HTMLElement} The view node.
     301                 * @param {HTMLElement} The view's content node.
     302                 */
     303                bindNodes: function() {},
     304
     305                /**
     306                 * Runs before a view is removed from the DOM.
     307                 */
     308                unbind: function() {
     309                        this.getNodes( function() {
     310                                this.unbindNodes.apply( this, arguments );
     311                        }, true );
     312                },
     313
     314                /**
     315                 * Runs before a view is removed from the DOM.
     316                 *
     317                 * @param {tinymce.Editor} The editor instance the view node is in.
     318                 * @param {HTMLElement} The view node.
     319                 * @param {HTMLElement} The view's content node.
     320                 */
     321                unbindNodes: function() {},
     322
     323                /**
     324                 * Gets all the TinyMCE editors that support views.
     325                 *
     326                 * @param {Function} A callback (optional).
     327                 *
     328                 * @return {tinymce.Editor[]} An array of TinyMCE editor instances.
     329                 */
    78330                getEditors: function( callback ) {
    79331                        var editors = [];
    80332
    81333                        _.each( tinymce.editors, function( editor ) {
    82334                                if ( editor.plugins.wpview ) {
    83                                         if ( callback ) {
    84                                                 callback( editor );
    85                                         }
    86 
    87335                                        editors.push( editor );
     336                                        callback && callback.call( this, editor );
    88337                                }
    89338                        }, this );
    90339
    91340                        return editors;
    92341                },
    93                 getNodes: function( callback ) {
    94                         var nodes = [],
    95                                 self = this;
     342
     343                /**
     344                 * Gets all the view nodes in each editor.
     345                 *
     346                 * @param {Function} A callback (optional).
     347                 * @param {Boolean} Get (un)rendered nodes (optional).
     348                 *
     349                 * @return {HTMLElement[]} An array of view nodes.
     350                 */
     351                getNodes: function( callback, rendered ) {
     352                        var nodes = [];
    96353
    97354                        this.getEditors( function( editor ) {
     355                                var self = this;
     356
    98357                                $( editor.getBody() )
    99                                 .find( '[data-wpview-text="' + self.encodedText + '"]' )
    100                                 .each( function ( i, node ) {
    101                                         if ( callback ) {
    102                                                 callback( editor, node, $( node ).find( '.wpview-content' ).get( 0 ) );
    103                                         }
     358                                        .find( '[data-wpview-text="' + self.encodedText + '"]' )
     359                                        .filter( function() {
     360                                                var data;
    104361
    105                                         nodes.push( node );
    106                                 } );
     362                                                if ( rendered == null ) {
     363                                                        return true;
     364                                                }
     365
     366                                                data = $( this ).data( 'rendered' ) === true;
     367
     368                                                return rendered ? data : ! data;
     369                                        } )
     370                                        .each( function ( i, node ) {
     371                                                nodes.push( node );
     372                                                callback && callback.call( self, editor, node, $( node ).find( '.wpview-content' ).get( 0 ) );
     373                                        } );
    107374                        } );
    108375
    109376                        return nodes;
    110377                },
    111                 setContent: function( html, option ) {
    112                         this.getNodes( function ( editor, node, content ) {
    113                                 var el = ( option === 'wrap' || option === 'replace' ) ? node : content,
    114                                         insert = html;
    115378
    116                                 if ( _.isString( insert ) ) {
    117                                         insert = editor.dom.createFragment( insert );
    118                                 }
     379                /**
     380                 * Gets all the markers in each editor.
     381                 *
     382                 * @param {Function} A callback (optional).
     383                 *
     384                 * @return {HTMLElement[]} An array of marker nodes.
     385                 */
     386                getMarkers: function( callback ) {
     387                        var markers = [];
    119388
    120                                 if ( option === 'replace' ) {
    121                                         editor.dom.replace( insert, el );
    122                                 } else {
    123                                         el.innerHTML = '';
    124                                         el.appendChild( insert );
     389                        this.getEditors( function( editor ) {
     390                                var self = this;
     391
     392                                $( editor.getBody() )
     393                                        .find( '[data-wpview-marker="' + this.encodedText + '"]' )
     394                                        .each( function( i, node ) {
     395                                                markers.push( node );
     396                                                callback && callback.call( self, editor, node );
     397                                        } );
     398                        } );
     399
     400                        return markers;
     401                },
     402
     403                /**
     404                 * Replaces all markers with view nodes.
     405                 */
     406                replaceMarkers: function() {
     407                        this.getMarkers( function( editor, node ) {
     408                                if ( $( node ).text() !== decodeURIComponent( this.encodedText ) ) {
     409                                        editor.dom.setAttrib( node, 'data-wpview-marker', null );
     410                                        return;
    125411                                }
     412
     413                                editor.dom.replace(
     414                                        editor.dom.createFragment(
     415                                                '<div class="wpview-wrap" data-wpview-text="' + this.encodedText + '" data-wpview-type="' + this.type + '">' +
     416                                                        '<p class="wpview-selection-before">\u00a0</p>' +
     417                                                        '<div class="wpview-body" contenteditable="false">' +
     418                                                                '<div class="toolbar mce-arrow-down">' +
     419                                                                        ( _.isFunction( views[ this.type ].edit ) ? '<div class="dashicons dashicons-edit edit"></div>' : '' ) +
     420                                                                        '<div class="dashicons dashicons-no remove"></div>' +
     421                                                                '</div>' +
     422                                                                '<div class="wpview-content wpview-type-' + this.type + '"></div>' +
     423                                                                ( this.overlay ? '<div class="wpview-overlay"></div>' : '' ) +
     424                                                        '</div>' +
     425                                                        '<p class="wpview-selection-after">\u00a0</p>' +
     426                                                '</div>'
     427                                        ),
     428                                        node
     429                                );
     430                        } );
     431                },
     432
     433                /**
     434                 * Removes all markers.
     435                 */
     436                removeMarkers: function() {
     437                        this.getMarkers( function( editor, node ) {
     438                                editor.dom.setAttrib( node, 'data-wpview-marker', null );
    126439                        } );
    127440                },
    128                 /* jshint scripturl: true */
    129                 setIframes: function ( head, body ) {
     441
     442                /**
     443                 * Gets all the markers in each editor.
     444                 *
     445                 * @param {(String|HTMLElement)} The content to set.
     446                 * @param {Function} A callback (optional).
     447                 * @param {Boolean} Only set for (un)rendered nodes (optional).
     448                 */
     449                setContent: function( html, callback, rendered ) {
     450                        this.getNodes( function( editor, node, content ) {
     451                                content.innerHTML = '';
     452                                content.appendChild( _.isString( html ) ? editor.dom.createFragment( html ) : html );
     453                                callback && callback.apply( this, arguments );
     454                        }, rendered );
     455                },
     456
     457                /**
     458                 * Set an iframe as the view content.
     459                 */
     460                setIframes: function( head, body ) {
    130461                        var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver,
    131462                                importStyles = this.type === 'video' || this.type === 'audio' || this.type === 'playlist';
    132463
    133464                        if ( head || body.indexOf( '<script' ) !== -1 ) {
    134                                 this.getNodes( function ( editor, node, content ) {
     465                                this.getNodes( function( editor, node, content ) {
    135466                                        var dom = editor.dom,
    136467                                                styles = '',
    137468                                                bodyClasses = editor.getBody().className || '',
     
    156487                                                }
    157488                                        }
    158489
    159                                         // Seems Firefox needs a bit of time to insert/set the view nodes, or the iframe will fail
    160                                         // especially when switching Text => Visual.
     490                                        // Seems Firefox needs a bit of time to insert/set the view nodes,
     491                                        // or the iframe will fail especially when switching Text => Visual.
    161492                                        setTimeout( function() {
    162493                                                iframe = dom.add( content, 'iframe', {
     494                                                        /* jshint scripturl: true */
    163495                                                        src: tinymce.Env.ie ? 'javascript:""' : '',
    164496                                                        frameBorder: '0',
    165497                                                        allowTransparency: 'true',
     
    241573                                                                iframeDoc.body.className = editor.getBody().className;
    242574                                                        });
    243575                                                }
    244                                         }, waitInterval );
     576                                        }, 50 );
    245577                                });
    246578                        } else {
    247579                                this.setContent( body );
    248580                        }
    249581                },
     582
     583                /**
     584                 * Set a loader.
     585                 */
     586                setLoader: function() {
     587                        this.setContent(
     588                                '<div class="loading-placeholder">' +
     589                                        '<div class="dashicons dashicons-admin-media"></div>' +
     590                                        '<div class="wpview-loading"><ins></ins></div>' +
     591                                '</div>'
     592                        );
     593                },
     594
     595                /**
     596                 * Set an error.
     597                 *
     598                 * @param {String} The error message to set.
     599                 * @param {String} A dashicon ID (optional). {@link https://developer.wordpress.org/resource/dashicons/}
     600                 */
    250601                setError: function( message, dashicon ) {
    251602                        this.setContent(
    252603                                '<div class="wpview-error">' +
    253                                         '<div class="dashicons dashicons-' + ( dashicon ? dashicon : 'no' ) + '"></div>' +
     604                                        '<div class="dashicons dashicons-' + ( dashicon || 'no' ) + '"></div>' +
    254605                                        '<p>' + message + '</p>' +
    255606                                '</div>'
    256607                        );
    257                 },
    258                 rendered: function( value ) {
    259                         var notRendered;
    260 
    261                         this.getNodes( function( editor, node ) {
    262                                 if ( value != null ) {
    263                                         $( node ).data( 'rendered', value === true );
    264                                 } else {
    265                                         notRendered = notRendered || ! $( node ).data( 'rendered' );
    266                                 }
    267                         } );
    268 
    269                         return ! notRendered;
    270608                }
    271609        } );
    272610
    273         // take advantage of the Backbone extend method
     611        // Take advantage of the Backbone extend method.
    274612        wp.mce.View.extend = Backbone.View.extend;
     613} )( jQuery );
    275614
    276         /**
    277          * wp.mce.views
    278          *
    279          * A set of utilities that simplifies adding custom UI within a TinyMCE editor.
    280          * At its core, it serves as a series of converters, transforming text to a
    281          * custom UI, and back again.
    282          */
    283         wp.mce.views = {
    284 
    285                 /**
    286                  * wp.mce.views.register( type, view )
    287                  *
    288                  * Registers a new TinyMCE view.
    289                  *
    290                  * @param type
    291                  * @param constructor
    292                  *
    293                  */
    294                 register: function( type, constructor ) {
    295                         var defaultConstructor = {
    296                                         type: type,
    297                                         View: {},
    298                                         toView: function( content ) {
    299                                                 var match = wp.shortcode.next( this.type, content );
    300 
    301                                                 if ( ! match ) {
    302                                                         return;
    303                                                 }
    304 
    305                                                 return {
    306                                                         index: match.index,
    307                                                         content: match.content,
    308                                                         options: {
    309                                                                 shortcode: match.shortcode
    310                                                         }
    311                                                 };
    312                                         }
    313                                 };
    314 
    315                         constructor = _.defaults( constructor, defaultConstructor );
    316                         constructor.View = wp.mce.View.extend( constructor.View );
    317 
    318                         views[ type ] = constructor;
    319                 },
    320 
    321                 /**
    322                  * wp.mce.views.get( id )
    323                  *
    324                  * Returns a TinyMCE view constructor.
    325                  *
    326                  * @param type
    327                  */
    328                 get: function( type ) {
    329                         return views[ type ];
    330                 },
    331 
    332                 /**
    333                  * wp.mce.views.unregister( type )
    334                  *
    335                  * Unregisters a TinyMCE view.
    336                  *
    337                  * @param type
    338                  */
    339                 unregister: function( type ) {
    340                         delete views[ type ];
    341                 },
    342 
    343                 /**
    344                  * wp.mce.views.unbind( editor )
    345                  *
    346                  * The editor DOM is being rebuilt, run cleanup.
    347                  */
    348                 unbind: function() {
    349                         _.each( instances, function( instance ) {
    350                                 instance.unbind();
    351                         } );
    352                 },
    353 
    354                 /**
    355                  * toViews( content )
    356                  * Scans a `content` string for each view's pattern, replacing any
    357                  * matches with wrapper elements, and creates a new instance for
    358                  * every match, which triggers the related data to be fetched.
    359                  *
    360                  * @param content
    361                  */
    362                 toViews: function( content ) {
    363                         var pieces = [ { content: content } ],
    364                                 current;
    365 
    366                         _.each( views, function( view, viewType ) {
    367                                 current = pieces.slice();
    368                                 pieces  = [];
    369 
    370                                 _.each( current, function( piece ) {
    371                                         var remaining = piece.content,
    372                                                 result;
    373 
    374                                         // Ignore processed pieces, but retain their location.
    375                                         if ( piece.processed ) {
    376                                                 pieces.push( piece );
    377                                                 return;
    378                                         }
    379 
    380                                         // Iterate through the string progressively matching views
    381                                         // and slicing the string as we go.
    382                                         while ( remaining && (result = view.toView( remaining )) ) {
    383                                                 // Any text before the match becomes an unprocessed piece.
    384                                                 if ( result.index ) {
    385                                                         pieces.push({ content: remaining.substring( 0, result.index ) });
    386                                                 }
    387 
    388                                                 // Add the processed piece for the match.
    389                                                 pieces.push({
    390                                                         content: wp.mce.views.toView( viewType, result.content, result.options ),
    391                                                         processed: true
    392                                                 });
    393 
    394                                                 // Update the remaining content.
    395                                                 remaining = remaining.slice( result.index + result.content.length );
    396                                         }
    397 
    398                                         // There are no additional matches. If any content remains,
    399                                         // add it as an unprocessed piece.
    400                                         if ( remaining ) {
    401                                                 pieces.push({ content: remaining });
    402                                         }
    403                                 });
    404                         });
    405 
    406                         return _.pluck( pieces, 'content' ).join('');
    407                 },
    408 
    409                 /**
    410                  * Create a placeholder for a particular view type
    411                  *
    412                  * @param viewType
    413                  * @param text
    414                  * @param options
    415                  *
    416                  */
    417                 toView: function( viewType, text, options ) {
    418                         var view = wp.mce.views.get( viewType ),
    419                                 encodedText = window.encodeURIComponent( text ),
    420                                 instance, viewOptions;
    421 
    422 
    423                         if ( ! view ) {
    424                                 return text;
    425                         }
    426 
    427                         if ( ! wp.mce.views.getInstance( encodedText ) ) {
    428                                 viewOptions = options;
    429                                 viewOptions.type = viewType;
    430                                 viewOptions.encodedText = encodedText;
    431                                 instance = new view.View( viewOptions );
    432                                 instances[ encodedText ] = instance;
    433                         }
    434 
    435                         return wp.html.string({
    436                                 tag: 'div',
    437 
    438                                 attrs: {
    439                                         'class': 'wpview-wrap',
    440                                         'data-wpview-text': encodedText,
    441                                         'data-wpview-type': viewType
    442                                 },
    443 
    444                                 content: '\u00a0'
    445                         });
    446                 },
    447 
    448                 /**
    449                  * Refresh views after an update is made
    450                  *
    451                  * @param view {object} being refreshed
    452                  * @param text {string} textual representation of the view
    453                  * @param force {Boolean} whether to force rendering
    454                  */
    455                 refreshView: function( view, text, force ) {
    456                         var encodedText = window.encodeURIComponent( text ),
    457                                 viewOptions,
    458                                 result, instance;
    459 
    460                         instance = wp.mce.views.getInstance( encodedText );
    461 
    462                         if ( ! instance ) {
    463                                 result = view.toView( text );
    464                                 viewOptions = result.options;
    465                                 viewOptions.type = view.type;
    466                                 viewOptions.encodedText = encodedText;
    467                                 instance = new view.View( viewOptions );
    468                                 instances[ encodedText ] = instance;
    469                         }
    470 
    471                         instance.render( force );
    472                 },
    473 
    474                 getInstance: function( encodedText ) {
    475                         return instances[ encodedText ];
    476                 },
    477 
    478                 /**
    479                  * render( scope )
    480                  *
    481                  * Renders any view instances inside a DOM node `scope`.
    482                  *
    483                  * View instances are detected by the presence of wrapper elements.
    484                  * To generate wrapper elements, pass your content through
    485                  * `wp.mce.view.toViews( content )`.
    486                  */
    487                 render: function( force ) {
    488                         _.each( instances, function( instance ) {
    489                                 instance.render( force );
    490                         } );
    491                 },
    492 
    493                 edit: function( node ) {
    494                         var viewType = $( node ).data('wpview-type'),
    495                                 view = wp.mce.views.get( viewType );
    496 
    497                         if ( view ) {
    498                                 view.edit( node );
    499                         }
    500                 }
    501         };
     615/*
     616 * The WordPress core TinyMCE views.
     617 * Views for the gallery, audio, video, playlist and embed shortcodes,
     618 * and a view for embeddable URLs.
     619 */
     620( function( $, views ) {
     621        var mediaWindows = [],
     622                windowIdx = 0,
     623                waitInterval = 50;
    502624
    503         wp.mce.views.register( 'gallery', {
     625        views.register( 'gallery', {
    504626                View: {
    505                         template: media.template( 'editor-gallery' ),
     627                        template: wp.media.template( 'editor-gallery' ),
    506628
    507629                        // The fallback post ID to use as a parent for galleries that don't
    508630                        // specify the `ids` or `include` parameters.
     
    520642
    521643                                this.attachments = wp.media.gallery.attachments( this.shortcode, this.postID );
    522644                                this.dfd = this.attachments.more().done( function() {
    523                                         self.render( true );
     645                                        self.render();
    524646                                } );
    525647                        },
    526648
     
    681803                                        }
    682804                                } )
    683805                                .done( function( response ) {
    684                                         if ( response ) {
    685                                                 self.parsed = response;
    686                                                 self.setIframes( response.head, response.body );
    687                                                 self.deferredListen();
    688                                         } else {
    689                                                 self.fail( true );
    690                                         }
     806                                        self.parsed = response;
     807                                        self.render();
    691808                                } )
    692809                                .fail( function( response ) {
    693810                                        self.fail( response || true );
     
    703820                                        }
    704821                                }
    705822
    706                                 if ( this.error.message ) {
    707                                         if ( ( this.error.type === 'not-embeddable' && this.type === 'embed' ) || this.error.type === 'not-ssl' ||
    708                                                 this.error.type === 'no-items' ) {
    709 
    710                                                 this.setError( this.error.message, 'admin-media' );
    711                                         } else {
    712                                                 this.setContent( '<p>' + this.original + '</p>', 'replace' );
    713                                         }
    714                                 } else if ( this.error.statusText ) {
    715                                         this.setError( this.error.statusText, 'admin-media' );
    716                                 } else if ( this.original ) {
    717                                         this.setContent( '<p>' + this.original + '</p>', 'replace' );
     823                                if ( this.type === 'embedURL' ) {
     824                                        this.removeMarkers();
     825                                } else {
     826                                        this.setError( this.error.message || this.error.statusText, 'admin-media' );
    718827                                }
    719828                        },
    720829
     
    742851
    743852                        unbind: function() {
    744853                                this.stopPlayers( 'remove' );
     854                        },
     855
     856                        getHtml: function() {
     857                                return this.parsed ? '\u00a0' : '';
    745858                        }
    746859                },
    747860
     
    790903         *
    791904         * @mixes wp.mce.av
    792905         */
    793         wp.mce.views.register( 'video', _.extend( {}, wp.mce.av, {
     906        views.register( 'video', _.extend( {}, wp.mce.av, {
    794907                state: 'video-details'
    795908        } ) );
    796909
     
    799912         *
    800913         * @mixes wp.mce.av
    801914         */
    802         wp.mce.views.register( 'audio', _.extend( {}, wp.mce.av, {
     915        views.register( 'audio', _.extend( {}, wp.mce.av, {
    803916                state: 'audio-details'
    804917        } ) );
    805918
     
    808921         *
    809922         * @mixes wp.mce.av
    810923         */
    811         wp.mce.views.register( 'playlist', _.extend( {}, wp.mce.av, {
     924        views.register( 'playlist', _.extend( {}, wp.mce.av, {
    812925                state: [ 'playlist-edit', 'video-playlist-edit' ]
    813926        } ) );
    814927
     
    821934                        action: 'parse-embed',
    822935                        initialize: function( options ) {
    823936                                this.content = options.content;
    824                                 this.original = options.url || options.shortcode.string();
    825937
    826938                                if ( options.url ) {
    827                                         this.shortcode = media.embed.shortcode( {
     939                                        this.loader = false;
     940                                        this.shortcode = wp.media.embed.shortcode( {
    828941                                                url: options.url
    829942                                        } );
    830943                                } else {
     
    838951                        }
    839952                } ),
    840953                edit: function( node ) {
    841                         var embed = media.embed,
     954                        var embed = wp.media.embed,
    842955                                self = this,
    843956                                frame,
    844957                                data,
     
    873986                }
    874987        };
    875988
    876         wp.mce.views.register( 'embed', _.extend( {}, wp.mce.embedMixin ) );
     989        views.register( 'embed', _.extend( {}, wp.mce.embedMixin ) );
    877990
    878         wp.mce.views.register( 'embedURL', _.extend( {}, wp.mce.embedMixin, {
     991        views.register( 'embedURL', _.extend( {}, wp.mce.embedMixin, {
    879992                toView: function( content ) {
    880993                        var re = /(^|<p>)(https?:\/\/[^\s"]+?)(<\/p>\s*|$)/gi,
    881994                                match = re.exec( tinymce.trim( content ) );
     
    8931006                        };
    8941007                }
    8951008        } ) );
    896 
    897 }(jQuery));
     1009} )( jQuery, wp.mce.views );