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