]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/js/media-editor.js
WordPress 4.2.3-scripts
[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                                 return this._frame;
674                         }
675
676                         this._frame = wp.media({
677                                 state: 'featured-image',
678                                 states: [ new wp.media.controller.FeaturedImage() , new wp.media.controller.EditImage() ]
679                         });
680
681                         this._frame.on( 'toolbar:create:featured-image', function( toolbar ) {
682                                 /**
683                                  * @this wp.media.view.MediaFrame.Select
684                                  */
685                                 this.createSelectToolbar( toolbar, {
686                                         text: wp.media.view.l10n.setFeaturedImage
687                                 });
688                         }, this._frame );
689
690                         this._frame.on( 'content:render:edit-image', function() {
691                                 var selection = this.state('featured-image').get('selection'),
692                                         view = new wp.media.view.EditImage( { model: selection.single(), controller: this } ).render();
693
694                                 this.content.set( view );
695
696                                 // after bringing in the frame, load the actual editor via an ajax call
697                                 view.loadEditor();
698
699                         }, this._frame );
700
701                         this._frame.state('featured-image').on( 'select', this.select );
702                         return this._frame;
703                 },
704                 /**
705                  * 'select' callback for Featured Image workflow, triggered when
706                  *  the 'Set Featured Image' button is clicked in the media modal.
707                  *
708                  * @global wp.media.view.settings
709                  *
710                  * @this wp.media.controller.FeaturedImage
711                  */
712                 select: function() {
713                         var selection = this.get('selection').single();
714
715                         if ( ! wp.media.view.settings.post.featuredImageId ) {
716                                 return;
717                         }
718
719                         wp.media.featuredImage.set( selection ? selection.id : -1 );
720                 },
721                 /**
722                  * Open the content media manager to the 'featured image' tab when
723                  * the post thumbnail is clicked.
724                  *
725                  * Update the featured image id when the 'remove' link is clicked.
726                  *
727                  * @global wp.media.view.settings
728                  */
729                 init: function() {
730                         $('#postimagediv').on( 'click', '#set-post-thumbnail', function( event ) {
731                                 event.preventDefault();
732                                 // Stop propagation to prevent thickbox from activating.
733                                 event.stopPropagation();
734
735                                 wp.media.featuredImage.frame().open();
736                         }).on( 'click', '#remove-post-thumbnail', function() {
737                                 wp.media.view.settings.post.featuredImageId = -1;
738                         });
739                 }
740         };
741
742         $( wp.media.featuredImage.init );
743
744         /**
745          * wp.media.editor
746          * @namespace
747          */
748         wp.media.editor = {
749                 /**
750                  * Send content to the editor
751                  *
752                  * @global tinymce
753                  * @global QTags
754                  * @global wpActiveEditor
755                  * @global tb_remove() - Possibly overloaded by legacy plugins
756                  *
757                  * @param {string} html Content to send to the editor
758                  */
759                 insert: function( html ) {
760                         var editor, wpActiveEditor,
761                                 hasTinymce = ! _.isUndefined( window.tinymce ),
762                                 hasQuicktags = ! _.isUndefined( window.QTags );
763
764                         if ( this.activeEditor ) {
765                                 wpActiveEditor = window.wpActiveEditor = this.activeEditor;
766                         } else {
767                                 wpActiveEditor = window.wpActiveEditor;
768                         }
769
770                         // Delegate to the global `send_to_editor` if it exists.
771                         // This attempts to play nice with any themes/plugins that have
772                         // overridden the insert functionality.
773                         if ( window.send_to_editor ) {
774                                 return window.send_to_editor.apply( this, arguments );
775                         }
776
777                         if ( ! wpActiveEditor ) {
778                                 if ( hasTinymce && tinymce.activeEditor ) {
779                                         editor = tinymce.activeEditor;
780                                         wpActiveEditor = window.wpActiveEditor = editor.id;
781                                 } else if ( ! hasQuicktags ) {
782                                         return false;
783                                 }
784                         } else if ( hasTinymce ) {
785                                 editor = tinymce.get( wpActiveEditor );
786                         }
787
788                         if ( editor && ! editor.isHidden() ) {
789                                 editor.execCommand( 'mceInsertContent', false, html );
790                         } else if ( hasQuicktags ) {
791                                 QTags.insertContent( html );
792                         } else {
793                                 document.getElementById( wpActiveEditor ).value += html;
794                         }
795
796                         // If the old thickbox remove function exists, call it in case
797                         // a theme/plugin overloaded it.
798                         if ( window.tb_remove ) {
799                                 try { window.tb_remove(); } catch( e ) {}
800                         }
801                 },
802
803                 /**
804                  * Setup 'workflow' and add to the 'workflows' cache. 'open' can
805                  *  subsequently be called upon it.
806                  *
807                  * @global wp.media.view.l10n
808                  *
809                  * @param {string} id A slug used to identify the workflow.
810                  * @param {Object} [options={}]
811                  *
812                  * @this wp.media.editor
813                  *
814                  * @returns {wp.media.view.MediaFrame.Select} A media workflow.
815                  */
816                 add: function( id, options ) {
817                         var workflow = this.get( id );
818
819                         // only add once: if exists return existing
820                         if ( workflow ) {
821                                 return workflow;
822                         }
823
824                         workflow = workflows[ id ] = wp.media( _.defaults( options || {}, {
825                                 frame:    'post',
826                                 state:    'insert',
827                                 title:    wp.media.view.l10n.addMedia,
828                                 multiple: true
829                         } ) );
830
831                         workflow.on( 'insert', function( selection ) {
832                                 var state = workflow.state();
833
834                                 selection = selection || state.get('selection');
835
836                                 if ( ! selection )
837                                         return;
838
839                                 $.when.apply( $, selection.map( function( attachment ) {
840                                         var display = state.display( attachment ).toJSON();
841                                         /**
842                                          * @this wp.media.editor
843                                          */
844                                         return this.send.attachment( display, attachment.toJSON() );
845                                 }, this ) ).done( function() {
846                                         wp.media.editor.insert( _.toArray( arguments ).join('\n\n') );
847                                 });
848                         }, this );
849
850                         workflow.state('gallery-edit').on( 'update', function( selection ) {
851                                 /**
852                                  * @this wp.media.editor
853                                  */
854                                 this.insert( wp.media.gallery.shortcode( selection ).string() );
855                         }, this );
856
857                         workflow.state('playlist-edit').on( 'update', function( selection ) {
858                                 /**
859                                  * @this wp.media.editor
860                                  */
861                                 this.insert( wp.media.playlist.shortcode( selection ).string() );
862                         }, this );
863
864                         workflow.state('video-playlist-edit').on( 'update', function( selection ) {
865                                 /**
866                                  * @this wp.media.editor
867                                  */
868                                 this.insert( wp.media.playlist.shortcode( selection ).string() );
869                         }, this );
870
871                         workflow.state('embed').on( 'select', function() {
872                                 /**
873                                  * @this wp.media.editor
874                                  */
875                                 var state = workflow.state(),
876                                         type = state.get('type'),
877                                         embed = state.props.toJSON();
878
879                                 embed.url = embed.url || '';
880
881                                 if ( 'link' === type ) {
882                                         _.defaults( embed, {
883                                                 linkText: embed.url,
884                                                 linkUrl: embed.url
885                                         });
886
887                                         this.send.link( embed ).done( function( resp ) {
888                                                 wp.media.editor.insert( resp );
889                                         });
890
891                                 } else if ( 'image' === type ) {
892                                         _.defaults( embed, {
893                                                 title:   embed.url,
894                                                 linkUrl: '',
895                                                 align:   'none',
896                                                 link:    'none'
897                                         });
898
899                                         if ( 'none' === embed.link ) {
900                                                 embed.linkUrl = '';
901                                         } else if ( 'file' === embed.link ) {
902                                                 embed.linkUrl = embed.url;
903                                         }
904
905                                         this.insert( wp.media.string.image( embed ) );
906                                 }
907                         }, this );
908
909                         workflow.state('featured-image').on( 'select', wp.media.featuredImage.select );
910                         workflow.setState( workflow.options.state );
911                         return workflow;
912                 },
913                 /**
914                  * Determines the proper current workflow id
915                  *
916                  * @global wpActiveEditor
917                  * @global tinymce
918                  *
919                  * @param {string} [id=''] A slug used to identify the workflow.
920                  *
921                  * @returns {wpActiveEditor|string|tinymce.activeEditor.id}
922                  */
923                 id: function( id ) {
924                         if ( id ) {
925                                 return id;
926                         }
927
928                         // If an empty `id` is provided, default to `wpActiveEditor`.
929                         id = window.wpActiveEditor;
930
931                         // If that doesn't work, fall back to `tinymce.activeEditor.id`.
932                         if ( ! id && ! _.isUndefined( window.tinymce ) && tinymce.activeEditor ) {
933                                 id = tinymce.activeEditor.id;
934                         }
935
936                         // Last but not least, fall back to the empty string.
937                         id = id || '';
938                         return id;
939                 },
940                 /**
941                  * Return the workflow specified by id
942                  *
943                  * @param {string} id A slug used to identify the workflow.
944                  *
945                  * @this wp.media.editor
946                  *
947                  * @returns {wp.media.view.MediaFrame} A media workflow.
948                  */
949                 get: function( id ) {
950                         id = this.id( id );
951                         return workflows[ id ];
952                 },
953                 /**
954                  * Remove the workflow represented by id from the workflow cache
955                  *
956                  * @param {string} id A slug used to identify the workflow.
957                  *
958                  * @this wp.media.editor
959                  */
960                 remove: function( id ) {
961                         id = this.id( id );
962                         delete workflows[ id ];
963                 },
964                 /**
965                  * @namespace
966                  */
967                 send: {
968                         /**
969                          * Called when sending an attachment to the editor
970                          *   from the medial modal.
971                          *
972                          * @global wp.media.view.settings
973                          * @global wp.media.post
974                          *
975                          * @param {Object} props Attachment details (align, link, size, etc).
976                          * @param {Object} attachment The attachment object, media version of Post.
977                          * @returns {Promise}
978                          */
979                         attachment: function( props, attachment ) {
980                                 var caption = attachment.caption,
981                                         options, html;
982
983                                 // If captions are disabled, clear the caption.
984                                 if ( ! wp.media.view.settings.captions ) {
985                                         delete attachment.caption;
986                                 }
987
988                                 props = wp.media.string.props( props, attachment );
989
990                                 options = {
991                                         id:           attachment.id,
992                                         post_content: attachment.description,
993                                         post_excerpt: caption
994                                 };
995
996                                 if ( props.linkUrl ) {
997                                         options.url = props.linkUrl;
998                                 }
999
1000                                 if ( 'image' === attachment.type ) {
1001                                         html = wp.media.string.image( props );
1002
1003                                         _.each({
1004                                                 align: 'align',
1005                                                 size:  'image-size',
1006                                                 alt:   'image_alt'
1007                                         }, function( option, prop ) {
1008                                                 if ( props[ prop ] )
1009                                                         options[ option ] = props[ prop ];
1010                                         });
1011                                 } else if ( 'video' === attachment.type ) {
1012                                         html = wp.media.string.video( props, attachment );
1013                                 } else if ( 'audio' === attachment.type ) {
1014                                         html = wp.media.string.audio( props, attachment );
1015                                 } else {
1016                                         html = wp.media.string.link( props );
1017                                         options.post_title = props.title;
1018                                 }
1019
1020                                 return wp.media.post( 'send-attachment-to-editor', {
1021                                         nonce:      wp.media.view.settings.nonce.sendToEditor,
1022                                         attachment: options,
1023                                         html:       html,
1024                                         post_id:    wp.media.view.settings.post.id
1025                                 });
1026                         },
1027                         /**
1028                          * Called when 'Insert From URL' source is not an image. Example: YouTube url.
1029                          *
1030                          * @global wp.media.view.settings
1031                          *
1032                          * @param {Object} embed
1033                          * @returns {Promise}
1034                          */
1035                         link: function( embed ) {
1036                                 return wp.media.post( 'send-link-to-editor', {
1037                                         nonce:     wp.media.view.settings.nonce.sendToEditor,
1038                                         src:       embed.linkUrl,
1039                                         link_text: embed.linkText,
1040                                         html:      wp.media.string.link( embed ),
1041                                         post_id:   wp.media.view.settings.post.id
1042                                 });
1043                         }
1044                 },
1045                 /**
1046                  * Open a workflow
1047                  *
1048                  * @param {string} [id=undefined] Optional. A slug used to identify the workflow.
1049                  * @param {Object} [options={}]
1050                  *
1051                  * @this wp.media.editor
1052                  *
1053                  * @returns {wp.media.view.MediaFrame}
1054                  */
1055                 open: function( id, options ) {
1056                         var workflow;
1057
1058                         options = options || {};
1059
1060                         id = this.id( id );
1061                         this.activeEditor = id;
1062
1063                         workflow = this.get( id );
1064
1065                         // Redo workflow if state has changed
1066                         if ( ! workflow || ( workflow.options && options.state !== workflow.options.state ) ) {
1067                                 workflow = this.add( id, options );
1068                         }
1069
1070                         return workflow.open();
1071                 },
1072
1073                 /**
1074                  * Bind click event for .insert-media using event delegation
1075                  *
1076                  * @global wp.media.view.l10n
1077                  */
1078                 init: function() {
1079                         $(document.body)
1080                                 .on( 'click.add-media-button', '.insert-media', function( event ) {
1081                                         var elem = $( event.currentTarget ),
1082                                                 editor = elem.data('editor'),
1083                                                 options = {
1084                                                         frame:    'post',
1085                                                         state:    'insert',
1086                                                         title:    wp.media.view.l10n.addMedia,
1087                                                         multiple: true
1088                                                 };
1089
1090                                         event.preventDefault();
1091
1092                                         // Remove focus from the `.insert-media` button.
1093                                         // Prevents Opera from showing the outline of the button
1094                                         // above the modal.
1095                                         //
1096                                         // See: https://core.trac.wordpress.org/ticket/22445
1097                                         elem.blur();
1098
1099                                         if ( elem.hasClass( 'gallery' ) ) {
1100                                                 options.state = 'gallery';
1101                                                 options.title = wp.media.view.l10n.createGalleryTitle;
1102                                         }
1103
1104                                         wp.media.editor.open( editor, options );
1105                                 });
1106
1107                         // Initialize and render the Editor drag-and-drop uploader.
1108                         new wp.media.view.EditorUploader().render();
1109                 }
1110         };
1111
1112         _.bindAll( wp.media.editor, 'open' );
1113         $( wp.media.editor.init );
1114 }(jQuery, _));