]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/js/media-editor.js
Wordpress 4.6
[autoinstalls/wordpress.git] / wp-includes / js / media-editor.js
1 /* global getUserSetting, tinymce, QTags */
2
3 // WordPress, TinyMCE, and Media
4 // -----------------------------
5 (function($, _){
6         /**
7          * Stores the editors' `wp.media.controller.Frame` instances.
8          *
9          * @static
10          */
11         var workflows = {};
12
13         /**
14          * A helper mixin function to avoid truthy and falsey values being
15          *   passed as an input that expects booleans. If key is undefined in the map,
16          *   but has a default value, set it.
17          *
18          * @param {object} attrs Map of props from a shortcode or settings.
19          * @param {string} key The key within the passed map to check for a value.
20          * @returns {mixed|undefined} The original or coerced value of key within attrs
21          */
22         wp.media.coerce = function ( attrs, key ) {
23                 if ( _.isUndefined( attrs[ key ] ) && ! _.isUndefined( this.defaults[ key ] ) ) {
24                         attrs[ key ] = this.defaults[ key ];
25                 } else if ( 'true' === attrs[ key ] ) {
26                         attrs[ key ] = true;
27                 } else if ( 'false' === attrs[ key ] ) {
28                         attrs[ key ] = false;
29                 }
30                 return attrs[ key ];
31         };
32
33         /**
34          * wp.media.string
35          * @namespace
36          */
37         wp.media.string = {
38                 /**
39                  * Joins the `props` and `attachment` objects,
40                  * outputting the proper object format based on the
41                  * attachment's type.
42                  *
43                  * @global wp.media.view.settings
44                  * @global getUserSetting()
45                  *
46                  * @param {Object} [props={}] Attachment details (align, link, size, etc).
47                  * @param {Object} attachment The attachment object, media version of Post.
48                  * @returns {Object} Joined props
49                  */
50                 props: function( props, attachment ) {
51                         var link, linkUrl, size, sizes, fallbacks,
52                                 defaultProps = wp.media.view.settings.defaultProps;
53
54                         // Final fallbacks run after all processing has been completed.
55                         fallbacks = function( props ) {
56                                 // Generate alt fallbacks and strip tags.
57                                 if ( 'image' === props.type && ! props.alt ) {
58                                         if ( props.caption ) {
59                                                 props.alt = props.caption;
60                                         } else if ( props.title !== props.url ) {
61                                                 props.alt = props.title;
62                                         } else {
63                                                 props.alt = '';
64                                         }
65
66                                         props.alt = props.alt.replace( /<\/?[^>]+>/g, '' );
67                                         props.alt = props.alt.replace( /[\r\n]+/g, ' ' );
68                                 }
69
70                                 return props;
71                         };
72
73                         props = props ? _.clone( props ) : {};
74
75                         if ( attachment && attachment.type ) {
76                                 props.type = attachment.type;
77                         }
78
79                         if ( 'image' === props.type ) {
80                                 props = _.defaults( props || {}, {
81                                         align:   defaultProps.align || getUserSetting( 'align', 'none' ),
82                                         size:    defaultProps.size  || getUserSetting( 'imgsize', 'medium' ),
83                                         url:     '',
84                                         classes: []
85                                 });
86                         }
87
88                         // All attachment-specific settings follow.
89                         if ( ! attachment ) {
90                                 return fallbacks( props );
91                         }
92
93                         props.title = props.title || attachment.title;
94
95                         link = props.link || defaultProps.link || getUserSetting( 'urlbutton', 'file' );
96                         if ( 'file' === link || 'embed' === link ) {
97                                 linkUrl = attachment.url;
98                         } else if ( 'post' === link ) {
99                                 linkUrl = attachment.link;
100                         } else if ( 'custom' === link ) {
101                                 linkUrl = props.linkUrl;
102                         }
103                         props.linkUrl = linkUrl || '';
104
105                         // Format properties for images.
106                         if ( 'image' === attachment.type ) {
107                                 props.classes.push( 'wp-image-' + attachment.id );
108
109                                 sizes = attachment.sizes;
110                                 size = sizes && sizes[ props.size ] ? sizes[ props.size ] : attachment;
111
112                                 _.extend( props, _.pick( attachment, 'align', 'caption', 'alt' ), {
113                                         width:     size.width,
114                                         height:    size.height,
115                                         src:       size.url,
116                                         captionId: 'attachment_' + attachment.id
117                                 });
118                         } else if ( 'video' === attachment.type || 'audio' === attachment.type ) {
119                                 _.extend( props, _.pick( attachment, 'title', 'type', 'icon', 'mime' ) );
120                         // Format properties for non-images.
121                         } else {
122                                 props.title = props.title || attachment.filename;
123                                 props.rel = props.rel || 'attachment wp-att-' + attachment.id;
124                         }
125
126                         return fallbacks( props );
127                 },
128                 /**
129                  * Create link markup that is suitable for passing to the editor
130                  *
131                  * @global wp.html.string
132                  *
133                  * @param {Object} props Attachment details (align, link, size, etc).
134                  * @param {Object} attachment The attachment object, media version of Post.
135                  * @returns {string} The link markup
136                  */
137                 link: function( props, attachment ) {
138                         var options;
139
140                         props = wp.media.string.props( props, attachment );
141
142                         options = {
143                                 tag:     'a',
144                                 content: props.title,
145                                 attrs:   {
146                                         href: props.linkUrl
147                                 }
148                         };
149
150                         if ( props.rel ) {
151                                 options.attrs.rel = props.rel;
152                         }
153
154                         return wp.html.string( options );
155                 },
156                 /**
157                  * Create an Audio shortcode string that is suitable for passing to the editor
158                  *
159                  * @param {Object} props Attachment details (align, link, size, etc).
160                  * @param {Object} attachment The attachment object, media version of Post.
161                  * @returns {string} The audio shortcode
162                  */
163                 audio: function( props, attachment ) {
164                         return wp.media.string._audioVideo( 'audio', props, attachment );
165                 },
166                 /**
167                  * Create a Video shortcode string that is suitable for passing to the editor
168                  *
169                  * @param {Object} props Attachment details (align, link, size, etc).
170                  * @param {Object} attachment The attachment object, media version of Post.
171                  * @returns {string} The video shortcode
172                  */
173                 video: function( props, attachment ) {
174                         return wp.media.string._audioVideo( 'video', props, attachment );
175                 },
176                 /**
177                  * Helper function to create a media shortcode string
178                  *
179                  * @access private
180                  *
181                  * @global wp.shortcode
182                  * @global wp.media.view.settings
183                  *
184                  * @param {string} type The shortcode tag name: 'audio' or 'video'.
185                  * @param {Object} props Attachment details (align, link, size, etc).
186                  * @param {Object} attachment The attachment object, media version of Post.
187                  * @returns {string} The media shortcode
188                  */
189                 _audioVideo: function( type, props, attachment ) {
190                         var shortcode, html, extension;
191
192                         props = wp.media.string.props( props, attachment );
193                         if ( props.link !== 'embed' )
194                                 return wp.media.string.link( props );
195
196                         shortcode = {};
197
198                         if ( 'video' === type ) {
199                                 if ( attachment.image && -1 === attachment.image.src.indexOf( attachment.icon ) ) {
200                                         shortcode.poster = attachment.image.src;
201                                 }
202
203                                 if ( attachment.width ) {
204                                         shortcode.width = attachment.width;
205                                 }
206
207                                 if ( attachment.height ) {
208                                         shortcode.height = attachment.height;
209                                 }
210                         }
211
212                         extension = attachment.filename.split('.').pop();
213
214                         if ( _.contains( wp.media.view.settings.embedExts, extension ) ) {
215                                 shortcode[extension] = attachment.url;
216                         } else {
217                                 // Render unsupported audio and video files as links.
218                                 return wp.media.string.link( props );
219                         }
220
221                         html = wp.shortcode.string({
222                                 tag:     type,
223                                 attrs:   shortcode
224                         });
225
226                         return html;
227                 },
228                 /**
229                  * Create image markup, optionally with a link and/or wrapped in a caption shortcode,
230                  *  that is suitable for passing to the editor
231                  *
232                  * @global wp.html
233                  * @global wp.shortcode
234                  *
235                  * @param {Object} props Attachment details (align, link, size, etc).
236                  * @param {Object} attachment The attachment object, media version of Post.
237                  * @returns {string}
238                  */
239                 image: function( props, attachment ) {
240                         var img = {},
241                                 options, classes, shortcode, html;
242
243                         props.type = 'image';
244                         props = wp.media.string.props( props, attachment );
245                         classes = props.classes || [];
246
247                         img.src = ! _.isUndefined( attachment ) ? attachment.url : props.url;
248                         _.extend( img, _.pick( props, 'width', 'height', 'alt' ) );
249
250                         // Only assign the align class to the image if we're not printing
251                         // a caption, since the alignment is sent to the shortcode.
252                         if ( props.align && ! props.caption ) {
253                                 classes.push( 'align' + props.align );
254                         }
255
256                         if ( props.size ) {
257                                 classes.push( 'size-' + props.size );
258                         }
259
260                         img['class'] = _.compact( classes ).join(' ');
261
262                         // Generate `img` tag options.
263                         options = {
264                                 tag:    'img',
265                                 attrs:  img,
266                                 single: true
267                         };
268
269                         // Generate the `a` element options, if they exist.
270                         if ( props.linkUrl ) {
271                                 options = {
272                                         tag:   'a',
273                                         attrs: {
274                                                 href: props.linkUrl
275                                         },
276                                         content: options
277                                 };
278                         }
279
280                         html = wp.html.string( options );
281
282                         // Generate the caption shortcode.
283                         if ( props.caption ) {
284                                 shortcode = {};
285
286                                 if ( img.width ) {
287                                         shortcode.width = img.width;
288                                 }
289
290                                 if ( props.captionId ) {
291                                         shortcode.id = props.captionId;
292                                 }
293
294                                 if ( props.align ) {
295                                         shortcode.align = 'align' + props.align;
296                                 }
297
298                                 html = wp.shortcode.string({
299                                         tag:     'caption',
300                                         attrs:   shortcode,
301                                         content: html + ' ' + props.caption
302                                 });
303                         }
304
305                         return html;
306                 }
307         };
308
309         wp.media.embed = {
310                 coerce : wp.media.coerce,
311
312                 defaults : {
313                         url : '',
314                         width: '',
315                         height: ''
316                 },
317
318                 edit : function( data, isURL ) {
319                         var frame, props = {}, shortcode;
320
321                         if ( isURL ) {
322                                 props.url = data.replace(/<[^>]+>/g, '');
323                         } else {
324                                 shortcode = wp.shortcode.next( 'embed', data ).shortcode;
325
326                                 props = _.defaults( shortcode.attrs.named, this.defaults );
327                                 if ( shortcode.content ) {
328                                         props.url = shortcode.content;
329                                 }
330                         }
331
332                         frame = wp.media({
333                                 frame: 'post',
334                                 state: 'embed',
335                                 metadata: props
336                         });
337
338                         return frame;
339                 },
340
341                 shortcode : function( model ) {
342                         var self = this, content;
343
344                         _.each( this.defaults, function( value, key ) {
345                                 model[ key ] = self.coerce( model, key );
346
347                                 if ( value === model[ key ] ) {
348                                         delete model[ key ];
349                                 }
350                         });
351
352                         content = model.url;
353                         delete model.url;
354
355                         return new wp.shortcode({
356                                 tag: 'embed',
357                                 attrs: model,
358                                 content: content
359                         });
360                 }
361         };
362
363         wp.media.collection = function(attributes) {
364                 var collections = {};
365
366                 return _.extend( {
367                         coerce : wp.media.coerce,
368                         /**
369                          * Retrieve attachments based on the properties of the passed shortcode
370                          *
371                          * @global wp.media.query
372                          *
373                          * @param {wp.shortcode} shortcode An instance of wp.shortcode().
374                          * @returns {wp.media.model.Attachments} A Backbone.Collection containing
375                          *      the media items belonging to a collection.
376                          *      The query[ this.tag ] property is a Backbone.Model
377                          *          containing the 'props' for the collection.
378                          */
379                         attachments: function( shortcode ) {
380                                 var shortcodeString = shortcode.string(),
381                                         result = collections[ shortcodeString ],
382                                         attrs, args, query, others, self = this;
383
384                                 delete collections[ shortcodeString ];
385                                 if ( result ) {
386                                         return result;
387                                 }
388                                 // Fill the default shortcode attributes.
389                                 attrs = _.defaults( shortcode.attrs.named, this.defaults );
390                                 args  = _.pick( attrs, 'orderby', 'order' );
391
392                                 args.type    = this.type;
393                                 args.perPage = -1;
394
395                                 // Mark the `orderby` override attribute.
396                                 if ( undefined !== attrs.orderby ) {
397                                         attrs._orderByField = attrs.orderby;
398                                 }
399
400                                 if ( 'rand' === attrs.orderby ) {
401                                         attrs._orderbyRandom = true;
402                                 }
403
404                                 // Map the `orderby` attribute to the corresponding model property.
405                                 if ( ! attrs.orderby || /^menu_order(?: ID)?$/i.test( attrs.orderby ) ) {
406                                         args.orderby = 'menuOrder';
407                                 }
408
409                                 // Map the `ids` param to the correct query args.
410                                 if ( attrs.ids ) {
411                                         args.post__in = attrs.ids.split(',');
412                                         args.orderby  = 'post__in';
413                                 } else if ( attrs.include ) {
414                                         args.post__in = attrs.include.split(',');
415                                 }
416
417                                 if ( attrs.exclude ) {
418                                         args.post__not_in = attrs.exclude.split(',');
419                                 }
420
421                                 if ( ! args.post__in ) {
422                                         args.uploadedTo = attrs.id;
423                                 }
424
425                                 // Collect the attributes that were not included in `args`.
426                                 others = _.omit( attrs, 'id', 'ids', 'include', 'exclude', 'orderby', 'order' );
427
428                                 _.each( this.defaults, function( value, key ) {
429                                         others[ key ] = self.coerce( others, key );
430                                 });
431
432                                 query = wp.media.query( args );
433                                 query[ this.tag ] = new Backbone.Model( others );
434                                 return query;
435                         },
436                         /**
437                          * Triggered when clicking 'Insert {label}' or 'Update {label}'
438                          *
439                          * @global wp.shortcode
440                          * @global wp.media.model.Attachments
441                          *
442                          * @param {wp.media.model.Attachments} attachments A Backbone.Collection containing
443                          *      the media items belonging to a collection.
444                          *      The query[ this.tag ] property is a Backbone.Model
445                          *          containing the 'props' for the collection.
446                          * @returns {wp.shortcode}
447                          */
448                         shortcode: function( attachments ) {
449                                 var props = attachments.props.toJSON(),
450                                         attrs = _.pick( props, 'orderby', 'order' ),
451                                         shortcode, clone;
452
453                                 if ( attachments.type ) {
454                                         attrs.type = attachments.type;
455                                         delete attachments.type;
456                                 }
457
458                                 if ( attachments[this.tag] ) {
459                                         _.extend( attrs, attachments[this.tag].toJSON() );
460                                 }
461
462                                 // Convert all gallery shortcodes to use the `ids` property.
463                                 // Ignore `post__in` and `post__not_in`; the attachments in
464                                 // the collection will already reflect those properties.
465                                 attrs.ids = attachments.pluck('id');
466
467                                 // Copy the `uploadedTo` post ID.
468                                 if ( props.uploadedTo ) {
469                                         attrs.id = props.uploadedTo;
470                                 }
471                                 // Check if the gallery is randomly ordered.
472                                 delete attrs.orderby;
473
474                                 if ( attrs._orderbyRandom ) {
475                                         attrs.orderby = 'rand';
476                                 } else if ( attrs._orderByField && attrs._orderByField != 'rand' ) {
477                                         attrs.orderby = attrs._orderByField;
478                                 }
479
480                                 delete attrs._orderbyRandom;
481                                 delete attrs._orderByField;
482
483                                 // If the `ids` attribute is set and `orderby` attribute
484                                 // is the default value, clear it for cleaner output.
485                                 if ( attrs.ids && 'post__in' === attrs.orderby ) {
486                                         delete attrs.orderby;
487                                 }
488
489                                 attrs = this.setDefaults( attrs );
490
491                                 shortcode = new wp.shortcode({
492                                         tag:    this.tag,
493                                         attrs:  attrs,
494                                         type:   'single'
495                                 });
496
497                                 // Use a cloned version of the gallery.
498                                 clone = new wp.media.model.Attachments( attachments.models, {
499                                         props: props
500                                 });
501                                 clone[ this.tag ] = attachments[ this.tag ];
502                                 collections[ shortcode.string() ] = clone;
503
504                                 return shortcode;
505                         },
506                         /**
507                          * Triggered when double-clicking a collection shortcode placeholder
508                          *   in the editor
509                          *
510                          * @global wp.shortcode
511                          * @global wp.media.model.Selection
512                          * @global wp.media.view.l10n
513                          *
514                          * @param {string} content Content that is searched for possible
515                          *    shortcode markup matching the passed tag name,
516                          *
517                          * @this wp.media.{prop}
518                          *
519                          * @returns {wp.media.view.MediaFrame.Select} A media workflow.
520                          */
521                         edit: function( content ) {
522                                 var shortcode = wp.shortcode.next( this.tag, content ),
523                                         defaultPostId = this.defaults.id,
524                                         attachments, selection, state;
525
526                                 // Bail if we didn't match the shortcode or all of the content.
527                                 if ( ! shortcode || shortcode.content !== content ) {
528                                         return;
529                                 }
530
531                                 // Ignore the rest of the match object.
532                                 shortcode = shortcode.shortcode;
533
534                                 if ( _.isUndefined( shortcode.get('id') ) && ! _.isUndefined( defaultPostId ) ) {
535                                         shortcode.set( 'id', defaultPostId );
536                                 }
537
538                                 attachments = this.attachments( shortcode );
539
540                                 selection = new wp.media.model.Selection( attachments.models, {
541                                         props:    attachments.props.toJSON(),
542                                         multiple: true
543                                 });
544
545                                 selection[ this.tag ] = attachments[ this.tag ];
546
547                                 // Fetch the query's attachments, and then break ties from the
548                                 // query to allow for sorting.
549                                 selection.more().done( function() {
550                                         // Break ties with the query.
551                                         selection.props.set({ query: false });
552                                         selection.unmirror();
553                                         selection.props.unset('orderby');
554                                 });
555
556                                 // Destroy the previous gallery frame.
557                                 if ( this.frame ) {
558                                         this.frame.dispose();
559                                 }
560
561                                 if ( shortcode.attrs.named.type && 'video' === shortcode.attrs.named.type ) {
562                                         state = 'video-' + this.tag + '-edit';
563                                 } else {
564                                         state = this.tag + '-edit';
565                                 }
566
567                                 // Store the current frame.
568                                 this.frame = wp.media({
569                                         frame:     'post',
570                                         state:     state,
571                                         title:     this.editTitle,
572                                         editing:   true,
573                                         multiple:  true,
574                                         selection: selection
575                                 }).open();
576
577                                 return this.frame;
578                         },
579
580                         setDefaults: function( attrs ) {
581                                 var self = this;
582                                 // Remove default attributes from the shortcode.
583                                 _.each( this.defaults, function( value, key ) {
584                                         attrs[ key ] = self.coerce( attrs, key );
585                                         if ( value === attrs[ key ] ) {
586                                                 delete attrs[ key ];
587                                         }
588                                 });
589
590                                 return attrs;
591                         }
592                 }, attributes );
593         };
594
595         wp.media._galleryDefaults = {
596                 itemtag: 'dl',
597                 icontag: 'dt',
598                 captiontag: 'dd',
599                 columns: '3',
600                 link: 'post',
601                 size: 'thumbnail',
602                 order: 'ASC',
603                 id: wp.media.view.settings.post && wp.media.view.settings.post.id,
604                 orderby : 'menu_order ID'
605         };
606
607         if ( wp.media.view.settings.galleryDefaults ) {
608                 wp.media.galleryDefaults = _.extend( {}, wp.media._galleryDefaults, wp.media.view.settings.galleryDefaults );
609         } else {
610                 wp.media.galleryDefaults = wp.media._galleryDefaults;
611         }
612
613         wp.media.gallery = new wp.media.collection({
614                 tag: 'gallery',
615                 type : 'image',
616                 editTitle : wp.media.view.l10n.editGalleryTitle,
617                 defaults : wp.media.galleryDefaults,
618
619                 setDefaults: function( attrs ) {
620                         var self = this, changed = ! _.isEqual( wp.media.galleryDefaults, wp.media._galleryDefaults );
621                         _.each( this.defaults, function( value, key ) {
622                                 attrs[ key ] = self.coerce( attrs, key );
623                                 if ( value === attrs[ key ] && ( ! changed || value === wp.media._galleryDefaults[ key ] ) ) {
624                                         delete attrs[ key ];
625                                 }
626                         } );
627                         return attrs;
628                 }
629         });
630
631         /**
632          * wp.media.featuredImage
633          * @namespace
634          */
635         wp.media.featuredImage = {
636                 /**
637                  * Get the featured image post ID
638                  *
639                  * @global wp.media.view.settings
640                  *
641                  * @returns {wp.media.view.settings.post.featuredImageId|number}
642                  */
643                 get: function() {
644                         return wp.media.view.settings.post.featuredImageId;
645                 },
646                 /**
647                  * Set the featured image id, save the post thumbnail data and
648                  * set the HTML in the post meta box to the new featured image.
649                  *
650                  * @global wp.media.view.settings
651                  * @global wp.media.post
652                  *
653                  * @param {number} id The post ID of the featured image, or -1 to unset it.
654                  */
655                 set: function( id ) {
656                         var settings = wp.media.view.settings;
657
658                         settings.post.featuredImageId = id;
659
660                         wp.media.post( 'get-post-thumbnail-html', {
661                                 post_id:      settings.post.id,
662                                 thumbnail_id: settings.post.featuredImageId,
663                                 _wpnonce:     settings.post.nonce
664                         }).done( function( html ) {
665                                 if ( html == '0' ) {
666                                         window.alert( window.setPostThumbnailL10n.error );
667                                         return;
668                                 }
669                                 $( '.inside', '#postimagediv' ).html( html );
670                         });
671                 },
672                 /**
673                  * Remove the featured image id, save the post thumbnail data and
674                  * set the HTML in the post meta box to no featured image.
675                  */
676                 remove: function() {
677                         wp.media.featuredImage.set( -1 );
678                 },
679                 /**
680                  * The Featured Image workflow
681                  *
682                  * @global wp.media.controller.FeaturedImage
683                  * @global wp.media.view.l10n
684                  *
685                  * @this wp.media.featuredImage
686                  *
687                  * @returns {wp.media.view.MediaFrame.Select} A media workflow.
688                  */
689                 frame: function() {
690                         if ( this._frame ) {
691                                 wp.media.frame = this._frame;
692                                 return this._frame;
693                         }
694
695                         this._frame = wp.media({
696                                 state: 'featured-image',
697                                 states: [ new wp.media.controller.FeaturedImage() , new wp.media.controller.EditImage() ]
698                         });
699
700                         this._frame.on( 'toolbar:create:featured-image', function( toolbar ) {
701                                 /**
702                                  * @this wp.media.view.MediaFrame.Select
703                                  */
704                                 this.createSelectToolbar( toolbar, {
705                                         text: wp.media.view.l10n.setFeaturedImage
706                                 });
707                         }, this._frame );
708
709                         this._frame.on( 'content:render:edit-image', function() {
710                                 var selection = this.state('featured-image').get('selection'),
711                                         view = new wp.media.view.EditImage( { model: selection.single(), controller: this } ).render();
712
713                                 this.content.set( view );
714
715                                 // after bringing in the frame, load the actual editor via an ajax call
716                                 view.loadEditor();
717
718                         }, this._frame );
719
720                         this._frame.state('featured-image').on( 'select', this.select );
721                         return this._frame;
722                 },
723                 /**
724                  * 'select' callback for Featured Image workflow, triggered when
725                  *  the 'Set Featured Image' button is clicked in the media modal.
726                  *
727                  * @global wp.media.view.settings
728                  *
729                  * @this wp.media.controller.FeaturedImage
730                  */
731                 select: function() {
732                         var selection = this.get('selection').single();
733
734                         if ( ! wp.media.view.settings.post.featuredImageId ) {
735                                 return;
736                         }
737
738                         wp.media.featuredImage.set( selection ? selection.id : -1 );
739                 },
740                 /**
741                  * Open the content media manager to the 'featured image' tab when
742                  * the post thumbnail is clicked.
743                  *
744                  * Update the featured image id when the 'remove' link is clicked.
745                  *
746                  * @global wp.media.view.settings
747                  */
748                 init: function() {
749                         $('#postimagediv').on( 'click', '#set-post-thumbnail', function( event ) {
750                                 event.preventDefault();
751                                 // Stop propagation to prevent thickbox from activating.
752                                 event.stopPropagation();
753
754                                 wp.media.featuredImage.frame().open();
755                         }).on( 'click', '#remove-post-thumbnail', function() {
756                                 wp.media.featuredImage.remove();
757                                 return false;
758                         });
759                 }
760         };
761
762         $( wp.media.featuredImage.init );
763
764         /**
765          * wp.media.editor
766          * @namespace
767          */
768         wp.media.editor = {
769                 /**
770                  * Send content to the editor
771                  *
772                  * @global tinymce
773                  * @global QTags
774                  * @global wpActiveEditor
775                  * @global tb_remove() - Possibly overloaded by legacy plugins
776                  *
777                  * @param {string} html Content to send to the editor
778                  */
779                 insert: function( html ) {
780                         var editor, wpActiveEditor,
781                                 hasTinymce = ! _.isUndefined( window.tinymce ),
782                                 hasQuicktags = ! _.isUndefined( window.QTags );
783
784                         if ( this.activeEditor ) {
785                                 wpActiveEditor = window.wpActiveEditor = this.activeEditor;
786                         } else {
787                                 wpActiveEditor = window.wpActiveEditor;
788                         }
789
790                         // Delegate to the global `send_to_editor` if it exists.
791                         // This attempts to play nice with any themes/plugins that have
792                         // overridden the insert functionality.
793                         if ( window.send_to_editor ) {
794                                 return window.send_to_editor.apply( this, arguments );
795                         }
796
797                         if ( ! wpActiveEditor ) {
798                                 if ( hasTinymce && tinymce.activeEditor ) {
799                                         editor = tinymce.activeEditor;
800                                         wpActiveEditor = window.wpActiveEditor = editor.id;
801                                 } else if ( ! hasQuicktags ) {
802                                         return false;
803                                 }
804                         } else if ( hasTinymce ) {
805                                 editor = tinymce.get( wpActiveEditor );
806                         }
807
808                         if ( editor && ! editor.isHidden() ) {
809                                 editor.execCommand( 'mceInsertContent', false, html );
810                         } else if ( hasQuicktags ) {
811                                 QTags.insertContent( html );
812                         } else {
813                                 document.getElementById( wpActiveEditor ).value += html;
814                         }
815
816                         // If the old thickbox remove function exists, call it in case
817                         // a theme/plugin overloaded it.
818                         if ( window.tb_remove ) {
819                                 try { window.tb_remove(); } catch( e ) {}
820                         }
821                 },
822
823                 /**
824                  * Setup 'workflow' and add to the 'workflows' cache. 'open' can
825                  *  subsequently be called upon it.
826                  *
827                  * @global wp.media.view.l10n
828                  *
829                  * @param {string} id A slug used to identify the workflow.
830                  * @param {Object} [options={}]
831                  *
832                  * @this wp.media.editor
833                  *
834                  * @returns {wp.media.view.MediaFrame.Select} A media workflow.
835                  */
836                 add: function( id, options ) {
837                         var workflow = this.get( id );
838
839                         // only add once: if exists return existing
840                         if ( workflow ) {
841                                 return workflow;
842                         }
843
844                         workflow = workflows[ id ] = wp.media( _.defaults( options || {}, {
845                                 frame:    'post',
846                                 state:    'insert',
847                                 title:    wp.media.view.l10n.addMedia,
848                                 multiple: true
849                         } ) );
850
851                         workflow.on( 'insert', function( selection ) {
852                                 var state = workflow.state();
853
854                                 selection = selection || state.get('selection');
855
856                                 if ( ! selection )
857                                         return;
858
859                                 $.when.apply( $, selection.map( function( attachment ) {
860                                         var display = state.display( attachment ).toJSON();
861                                         /**
862                                          * @this wp.media.editor
863                                          */
864                                         return this.send.attachment( display, attachment.toJSON() );
865                                 }, this ) ).done( function() {
866                                         wp.media.editor.insert( _.toArray( arguments ).join('\n\n') );
867                                 });
868                         }, this );
869
870                         workflow.state('gallery-edit').on( 'update', function( selection ) {
871                                 /**
872                                  * @this wp.media.editor
873                                  */
874                                 this.insert( wp.media.gallery.shortcode( selection ).string() );
875                         }, this );
876
877                         workflow.state('playlist-edit').on( 'update', function( selection ) {
878                                 /**
879                                  * @this wp.media.editor
880                                  */
881                                 this.insert( wp.media.playlist.shortcode( selection ).string() );
882                         }, this );
883
884                         workflow.state('video-playlist-edit').on( 'update', function( selection ) {
885                                 /**
886                                  * @this wp.media.editor
887                                  */
888                                 this.insert( wp.media.playlist.shortcode( selection ).string() );
889                         }, this );
890
891                         workflow.state('embed').on( 'select', function() {
892                                 /**
893                                  * @this wp.media.editor
894                                  */
895                                 var state = workflow.state(),
896                                         type = state.get('type'),
897                                         embed = state.props.toJSON();
898
899                                 embed.url = embed.url || '';
900
901                                 if ( 'link' === type ) {
902                                         _.defaults( embed, {
903                                                 linkText: embed.url,
904                                                 linkUrl: embed.url
905                                         });
906
907                                         this.send.link( embed ).done( function( resp ) {
908                                                 wp.media.editor.insert( resp );
909                                         });
910
911                                 } else if ( 'image' === type ) {
912                                         _.defaults( embed, {
913                                                 title:   embed.url,
914                                                 linkUrl: '',
915                                                 align:   'none',
916                                                 link:    'none'
917                                         });
918
919                                         if ( 'none' === embed.link ) {
920                                                 embed.linkUrl = '';
921                                         } else if ( 'file' === embed.link ) {
922                                                 embed.linkUrl = embed.url;
923                                         }
924
925                                         this.insert( wp.media.string.image( embed ) );
926                                 }
927                         }, this );
928
929                         workflow.state('featured-image').on( 'select', wp.media.featuredImage.select );
930                         workflow.setState( workflow.options.state );
931                         return workflow;
932                 },
933                 /**
934                  * Determines the proper current workflow id
935                  *
936                  * @global wpActiveEditor
937                  * @global tinymce
938                  *
939                  * @param {string} [id=''] A slug used to identify the workflow.
940                  *
941                  * @returns {wpActiveEditor|string|tinymce.activeEditor.id}
942                  */
943                 id: function( id ) {
944                         if ( id ) {
945                                 return id;
946                         }
947
948                         // If an empty `id` is provided, default to `wpActiveEditor`.
949                         id = window.wpActiveEditor;
950
951                         // If that doesn't work, fall back to `tinymce.activeEditor.id`.
952                         if ( ! id && ! _.isUndefined( window.tinymce ) && tinymce.activeEditor ) {
953                                 id = tinymce.activeEditor.id;
954                         }
955
956                         // Last but not least, fall back to the empty string.
957                         id = id || '';
958                         return id;
959                 },
960                 /**
961                  * Return the workflow specified by id
962                  *
963                  * @param {string} id A slug used to identify the workflow.
964                  *
965                  * @this wp.media.editor
966                  *
967                  * @returns {wp.media.view.MediaFrame} A media workflow.
968                  */
969                 get: function( id ) {
970                         id = this.id( id );
971                         return workflows[ id ];
972                 },
973                 /**
974                  * Remove the workflow represented by id from the workflow cache
975                  *
976                  * @param {string} id A slug used to identify the workflow.
977                  *
978                  * @this wp.media.editor
979                  */
980                 remove: function( id ) {
981                         id = this.id( id );
982                         delete workflows[ id ];
983                 },
984                 /**
985                  * @namespace
986                  */
987                 send: {
988                         /**
989                          * Called when sending an attachment to the editor
990                          *   from the medial modal.
991                          *
992                          * @global wp.media.view.settings
993                          * @global wp.media.post
994                          *
995                          * @param {Object} props Attachment details (align, link, size, etc).
996                          * @param {Object} attachment The attachment object, media version of Post.
997                          * @returns {Promise}
998                          */
999                         attachment: function( props, attachment ) {
1000                                 var caption = attachment.caption,
1001                                         options, html;
1002
1003                                 // If captions are disabled, clear the caption.
1004                                 if ( ! wp.media.view.settings.captions ) {
1005                                         delete attachment.caption;
1006                                 }
1007
1008                                 props = wp.media.string.props( props, attachment );
1009
1010                                 options = {
1011                                         id:           attachment.id,
1012                                         post_content: attachment.description,
1013                                         post_excerpt: caption
1014                                 };
1015
1016                                 if ( props.linkUrl ) {
1017                                         options.url = props.linkUrl;
1018                                 }
1019
1020                                 if ( 'image' === attachment.type ) {
1021                                         html = wp.media.string.image( props );
1022
1023                                         _.each({
1024                                                 align: 'align',
1025                                                 size:  'image-size',
1026                                                 alt:   'image_alt'
1027                                         }, function( option, prop ) {
1028                                                 if ( props[ prop ] )
1029                                                         options[ option ] = props[ prop ];
1030                                         });
1031                                 } else if ( 'video' === attachment.type ) {
1032                                         html = wp.media.string.video( props, attachment );
1033                                 } else if ( 'audio' === attachment.type ) {
1034                                         html = wp.media.string.audio( props, attachment );
1035                                 } else {
1036                                         html = wp.media.string.link( props );
1037                                         options.post_title = props.title;
1038                                 }
1039
1040                                 return wp.media.post( 'send-attachment-to-editor', {
1041                                         nonce:      wp.media.view.settings.nonce.sendToEditor,
1042                                         attachment: options,
1043                                         html:       html,
1044                                         post_id:    wp.media.view.settings.post.id
1045                                 });
1046                         },
1047                         /**
1048                          * Called when 'Insert From URL' source is not an image. Example: YouTube url.
1049                          *
1050                          * @global wp.media.view.settings
1051                          *
1052                          * @param {Object} embed
1053                          * @returns {Promise}
1054                          */
1055                         link: function( embed ) {
1056                                 return wp.media.post( 'send-link-to-editor', {
1057                                         nonce:     wp.media.view.settings.nonce.sendToEditor,
1058                                         src:       embed.linkUrl,
1059                                         link_text: embed.linkText,
1060                                         html:      wp.media.string.link( embed ),
1061                                         post_id:   wp.media.view.settings.post.id
1062                                 });
1063                         }
1064                 },
1065                 /**
1066                  * Open a workflow
1067                  *
1068                  * @param {string} [id=undefined] Optional. A slug used to identify the workflow.
1069                  * @param {Object} [options={}]
1070                  *
1071                  * @this wp.media.editor
1072                  *
1073                  * @returns {wp.media.view.MediaFrame}
1074                  */
1075                 open: function( id, options ) {
1076                         var workflow;
1077
1078                         options = options || {};
1079
1080                         id = this.id( id );
1081                         this.activeEditor = id;
1082
1083                         workflow = this.get( id );
1084
1085                         // Redo workflow if state has changed
1086                         if ( ! workflow || ( workflow.options && options.state !== workflow.options.state ) ) {
1087                                 workflow = this.add( id, options );
1088                         }
1089
1090                         wp.media.frame = workflow;
1091
1092                         return workflow.open();
1093                 },
1094
1095                 /**
1096                  * Bind click event for .insert-media using event delegation
1097                  *
1098                  * @global wp.media.view.l10n
1099                  */
1100                 init: function() {
1101                         $(document.body)
1102                                 .on( 'click.add-media-button', '.insert-media', function( event ) {
1103                                         var elem = $( event.currentTarget ),
1104                                                 editor = elem.data('editor'),
1105                                                 options = {
1106                                                         frame:    'post',
1107                                                         state:    'insert',
1108                                                         title:    wp.media.view.l10n.addMedia,
1109                                                         multiple: true
1110                                                 };
1111
1112                                         event.preventDefault();
1113
1114                                         // Remove focus from the `.insert-media` button.
1115                                         // Prevents Opera from showing the outline of the button
1116                                         // above the modal.
1117                                         //
1118                                         // See: https://core.trac.wordpress.org/ticket/22445
1119                                         elem.blur();
1120
1121                                         if ( elem.hasClass( 'gallery' ) ) {
1122                                                 options.state = 'gallery';
1123                                                 options.title = wp.media.view.l10n.createGalleryTitle;
1124                                         }
1125
1126                                         wp.media.editor.open( editor, options );
1127                                 });
1128
1129                         // Initialize and render the Editor drag-and-drop uploader.
1130                         new wp.media.view.EditorUploader().render();
1131                 }
1132         };
1133
1134         _.bindAll( wp.media.editor, 'open' );
1135         $( wp.media.editor.init );
1136 }(jQuery, _));