~canonical-sysadmins/wordpress/4.7.2

« back to all changes in this revision

Viewing changes to wp-includes/js/mce-view.js

  • Committer: Jacek Nykis
  • Date: 2015-01-05 16:17:05 UTC
  • Revision ID: jacek.nykis@canonical.com-20150105161705-w544l1h5mcg7u4w9
Initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* global tinymce */
 
2
/**
 
3
 * Note: this API is "experimental" meaning that it will probably change
 
4
 * in the next few releases based on feedback from 3.9.0.
 
5
 * If you decide to use it, please follow the development closely.
 
6
 */
 
7
 
 
8
// Ensure the global `wp` object exists.
 
9
window.wp = window.wp || {};
 
10
 
 
11
( function( $ ) {
 
12
        'use strict';
 
13
 
 
14
        var views = {},
 
15
                instances = {},
 
16
                media = wp.media,
 
17
                viewOptions = ['encodedText'];
 
18
 
 
19
        // Create the `wp.mce` object if necessary.
 
20
        wp.mce = wp.mce || {};
 
21
 
 
22
        /**
 
23
         * wp.mce.View
 
24
         *
 
25
         * A Backbone-like View constructor intended for use when rendering a TinyMCE View. The main difference is
 
26
         * that the TinyMCE View is not tied to a particular DOM node.
 
27
         *
 
28
         * @param {Object} [options={}]
 
29
         */
 
30
        wp.mce.View = function( options ) {
 
31
                options = options || {};
 
32
                this.type = options.type;
 
33
                _.extend( this, _.pick( options, viewOptions ) );
 
34
                this.initialize.apply( this, arguments );
 
35
        };
 
36
 
 
37
        _.extend( wp.mce.View.prototype, {
 
38
                initialize: function() {},
 
39
                getHtml: function() {
 
40
                        return '';
 
41
                },
 
42
                loadingPlaceholder: function() {
 
43
                        return '' +
 
44
                                '<div class="loading-placeholder">' +
 
45
                                        '<div class="dashicons dashicons-admin-media"></div>' +
 
46
                                        '<div class="wpview-loading"><ins></ins></div>' +
 
47
                                '</div>';
 
48
                },
 
49
                render: function( force ) {
 
50
                        if ( force || ! this.rendered() ) {
 
51
                                this.unbind();
 
52
 
 
53
                                this.setContent(
 
54
                                        '<p class="wpview-selection-before">\u00a0</p>' +
 
55
                                        '<div class="wpview-body" contenteditable="false">' +
 
56
                                                '<div class="toolbar">' +
 
57
                                                        ( _.isFunction( views[ this.type ].edit ) ? '<div class="dashicons dashicons-edit edit"></div>' : '' ) +
 
58
                                                        '<div class="dashicons dashicons-no-alt remove"></div>' +
 
59
                                                '</div>' +
 
60
                                                '<div class="wpview-content wpview-type-' + this.type + '">' +
 
61
                                                        ( this.getHtml() || this.loadingPlaceholder() ) +
 
62
                                                '</div>' +
 
63
                                                ( this.overlay ? '<div class="wpview-overlay"></div>' : '' ) +
 
64
                                        '</div>' +
 
65
                                        '<p class="wpview-selection-after">\u00a0</p>',
 
66
                                        'wrap'
 
67
                                );
 
68
 
 
69
                                $( this ).trigger( 'ready' );
 
70
 
 
71
                                this.rendered( true );
 
72
                        }
 
73
                },
 
74
                unbind: function() {},
 
75
                getEditors: function( callback ) {
 
76
                        var editors = [];
 
77
 
 
78
                        _.each( tinymce.editors, function( editor ) {
 
79
                                if ( editor.plugins.wpview ) {
 
80
                                        if ( callback ) {
 
81
                                                callback( editor );
 
82
                                        }
 
83
 
 
84
                                        editors.push( editor );
 
85
                                }
 
86
                        }, this );
 
87
 
 
88
                        return editors;
 
89
                },
 
90
                getNodes: function( callback ) {
 
91
                        var nodes = [],
 
92
                                self = this;
 
93
 
 
94
                        this.getEditors( function( editor ) {
 
95
                                $( editor.getBody() )
 
96
                                .find( '[data-wpview-text="' + self.encodedText + '"]' )
 
97
                                .each( function ( i, node ) {
 
98
                                        if ( callback ) {
 
99
                                                callback( editor, node, $( node ).find( '.wpview-content' ).get( 0 ) );
 
100
                                        }
 
101
 
 
102
                                        nodes.push( node );
 
103
                                } );
 
104
                        } );
 
105
 
 
106
                        return nodes;
 
107
                },
 
108
                setContent: function( html, option ) {
 
109
                        this.getNodes( function ( editor, node, content ) {
 
110
                                var el = ( option === 'wrap' || option === 'replace' ) ? node : content,
 
111
                                        insert = html;
 
112
 
 
113
                                if ( _.isString( insert ) ) {
 
114
                                        insert = editor.dom.createFragment( insert );
 
115
                                }
 
116
 
 
117
                                if ( option === 'replace' ) {
 
118
                                        editor.dom.replace( insert, el );
 
119
                                } else {
 
120
                                        el.innerHTML = '';
 
121
                                        el.appendChild( insert );
 
122
                                }
 
123
                        } );
 
124
                },
 
125
                /* jshint scripturl: true */
 
126
                setIframes: function ( head, body ) {
 
127
                        var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver,
 
128
                                importStyles = this.type === 'video' || this.type === 'audio' || this.type === 'playlist';
 
129
 
 
130
                        if ( head || body.indexOf( '<script' ) !== -1 ) {
 
131
                                this.getNodes( function ( editor, node, content ) {
 
132
                                        var dom = editor.dom,
 
133
                                                styles = '',
 
134
                                                bodyClasses = editor.getBody().className || '',
 
135
                                                iframe, iframeDoc, i, resize;
 
136
 
 
137
                                        content.innerHTML = '';
 
138
                                        head = head || '';
 
139
 
 
140
                                        if ( importStyles ) {
 
141
                                                if ( ! wp.mce.views.sandboxStyles ) {
 
142
                                                        tinymce.each( dom.$( 'link[rel="stylesheet"]', editor.getDoc().head ), function( link ) {
 
143
                                                                if ( link.href && link.href.indexOf( 'skins/lightgray/content.min.css' ) === -1 &&
 
144
                                                                        link.href.indexOf( 'skins/wordpress/wp-content.css' ) === -1 ) {
 
145
 
 
146
                                                                        styles += dom.getOuterHTML( link ) + '\n';
 
147
                                                                }
 
148
                                                        });
 
149
 
 
150
                                                        wp.mce.views.sandboxStyles = styles;
 
151
                                                } else {
 
152
                                                        styles = wp.mce.views.sandboxStyles;
 
153
                                                }
 
154
                                        }
 
155
 
 
156
                                        // Seems Firefox needs a bit of time to insert/set the view nodes, or the iframe will fail
 
157
                                        // especially when switching Text => Visual.
 
158
                                        setTimeout( function() {
 
159
                                                iframe = dom.add( content, 'iframe', {
 
160
                                                        src: tinymce.Env.ie ? 'javascript:""' : '',
 
161
                                                        frameBorder: '0',
 
162
                                                        allowTransparency: 'true',
 
163
                                                        scrolling: 'no',
 
164
                                                        'class': 'wpview-sandbox',
 
165
                                                        style: {
 
166
                                                                width: '100%',
 
167
                                                                display: 'block'
 
168
                                                        }
 
169
                                                } );
 
170
 
 
171
                                                iframeDoc = iframe.contentWindow.document;
 
172
 
 
173
                                                iframeDoc.open();
 
174
                                                iframeDoc.write(
 
175
                                                        '<!DOCTYPE html>' +
 
176
                                                        '<html>' +
 
177
                                                                '<head>' +
 
178
                                                                        '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />' +
 
179
                                                                        head +
 
180
                                                                        styles +
 
181
                                                                        '<style>' +
 
182
                                                                                'html {' +
 
183
                                                                                        'background: transparent;' +
 
184
                                                                                        'padding: 0;' +
 
185
                                                                                        'margin: 0;' +
 
186
                                                                                '}' +
 
187
                                                                                'body#wpview-iframe-sandbox {' +
 
188
                                                                                        'background: transparent;' +
 
189
                                                                                        'padding: 1px 0 !important;' +
 
190
                                                                                        'margin: -1px 0 0 !important;' +
 
191
                                                                                '}' +
 
192
                                                                                'body#wpview-iframe-sandbox:before,' +
 
193
                                                                                'body#wpview-iframe-sandbox:after {' +
 
194
                                                                                        'display: none;' +
 
195
                                                                                        'content: "";' +
 
196
                                                                                '}' +
 
197
                                                                        '</style>' +
 
198
                                                                '</head>' +
 
199
                                                                '<body id="wpview-iframe-sandbox" class="' + bodyClasses + '">' +
 
200
                                                                        body +
 
201
                                                                '</body>' +
 
202
                                                        '</html>'
 
203
                                                );
 
204
                                                iframeDoc.close();
 
205
 
 
206
                                                resize = function() {
 
207
                                                        // Make sure the iframe still exists.
 
208
                                                        iframe.contentWindow && $( iframe ).height( $( iframeDoc.body ).height() );
 
209
                                                };
 
210
 
 
211
                                                if ( MutationObserver ) {
 
212
                                                        new MutationObserver( _.debounce( function() {
 
213
                                                                resize();
 
214
                                                        }, 100 ) )
 
215
                                                        .observe( iframeDoc.body, {
 
216
                                                                attributes: true,
 
217
                                                                childList: true,
 
218
                                                                subtree: true
 
219
                                                        } );
 
220
                                                } else {
 
221
                                                        for ( i = 1; i < 6; i++ ) {
 
222
                                                                setTimeout( resize, i * 700 );
 
223
                                                        }
 
224
                                                }
 
225
 
 
226
                                                if ( importStyles ) {
 
227
                                                        editor.on( 'wp-body-class-change', function() {
 
228
                                                                iframeDoc.body.className = editor.getBody().className;
 
229
                                                        });
 
230
                                                }
 
231
                                        }, 50 );
 
232
                                });
 
233
                        } else {
 
234
                                this.setContent( body );
 
235
                        }
 
236
                },
 
237
                setError: function( message, dashicon ) {
 
238
                        this.setContent(
 
239
                                '<div class="wpview-error">' +
 
240
                                        '<div class="dashicons dashicons-' + ( dashicon ? dashicon : 'no' ) + '"></div>' +
 
241
                                        '<p>' + message + '</p>' +
 
242
                                '</div>'
 
243
                        );
 
244
                },
 
245
                rendered: function( value ) {
 
246
                        var notRendered;
 
247
 
 
248
                        this.getNodes( function( editor, node ) {
 
249
                                if ( value != null ) {
 
250
                                        $( node ).data( 'rendered', value === true );
 
251
                                } else {
 
252
                                        notRendered = notRendered || ! $( node ).data( 'rendered' );
 
253
                                }
 
254
                        } );
 
255
 
 
256
                        return ! notRendered;
 
257
                }
 
258
        } );
 
259
 
 
260
        // take advantage of the Backbone extend method
 
261
        wp.mce.View.extend = Backbone.View.extend;
 
262
 
 
263
        /**
 
264
         * wp.mce.views
 
265
         *
 
266
         * A set of utilities that simplifies adding custom UI within a TinyMCE editor.
 
267
         * At its core, it serves as a series of converters, transforming text to a
 
268
         * custom UI, and back again.
 
269
         */
 
270
        wp.mce.views = {
 
271
 
 
272
                /**
 
273
                 * wp.mce.views.register( type, view )
 
274
                 *
 
275
                 * Registers a new TinyMCE view.
 
276
                 *
 
277
                 * @param type
 
278
                 * @param constructor
 
279
                 *
 
280
                 */
 
281
                register: function( type, constructor ) {
 
282
                        var defaultConstructor = {
 
283
                                        type: type,
 
284
                                        View: {},
 
285
                                        toView: function( content ) {
 
286
                                                var match = wp.shortcode.next( this.type, content );
 
287
 
 
288
                                                if ( ! match ) {
 
289
                                                        return;
 
290
                                                }
 
291
 
 
292
                                                return {
 
293
                                                        index: match.index,
 
294
                                                        content: match.content,
 
295
                                                        options: {
 
296
                                                                shortcode: match.shortcode
 
297
                                                        }
 
298
                                                };
 
299
                                        }
 
300
                                };
 
301
 
 
302
                        constructor = _.defaults( constructor, defaultConstructor );
 
303
                        constructor.View = wp.mce.View.extend( constructor.View );
 
304
 
 
305
                        views[ type ] = constructor;
 
306
                },
 
307
 
 
308
                /**
 
309
                 * wp.mce.views.get( id )
 
310
                 *
 
311
                 * Returns a TinyMCE view constructor.
 
312
                 *
 
313
                 * @param type
 
314
                 */
 
315
                get: function( type ) {
 
316
                        return views[ type ];
 
317
                },
 
318
 
 
319
                /**
 
320
                 * wp.mce.views.unregister( type )
 
321
                 *
 
322
                 * Unregisters a TinyMCE view.
 
323
                 *
 
324
                 * @param type
 
325
                 */
 
326
                unregister: function( type ) {
 
327
                        delete views[ type ];
 
328
                },
 
329
 
 
330
                /**
 
331
                 * wp.mce.views.unbind( editor )
 
332
                 *
 
333
                 * The editor DOM is being rebuilt, run cleanup.
 
334
                 */
 
335
                unbind: function() {
 
336
                        _.each( instances, function( instance ) {
 
337
                                instance.unbind();
 
338
                        } );
 
339
                },
 
340
 
 
341
                /**
 
342
                 * toViews( content )
 
343
                 * Scans a `content` string for each view's pattern, replacing any
 
344
                 * matches with wrapper elements, and creates a new instance for
 
345
                 * every match, which triggers the related data to be fetched.
 
346
                 *
 
347
                 * @param content
 
348
                 */
 
349
                toViews: function( content ) {
 
350
                        var pieces = [ { content: content } ],
 
351
                                current;
 
352
 
 
353
                        _.each( views, function( view, viewType ) {
 
354
                                current = pieces.slice();
 
355
                                pieces  = [];
 
356
 
 
357
                                _.each( current, function( piece ) {
 
358
                                        var remaining = piece.content,
 
359
                                                result;
 
360
 
 
361
                                        // Ignore processed pieces, but retain their location.
 
362
                                        if ( piece.processed ) {
 
363
                                                pieces.push( piece );
 
364
                                                return;
 
365
                                        }
 
366
 
 
367
                                        // Iterate through the string progressively matching views
 
368
                                        // and slicing the string as we go.
 
369
                                        while ( remaining && (result = view.toView( remaining )) ) {
 
370
                                                // Any text before the match becomes an unprocessed piece.
 
371
                                                if ( result.index ) {
 
372
                                                        pieces.push({ content: remaining.substring( 0, result.index ) });
 
373
                                                }
 
374
 
 
375
                                                // Add the processed piece for the match.
 
376
                                                pieces.push({
 
377
                                                        content: wp.mce.views.toView( viewType, result.content, result.options ),
 
378
                                                        processed: true
 
379
                                                });
 
380
 
 
381
                                                // Update the remaining content.
 
382
                                                remaining = remaining.slice( result.index + result.content.length );
 
383
                                        }
 
384
 
 
385
                                        // There are no additional matches. If any content remains,
 
386
                                        // add it as an unprocessed piece.
 
387
                                        if ( remaining ) {
 
388
                                                pieces.push({ content: remaining });
 
389
                                        }
 
390
                                });
 
391
                        });
 
392
 
 
393
                        return _.pluck( pieces, 'content' ).join('');
 
394
                },
 
395
 
 
396
                /**
 
397
                 * Create a placeholder for a particular view type
 
398
                 *
 
399
                 * @param viewType
 
400
                 * @param text
 
401
                 * @param options
 
402
                 *
 
403
                 */
 
404
                toView: function( viewType, text, options ) {
 
405
                        var view = wp.mce.views.get( viewType ),
 
406
                                encodedText = window.encodeURIComponent( text ),
 
407
                                instance, viewOptions;
 
408
 
 
409
 
 
410
                        if ( ! view ) {
 
411
                                return text;
 
412
                        }
 
413
 
 
414
                        if ( ! wp.mce.views.getInstance( encodedText ) ) {
 
415
                                viewOptions = options;
 
416
                                viewOptions.type = viewType;
 
417
                                viewOptions.encodedText = encodedText;
 
418
                                instance = new view.View( viewOptions );
 
419
                                instances[ encodedText ] = instance;
 
420
                        }
 
421
 
 
422
                        return wp.html.string({
 
423
                                tag: 'div',
 
424
 
 
425
                                attrs: {
 
426
                                        'class': 'wpview-wrap',
 
427
                                        'data-wpview-text': encodedText,
 
428
                                        'data-wpview-type': viewType
 
429
                                },
 
430
 
 
431
                                content: '\u00a0'
 
432
                        });
 
433
                },
 
434
 
 
435
                /**
 
436
                 * Refresh views after an update is made
 
437
                 *
 
438
                 * @param view {object} being refreshed
 
439
                 * @param text {string} textual representation of the view
 
440
                 */
 
441
                refreshView: function( view, text ) {
 
442
                        var encodedText = window.encodeURIComponent( text ),
 
443
                                viewOptions,
 
444
                                result, instance;
 
445
 
 
446
                        instance = wp.mce.views.getInstance( encodedText );
 
447
 
 
448
                        if ( ! instance ) {
 
449
                                result = view.toView( text );
 
450
                                viewOptions = result.options;
 
451
                                viewOptions.type = view.type;
 
452
                                viewOptions.encodedText = encodedText;
 
453
                                instance = new view.View( viewOptions );
 
454
                                instances[ encodedText ] = instance;
 
455
                        }
 
456
 
 
457
                        instance.render();
 
458
                },
 
459
 
 
460
                getInstance: function( encodedText ) {
 
461
                        return instances[ encodedText ];
 
462
                },
 
463
 
 
464
                /**
 
465
                 * render( scope )
 
466
                 *
 
467
                 * Renders any view instances inside a DOM node `scope`.
 
468
                 *
 
469
                 * View instances are detected by the presence of wrapper elements.
 
470
                 * To generate wrapper elements, pass your content through
 
471
                 * `wp.mce.view.toViews( content )`.
 
472
                 */
 
473
                render: function( force ) {
 
474
                        _.each( instances, function( instance ) {
 
475
                                instance.render( force );
 
476
                        } );
 
477
                },
 
478
 
 
479
                edit: function( node ) {
 
480
                        var viewType = $( node ).data('wpview-type'),
 
481
                                view = wp.mce.views.get( viewType );
 
482
 
 
483
                        if ( view ) {
 
484
                                view.edit( node );
 
485
                        }
 
486
                }
 
487
        };
 
488
 
 
489
        wp.mce.views.register( 'gallery', {
 
490
                View: {
 
491
                        template: media.template( 'editor-gallery' ),
 
492
 
 
493
                        // The fallback post ID to use as a parent for galleries that don't
 
494
                        // specify the `ids` or `include` parameters.
 
495
                        //
 
496
                        // Uses the hidden input on the edit posts page by default.
 
497
                        postID: $('#post_ID').val(),
 
498
 
 
499
                        initialize: function( options ) {
 
500
                                this.shortcode = options.shortcode;
 
501
                                this.fetch();
 
502
                        },
 
503
 
 
504
                        fetch: function() {
 
505
                                var self = this;
 
506
 
 
507
                                this.attachments = wp.media.gallery.attachments( this.shortcode, this.postID );
 
508
                                this.dfd = this.attachments.more().done( function() {
 
509
                                        self.render( true );
 
510
                                } );
 
511
                        },
 
512
 
 
513
                        getHtml: function() {
 
514
                                var attrs = this.shortcode.attrs.named,
 
515
                                        attachments = false,
 
516
                                        options;
 
517
 
 
518
                                // Don't render errors while still fetching attachments
 
519
                                if ( this.dfd && 'pending' === this.dfd.state() && ! this.attachments.length ) {
 
520
                                        return '';
 
521
                                }
 
522
 
 
523
                                if ( this.attachments.length ) {
 
524
                                        attachments = this.attachments.toJSON();
 
525
 
 
526
                                        _.each( attachments, function( attachment ) {
 
527
                                                if ( attachment.sizes ) {
 
528
                                                        if ( attachment.sizes.thumbnail ) {
 
529
                                                                attachment.thumbnail = attachment.sizes.thumbnail;
 
530
                                                        } else if ( attachment.sizes.full ) {
 
531
                                                                attachment.thumbnail = attachment.sizes.full;
 
532
                                                        }
 
533
                                                }
 
534
                                        } );
 
535
                                }
 
536
 
 
537
                                options = {
 
538
                                        attachments: attachments,
 
539
                                        columns: attrs.columns ? parseInt( attrs.columns, 10 ) : wp.media.galleryDefaults.columns
 
540
                                };
 
541
 
 
542
                                return this.template( options );
 
543
                        }
 
544
                },
 
545
 
 
546
                edit: function( node ) {
 
547
                        var gallery = wp.media.gallery,
 
548
                                self = this,
 
549
                                frame, data;
 
550
 
 
551
                        data = window.decodeURIComponent( $( node ).attr('data-wpview-text') );
 
552
                        frame = gallery.edit( data );
 
553
 
 
554
                        frame.state('gallery-edit').on( 'update', function( selection ) {
 
555
                                var shortcode = gallery.shortcode( selection ).string();
 
556
                                $( node ).attr( 'data-wpview-text', window.encodeURIComponent( shortcode ) );
 
557
                                wp.mce.views.refreshView( self, shortcode );
 
558
                        });
 
559
 
 
560
                        frame.on( 'close', function() {
 
561
                                frame.detach();
 
562
                        });
 
563
                }
 
564
        } );
 
565
 
 
566
        /**
 
567
         * These are base methods that are shared by the audio and video shortcode's MCE controller.
 
568
         *
 
569
         * @mixin
 
570
         */
 
571
        wp.mce.av = {
 
572
                View: {
 
573
                        overlay: true,
 
574
 
 
575
                        action: 'parse-media-shortcode',
 
576
 
 
577
                        initialize: function( options ) {
 
578
                                var self = this;
 
579
 
 
580
                                this.shortcode = options.shortcode;
 
581
 
 
582
                                _.bindAll( this, 'setIframes', 'setNodes', 'fetch', 'stopPlayers' );
 
583
                                $( this ).on( 'ready', this.setNodes );
 
584
 
 
585
                                $( document ).on( 'media:edit', this.stopPlayers );
 
586
 
 
587
                                this.fetch();
 
588
 
 
589
                                this.getEditors( function( editor ) {
 
590
                                        editor.on( 'hide', self.stopPlayers );
 
591
                                });
 
592
                        },
 
593
 
 
594
                        setNodes: function () {
 
595
                                if ( this.parsed ) {
 
596
                                        this.setIframes( this.parsed.head, this.parsed.body );
 
597
                                } else {
 
598
                                        this.fail();
 
599
                                }
 
600
                        },
 
601
 
 
602
                        fetch: function () {
 
603
                                var self = this;
 
604
 
 
605
                                wp.ajax.send( this.action, {
 
606
                                        data: {
 
607
                                                post_ID: $( '#post_ID' ).val() || 0,
 
608
                                                type: this.shortcode.tag,
 
609
                                                shortcode: this.shortcode.string()
 
610
                                        }
 
611
                                } )
 
612
                                .done( function( response ) {
 
613
                                        if ( response ) {
 
614
                                                self.parsed = response;
 
615
                                                self.setIframes( response.head, response.body );
 
616
                                        } else {
 
617
                                                self.fail( true );
 
618
                                        }
 
619
                                } )
 
620
                                .fail( function( response ) {
 
621
                                        self.fail( response || true );
 
622
                                } );
 
623
                        },
 
624
 
 
625
                        fail: function( error ) {
 
626
                                if ( ! this.error ) {
 
627
                                        if ( error ) {
 
628
                                                this.error = error;
 
629
                                        } else {
 
630
                                                return;
 
631
                                        }
 
632
                                }
 
633
 
 
634
                                if ( this.error.message ) {
 
635
                                        if ( ( this.error.type === 'not-embeddable' && this.type === 'embed' ) || this.error.type === 'not-ssl' ||
 
636
                                                this.error.type === 'no-items' ) {
 
637
 
 
638
                                                this.setError( this.error.message, 'admin-media' );
 
639
                                        } else {
 
640
                                                this.setContent( '<p>' + this.original + '</p>', 'replace' );
 
641
                                        }
 
642
                                } else if ( this.error.statusText ) {
 
643
                                        this.setError( this.error.statusText, 'admin-media' );
 
644
                                } else if ( this.original ) {
 
645
                                        this.setContent( '<p>' + this.original + '</p>', 'replace' );
 
646
                                }
 
647
                        },
 
648
 
 
649
                        stopPlayers: function( remove ) {
 
650
                                var rem = remove === 'remove';
 
651
 
 
652
                                this.getNodes( function( editor, node, content ) {
 
653
                                        var p, win,
 
654
                                                iframe = $( 'iframe.wpview-sandbox', content ).get(0);
 
655
 
 
656
                                        if ( iframe && ( win = iframe.contentWindow ) && win.mejs ) {
 
657
                                                // Sometimes ME.js may show a "Download File" placeholder and player.remove() doesn't exist there.
 
658
                                                try {
 
659
                                                        for ( p in win.mejs.players ) {
 
660
                                                                win.mejs.players[p].pause();
 
661
 
 
662
                                                                if ( rem ) {
 
663
                                                                        win.mejs.players[p].remove();
 
664
                                                                }
 
665
                                                        }
 
666
                                                } catch( er ) {}
 
667
                                        }
 
668
                                });
 
669
                        },
 
670
 
 
671
                        unbind: function() {
 
672
                                this.stopPlayers( 'remove' );
 
673
                        }
 
674
                },
 
675
 
 
676
                /**
 
677
                 * Called when a TinyMCE view is clicked for editing.
 
678
                 * - Parses the shortcode out of the element's data attribute
 
679
                 * - Calls the `edit` method on the shortcode model
 
680
                 * - Launches the model window
 
681
                 * - Bind's an `update` callback which updates the element's data attribute
 
682
                 *   re-renders the view
 
683
                 *
 
684
                 * @param {HTMLElement} node
 
685
                 */
 
686
                edit: function( node ) {
 
687
                        var media = wp.media[ this.type ],
 
688
                                self = this,
 
689
                                frame, data, callback;
 
690
 
 
691
                        $( document ).trigger( 'media:edit' );
 
692
 
 
693
                        data = window.decodeURIComponent( $( node ).attr('data-wpview-text') );
 
694
                        frame = media.edit( data );
 
695
                        frame.on( 'close', function() {
 
696
                                frame.detach();
 
697
                        } );
 
698
 
 
699
                        callback = function( selection ) {
 
700
                                var shortcode = wp.media[ self.type ].shortcode( selection ).string();
 
701
                                $( node ).attr( 'data-wpview-text', window.encodeURIComponent( shortcode ) );
 
702
                                wp.mce.views.refreshView( self, shortcode );
 
703
                                frame.detach();
 
704
                        };
 
705
                        if ( _.isArray( self.state ) ) {
 
706
                                _.each( self.state, function (state) {
 
707
                                        frame.state( state ).on( 'update', callback );
 
708
                                } );
 
709
                        } else {
 
710
                                frame.state( self.state ).on( 'update', callback );
 
711
                        }
 
712
                        frame.open();
 
713
                }
 
714
        };
 
715
 
 
716
        /**
 
717
         * TinyMCE handler for the video shortcode
 
718
         *
 
719
         * @mixes wp.mce.av
 
720
         */
 
721
        wp.mce.views.register( 'video', _.extend( {}, wp.mce.av, {
 
722
                state: 'video-details'
 
723
        } ) );
 
724
 
 
725
        /**
 
726
         * TinyMCE handler for the audio shortcode
 
727
         *
 
728
         * @mixes wp.mce.av
 
729
         */
 
730
        wp.mce.views.register( 'audio', _.extend( {}, wp.mce.av, {
 
731
                state: 'audio-details'
 
732
        } ) );
 
733
 
 
734
        /**
 
735
         * TinyMCE handler for the playlist shortcode
 
736
         *
 
737
         * @mixes wp.mce.av
 
738
         */
 
739
        wp.mce.views.register( 'playlist', _.extend( {}, wp.mce.av, {
 
740
                state: [ 'playlist-edit', 'video-playlist-edit' ]
 
741
        } ) );
 
742
 
 
743
        /**
 
744
         * TinyMCE handler for the embed shortcode
 
745
         */
 
746
        wp.mce.embedMixin = {
 
747
                View: _.extend( {}, wp.mce.av.View, {
 
748
                        overlay: true,
 
749
                        action: 'parse-embed',
 
750
                        initialize: function( options ) {
 
751
                                this.content = options.content;
 
752
                                this.original = options.url || options.shortcode.string();
 
753
 
 
754
                                if ( options.url ) {
 
755
                                        this.shortcode = media.embed.shortcode( {
 
756
                                                url: options.url
 
757
                                        } );
 
758
                                } else {
 
759
                                        this.shortcode = options.shortcode;
 
760
                                }
 
761
 
 
762
                                _.bindAll( this, 'setIframes', 'setNodes', 'fetch' );
 
763
                                $( this ).on( 'ready', this.setNodes );
 
764
 
 
765
                                this.fetch();
 
766
                        }
 
767
                } ),
 
768
                edit: function( node ) {
 
769
                        var embed = media.embed,
 
770
                                self = this,
 
771
                                frame,
 
772
                                data,
 
773
                                isURL = 'embedURL' === this.type;
 
774
 
 
775
                        $( document ).trigger( 'media:edit' );
 
776
 
 
777
                        data = window.decodeURIComponent( $( node ).attr('data-wpview-text') );
 
778
                        frame = embed.edit( data, isURL );
 
779
                        frame.on( 'close', function() {
 
780
                                frame.detach();
 
781
                        } );
 
782
                        frame.state( 'embed' ).props.on( 'change:url', function (model, url) {
 
783
                                if ( ! url ) {
 
784
                                        return;
 
785
                                }
 
786
                                frame.state( 'embed' ).metadata = model.toJSON();
 
787
                        } );
 
788
                        frame.state( 'embed' ).on( 'select', function() {
 
789
                                var shortcode;
 
790
 
 
791
                                if ( isURL ) {
 
792
                                        shortcode = frame.state( 'embed' ).metadata.url;
 
793
                                } else {
 
794
                                        shortcode = embed.shortcode( frame.state( 'embed' ).metadata ).string();
 
795
                                }
 
796
                                $( node ).attr( 'data-wpview-text', window.encodeURIComponent( shortcode ) );
 
797
                                wp.mce.views.refreshView( self, shortcode );
 
798
                                frame.detach();
 
799
                        } );
 
800
                        frame.open();
 
801
                }
 
802
        };
 
803
 
 
804
        wp.mce.views.register( 'embed', _.extend( {}, wp.mce.embedMixin ) );
 
805
 
 
806
        wp.mce.views.register( 'embedURL', _.extend( {}, wp.mce.embedMixin, {
 
807
                toView: function( content ) {
 
808
                        var re = /(?:^|<p>)(https?:\/\/[^\s"]+?)(?:<\/p>\s*|$)/gi,
 
809
                                match = re.exec( tinymce.trim( content ) );
 
810
 
 
811
                        if ( ! match ) {
 
812
                                return;
 
813
                        }
 
814
 
 
815
                        return {
 
816
                                index: match.index,
 
817
                                content: match[0],
 
818
                                options: {
 
819
                                        url: match[1]
 
820
                                }
 
821
                        };
 
822
                }
 
823
        } ) );
 
824
 
 
825
}(jQuery));