]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/js/media-editor.js
WordPress 3.9.2-scripts
[autoinstalls/wordpress.git] / wp-includes / js / media-editor.js
1 /* global getUserSetting, tinymce, QTags, wpActiveEditor */
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.collection = function(attributes) {
302                 var collections = {};
303
304                 return _.extend( attributes, {
305                         coerce : wp.media.coerce,
306                         /**
307                          * Retrieve attachments based on the properties of the passed shortcode
308                          *
309                          * @global wp.media.query
310                          *
311                          * @param {wp.shortcode} shortcode An instance of wp.shortcode().
312                          * @returns {wp.media.model.Attachments} A Backbone.Collection containing
313                          *      the media items belonging to a collection.
314                          *      The query[ this.tag ] property is a Backbone.Model
315                          *          containing the 'props' for the collection.
316                          */
317                         attachments: function( shortcode ) {
318                                 var shortcodeString = shortcode.string(),
319                                         result = collections[ shortcodeString ],
320                                         attrs, args, query, others, self = this;
321
322                                 delete collections[ shortcodeString ];
323                                 if ( result ) {
324                                         return result;
325                                 }
326                                 // Fill the default shortcode attributes.
327                                 attrs = _.defaults( shortcode.attrs.named, this.defaults );
328                                 args  = _.pick( attrs, 'orderby', 'order' );
329
330                                 args.type    = this.type;
331                                 args.perPage = -1;
332
333                                 // Mark the `orderby` override attribute.
334                                 if ( undefined !== attrs.orderby ) {
335                                         attrs._orderByField = attrs.orderby;
336                                 }
337
338                                 if ( 'rand' === attrs.orderby ) {
339                                         attrs._orderbyRandom = true;
340                                 }
341
342                                 // Map the `orderby` attribute to the corresponding model property.
343                                 if ( ! attrs.orderby || /^menu_order(?: ID)?$/i.test( attrs.orderby ) ) {
344                                         args.orderby = 'menuOrder';
345                                 }
346
347                                 // Map the `ids` param to the correct query args.
348                                 if ( attrs.ids ) {
349                                         args.post__in = attrs.ids.split(',');
350                                         args.orderby  = 'post__in';
351                                 } else if ( attrs.include ) {
352                                         args.post__in = attrs.include.split(',');
353                                 }
354
355                                 if ( attrs.exclude ) {
356                                         args.post__not_in = attrs.exclude.split(',');
357                                 }
358
359                                 if ( ! args.post__in ) {
360                                         args.uploadedTo = attrs.id;
361                                 }
362
363                                 // Collect the attributes that were not included in `args`.
364                                 others = _.omit( attrs, 'id', 'ids', 'include', 'exclude', 'orderby', 'order' );
365
366                                 _.each( this.defaults, function( value, key ) {
367                                         others[ key ] = self.coerce( others, key );
368                                 });
369
370                                 query = wp.media.query( args );
371                                 query[ this.tag ] = new Backbone.Model( others );
372                                 return query;
373                         },
374                         /**
375                          * Triggered when clicking 'Insert {label}' or 'Update {label}'
376                          *
377                          * @global wp.shortcode
378                          * @global wp.media.model.Attachments
379                          *
380                          * @param {wp.media.model.Attachments} attachments A Backbone.Collection containing
381                          *      the media items belonging to a collection.
382                          *      The query[ this.tag ] property is a Backbone.Model
383                          *          containing the 'props' for the collection.
384                          * @returns {wp.shortcode}
385                          */
386                         shortcode: function( attachments ) {
387                                 var props = attachments.props.toJSON(),
388                                         attrs = _.pick( props, 'orderby', 'order' ),
389                                         shortcode, clone, self = this;
390
391                                 if ( attachments.type ) {
392                                         attrs.type = attachments.type;
393                                         delete attachments.type;
394                                 }
395
396                                 if ( attachments[this.tag] ) {
397                                         _.extend( attrs, attachments[this.tag].toJSON() );
398                                 }
399
400                                 // Convert all gallery shortcodes to use the `ids` property.
401                                 // Ignore `post__in` and `post__not_in`; the attachments in
402                                 // the collection will already reflect those properties.
403                                 attrs.ids = attachments.pluck('id');
404
405                                 // Copy the `uploadedTo` post ID.
406                                 if ( props.uploadedTo ) {
407                                         attrs.id = props.uploadedTo;
408                                 }
409                                 // Check if the gallery is randomly ordered.
410                                 delete attrs.orderby;
411
412                                 if ( attrs._orderbyRandom ) {
413                                         attrs.orderby = 'rand';
414                                 } else if ( attrs._orderByField && attrs._orderByField != 'rand' ) {
415                                         attrs.orderby = attrs._orderByField;
416                                 }
417
418                                 delete attrs._orderbyRandom;
419                                 delete attrs._orderByField;
420
421                                 // If the `ids` attribute is set and `orderby` attribute
422                                 // is the default value, clear it for cleaner output.
423                                 if ( attrs.ids && 'post__in' === attrs.orderby ) {
424                                         delete attrs.orderby;
425                                 }
426
427                                 // Remove default attributes from the shortcode.
428                                 _.each( this.defaults, function( value, key ) {
429                                         attrs[ key ] = self.coerce( attrs, key );
430                                         if ( value === attrs[ key ] ) {
431                                                 delete attrs[ key ];
432                                         }
433                                 });
434
435                                 shortcode = new wp.shortcode({
436                                         tag:    this.tag,
437                                         attrs:  attrs,
438                                         type:   'single'
439                                 });
440
441                                 // Use a cloned version of the gallery.
442                                 clone = new wp.media.model.Attachments( attachments.models, {
443                                         props: props
444                                 });
445                                 clone[ this.tag ] = attachments[ this.tag ];
446                                 collections[ shortcode.string() ] = clone;
447
448                                 return shortcode;
449                         },
450                         /**
451                          * Triggered when double-clicking a collection shortcode placeholder
452                          *   in the editor
453                          *
454                          * @global wp.shortcode
455                          * @global wp.media.model.Selection
456                          * @global wp.media.view.l10n
457                          *
458                          * @param {string} content Content that is searched for possible
459                          *    shortcode markup matching the passed tag name,
460                          *
461                          * @this wp.media.{prop}
462                          *
463                          * @returns {wp.media.view.MediaFrame.Select} A media workflow.
464                          */
465                         edit: function( content ) {
466                                 var shortcode = wp.shortcode.next( this.tag, content ),
467                                         defaultPostId = this.defaults.id,
468                                         attachments, selection, state;
469
470                                 // Bail if we didn't match the shortcode or all of the content.
471                                 if ( ! shortcode || shortcode.content !== content ) {
472                                         return;
473                                 }
474
475                                 // Ignore the rest of the match object.
476                                 shortcode = shortcode.shortcode;
477
478                                 if ( _.isUndefined( shortcode.get('id') ) && ! _.isUndefined( defaultPostId ) ) {
479                                         shortcode.set( 'id', defaultPostId );
480                                 }
481
482                                 attachments = this.attachments( shortcode );
483
484                                 selection = new wp.media.model.Selection( attachments.models, {
485                                         props:    attachments.props.toJSON(),
486                                         multiple: true
487                                 });
488
489                                 selection[ this.tag ] = attachments[ this.tag ];
490
491                                 // Fetch the query's attachments, and then break ties from the
492                                 // query to allow for sorting.
493                                 selection.more().done( function() {
494                                         // Break ties with the query.
495                                         selection.props.set({ query: false });
496                                         selection.unmirror();
497                                         selection.props.unset('orderby');
498                                 });
499
500                                 // Destroy the previous gallery frame.
501                                 if ( this.frame ) {
502                                         this.frame.dispose();
503                                 }
504
505                                 if ( shortcode.attrs.named.type && 'video' === shortcode.attrs.named.type ) {
506                                         state = 'video-' + this.tag + '-edit';
507                                 } else {
508                                         state = this.tag + '-edit';
509                                 }
510
511                                 // Store the current frame.
512                                 this.frame = wp.media({
513                                         frame:     'post',
514                                         state:     state,
515                                         title:     this.editTitle,
516                                         editing:   true,
517                                         multiple:  true,
518                                         selection: selection
519                                 }).open();
520
521                                 return this.frame;
522                         }
523                 });
524         };
525
526         wp.media.gallery = new wp.media.collection({
527                 tag: 'gallery',
528                 type : 'image',
529                 editTitle : wp.media.view.l10n.editGalleryTitle,
530                 defaults : {
531                         itemtag: 'dl',
532                         icontag: 'dt',
533                         captiontag: 'dd',
534                         columns: '3',
535                         link: 'post',
536                         size: 'thumbnail',
537                         order: 'ASC',
538                         id: wp.media.view.settings.post && wp.media.view.settings.post.id,
539                         orderby : 'menu_order ID'
540                 }
541         });
542
543         /**
544          * wp.media.featuredImage
545          * @namespace
546          */
547         wp.media.featuredImage = {
548                 /**
549                  * Get the featured image post ID
550                  *
551                  * @global wp.media.view.settings
552                  *
553                  * @returns {wp.media.view.settings.post.featuredImageId|number}
554                  */
555                 get: function() {
556                         return wp.media.view.settings.post.featuredImageId;
557                 },
558                 /**
559                  * Set the featured image id, save the post thumbnail data and
560                  * set the HTML in the post meta box to the new featured image.
561                  *
562                  * @global wp.media.view.settings
563                  * @global wp.media.post
564                  *
565                  * @param {number} id The post ID of the featured image, or -1 to unset it.
566                  */
567                 set: function( id ) {
568                         var settings = wp.media.view.settings;
569
570                         settings.post.featuredImageId = id;
571
572                         wp.media.post( 'set-post-thumbnail', {
573                                 json:         true,
574                                 post_id:      settings.post.id,
575                                 thumbnail_id: settings.post.featuredImageId,
576                                 _wpnonce:     settings.post.nonce
577                         }).done( function( html ) {
578                                 $( '.inside', '#postimagediv' ).html( html );
579                         });
580                 },
581                 /**
582                  * The Featured Image workflow
583                  *
584                  * @global wp.media.controller.FeaturedImage
585                  * @global wp.media.view.l10n
586                  *
587                  * @this wp.media.featuredImage
588                  *
589                  * @returns {wp.media.view.MediaFrame.Select} A media workflow.
590                  */
591                 frame: function() {
592                         if ( this._frame ) {
593                                 return this._frame;
594                         }
595
596                         this._frame = wp.media({
597                                 state: 'featured-image',
598                                 states: [ new wp.media.controller.FeaturedImage() , new wp.media.controller.EditImage() ]
599                         });
600
601                         this._frame.on( 'toolbar:create:featured-image', function( toolbar ) {
602                                 /**
603                                  * @this wp.media.view.MediaFrame.Select
604                                  */
605                                 this.createSelectToolbar( toolbar, {
606                                         text: wp.media.view.l10n.setFeaturedImage
607                                 });
608                         }, this._frame );
609
610                         this._frame.on( 'content:render:edit-image', function() {
611                                 var selection = this.state('featured-image').get('selection'),
612                                         view = new wp.media.view.EditImage( { model: selection.single(), controller: this } ).render();
613
614                                 this.content.set( view );
615
616                                 // after bringing in the frame, load the actual editor via an ajax call
617                                 view.loadEditor();
618
619                         }, this._frame );
620
621                         this._frame.state('featured-image').on( 'select', this.select );
622                         return this._frame;
623                 },
624                 /**
625                  * 'select' callback for Featured Image workflow, triggered when
626                  *  the 'Set Featured Image' button is clicked in the media modal.
627                  *
628                  * @global wp.media.view.settings
629                  *
630                  * @this wp.media.controller.FeaturedImage
631                  */
632                 select: function() {
633                         var selection = this.get('selection').single();
634
635                         if ( ! wp.media.view.settings.post.featuredImageId ) {
636                                 return;
637                         }
638
639                         wp.media.featuredImage.set( selection ? selection.id : -1 );
640                 },
641                 /**
642                  * Open the content media manager to the 'featured image' tab when
643                  * the post thumbnail is clicked.
644                  *
645                  * Update the featured image id when the 'remove' link is clicked.
646                  *
647                  * @global wp.media.view.settings
648                  */
649                 init: function() {
650                         $('#postimagediv').on( 'click', '#set-post-thumbnail', function( event ) {
651                                 event.preventDefault();
652                                 // Stop propagation to prevent thickbox from activating.
653                                 event.stopPropagation();
654
655                                 wp.media.featuredImage.frame().open();
656                         }).on( 'click', '#remove-post-thumbnail', function() {
657                                 wp.media.view.settings.post.featuredImageId = -1;
658                         });
659                 }
660         };
661
662         $( wp.media.featuredImage.init );
663
664         /**
665          * wp.media.editor
666          * @namespace
667          */
668         wp.media.editor = {
669                 /**
670                  * Send content to the editor
671                  *
672                  * @global tinymce
673                  * @global QTags
674                  * @global wpActiveEditor
675                  * @global tb_remove() - Possibly overloaded by legacy plugins
676                  *
677                  * @param {string} html Content to send to the editor
678                  */
679                 insert: function( html ) {
680                         var editor,
681                                 hasTinymce = ! _.isUndefined( window.tinymce ),
682                                 hasQuicktags = ! _.isUndefined( window.QTags ),
683                                 wpActiveEditor = window.wpActiveEditor;
684
685                         // Delegate to the global `send_to_editor` if it exists.
686                         // This attempts to play nice with any themes/plugins that have
687                         // overridden the insert functionality.
688                         if ( window.send_to_editor ) {
689                                 return window.send_to_editor.apply( this, arguments );
690                         }
691
692                         if ( ! wpActiveEditor ) {
693                                 if ( hasTinymce && tinymce.activeEditor ) {
694                                         editor = tinymce.activeEditor;
695                                         wpActiveEditor = window.wpActiveEditor = editor.id;
696                                 } else if ( ! hasQuicktags ) {
697                                         return false;
698                                 }
699                         } else if ( hasTinymce ) {
700                                 editor = tinymce.get( wpActiveEditor );
701                         }
702
703                         if ( editor && ! editor.isHidden() ) {
704                                 editor.execCommand( 'mceInsertContent', false, html );
705                         } else if ( hasQuicktags ) {
706                                 QTags.insertContent( html );
707                         } else {
708                                 document.getElementById( wpActiveEditor ).value += html;
709                         }
710
711                         // If the old thickbox remove function exists, call it in case
712                         // a theme/plugin overloaded it.
713                         if ( window.tb_remove ) {
714                                 try { window.tb_remove(); } catch( e ) {}
715                         }
716                 },
717
718                 /**
719                  * Setup 'workflow' and add to the 'workflows' cache. 'open' can
720                  *  subsequently be called upon it.
721                  *
722                  * @global wp.media.view.l10n
723                  *
724                  * @param {string} id A slug used to identify the workflow.
725                  * @param {Object} [options={}]
726                  *
727                  * @this wp.media.editor
728                  *
729                  * @returns {wp.media.view.MediaFrame.Select} A media workflow.
730                  */
731                 add: function( id, options ) {
732                         var workflow = this.get( id );
733
734                         // only add once: if exists return existing
735                         if ( workflow ) {
736                                 return workflow;
737                         }
738
739                         workflow = workflows[ id ] = wp.media( _.defaults( options || {}, {
740                                 frame:    'post',
741                                 state:    'insert',
742                                 title:    wp.media.view.l10n.addMedia,
743                                 multiple: true
744                         } ) );
745
746                         workflow.on( 'insert', function( selection ) {
747                                 var state = workflow.state();
748
749                                 selection = selection || state.get('selection');
750
751                                 if ( ! selection )
752                                         return;
753
754                                 $.when.apply( $, selection.map( function( attachment ) {
755                                         var display = state.display( attachment ).toJSON();
756                                         /**
757                                          * @this wp.media.editor
758                                          */
759                                         return this.send.attachment( display, attachment.toJSON() );
760                                 }, this ) ).done( function() {
761                                         wp.media.editor.insert( _.toArray( arguments ).join('\n\n') );
762                                 });
763                         }, this );
764
765                         workflow.state('gallery-edit').on( 'update', function( selection ) {
766                                 /**
767                                  * @this wp.media.editor
768                                  */
769                                 this.insert( wp.media.gallery.shortcode( selection ).string() );
770                         }, this );
771
772                         workflow.state('playlist-edit').on( 'update', function( selection ) {
773                                 /**
774                                  * @this wp.media.editor
775                                  */
776                                 this.insert( wp.media.playlist.shortcode( selection ).string() );
777                         }, this );
778
779                         workflow.state('video-playlist-edit').on( 'update', function( selection ) {
780                                 /**
781                                  * @this wp.media.editor
782                                  */
783                                 this.insert( wp.media.playlist.shortcode( selection ).string() );
784                         }, this );
785
786                         workflow.state('embed').on( 'select', function() {
787                                 /**
788                                  * @this wp.media.editor
789                                  */
790                                 var state = workflow.state(),
791                                         type = state.get('type'),
792                                         embed = state.props.toJSON();
793
794                                 embed.url = embed.url || '';
795
796                                 if ( 'link' === type ) {
797                                         _.defaults( embed, {
798                                                 title:   embed.url,
799                                                 linkUrl: embed.url
800                                         });
801
802                                         this.send.link( embed ).done( function( resp ) {
803                                                 wp.media.editor.insert( resp );
804                                         });
805
806                                 } else if ( 'image' === type ) {
807                                         _.defaults( embed, {
808                                                 title:   embed.url,
809                                                 linkUrl: '',
810                                                 align:   'none',
811                                                 link:    'none'
812                                         });
813
814                                         if ( 'none' === embed.link ) {
815                                                 embed.linkUrl = '';
816                                         } else if ( 'file' === embed.link ) {
817                                                 embed.linkUrl = embed.url;
818                                         }
819
820                                         this.insert( wp.media.string.image( embed ) );
821                                 }
822                         }, this );
823
824                         workflow.state('featured-image').on( 'select', wp.media.featuredImage.select );
825                         workflow.setState( workflow.options.state );
826                         return workflow;
827                 },
828                 /**
829                  * Determines the proper current workflow id
830                  *
831                  * @global wpActiveEditor
832                  * @global tinymce
833                  *
834                  * @param {string} [id=''] A slug used to identify the workflow.
835                  *
836                  * @returns {wpActiveEditor|string|tinymce.activeEditor.id}
837                  */
838                 id: function( id ) {
839                         if ( id ) {
840                                 return id;
841                         }
842
843                         // If an empty `id` is provided, default to `wpActiveEditor`.
844                         id = wpActiveEditor;
845
846                         // If that doesn't work, fall back to `tinymce.activeEditor.id`.
847                         if ( ! id && ! _.isUndefined( window.tinymce ) && tinymce.activeEditor ) {
848                                 id = tinymce.activeEditor.id;
849                         }
850
851                         // Last but not least, fall back to the empty string.
852                         id = id || '';
853                         return id;
854                 },
855                 /**
856                  * Return the workflow specified by id
857                  *
858                  * @param {string} id A slug used to identify the workflow.
859                  *
860                  * @this wp.media.editor
861                  *
862                  * @returns {wp.media.view.MediaFrame} A media workflow.
863                  */
864                 get: function( id ) {
865                         id = this.id( id );
866                         return workflows[ id ];
867                 },
868                 /**
869                  * Remove the workflow represented by id from the workflow cache
870                  *
871                  * @param {string} id A slug used to identify the workflow.
872                  *
873                  * @this wp.media.editor
874                  */
875                 remove: function( id ) {
876                         id = this.id( id );
877                         delete workflows[ id ];
878                 },
879                 /**
880                  * @namespace
881                  */
882                 send: {
883                         /**
884                          * Called when sending an attachment to the editor
885                          *   from the medial modal.
886                          *
887                          * @global wp.media.view.settings
888                          * @global wp.media.post
889                          *
890                          * @param {Object} props Attachment details (align, link, size, etc).
891                          * @param {Object} attachment The attachment object, media version of Post.
892                          * @returns {Promise}
893                          */
894                         attachment: function( props, attachment ) {
895                                 var caption = attachment.caption,
896                                         options, html;
897
898                                 // If captions are disabled, clear the caption.
899                                 if ( ! wp.media.view.settings.captions ) {
900                                         delete attachment.caption;
901                                 }
902
903                                 props = wp.media.string.props( props, attachment );
904
905                                 options = {
906                                         id:           attachment.id,
907                                         post_content: attachment.description,
908                                         post_excerpt: caption
909                                 };
910
911                                 if ( props.linkUrl ) {
912                                         options.url = props.linkUrl;
913                                 }
914
915                                 if ( 'image' === attachment.type ) {
916                                         html = wp.media.string.image( props );
917
918                                         _.each({
919                                                 align: 'align',
920                                                 size:  'image-size',
921                                                 alt:   'image_alt'
922                                         }, function( option, prop ) {
923                                                 if ( props[ prop ] )
924                                                         options[ option ] = props[ prop ];
925                                         });
926                                 } else if ( 'video' === attachment.type ) {
927                                         html = wp.media.string.video( props, attachment );
928                                 } else if ( 'audio' === attachment.type ) {
929                                         html = wp.media.string.audio( props, attachment );
930                                 } else {
931                                         html = wp.media.string.link( props );
932                                         options.post_title = props.title;
933                                 }
934
935                                 return wp.media.post( 'send-attachment-to-editor', {
936                                         nonce:      wp.media.view.settings.nonce.sendToEditor,
937                                         attachment: options,
938                                         html:       html,
939                                         post_id:    wp.media.view.settings.post.id
940                                 });
941                         },
942                         /**
943                          * Called when 'Insert From URL' source is not an image. Example: YouTube url.
944                          *
945                          * @global wp.media.view.settings
946                          *
947                          * @param {Object} embed
948                          * @returns {Promise}
949                          */
950                         link: function( embed ) {
951                                 return wp.media.post( 'send-link-to-editor', {
952                                         nonce:   wp.media.view.settings.nonce.sendToEditor,
953                                         src:     embed.linkUrl,
954                                         title:   embed.title,
955                                         html:    wp.media.string.link( embed ),
956                                         post_id: wp.media.view.settings.post.id
957                                 });
958                         }
959                 },
960                 /**
961                  * Open a workflow
962                  *
963                  * @param {string} [id=undefined] Optional. A slug used to identify the workflow.
964                  * @param {Object} [options={}]
965                  *
966                  * @this wp.media.editor
967                  *
968                  * @returns {wp.media.view.MediaFrame}
969                  */
970                 open: function( id, options ) {
971                         var workflow;
972
973                         options = options || {};
974
975                         id = this.id( id );
976 /*
977                         // Save a bookmark of the caret position in IE.
978                         if ( ! _.isUndefined( window.tinymce ) ) {
979                                 editor = tinymce.get( id );
980
981                                 if ( tinymce.isIE && editor && ! editor.isHidden() ) {
982                                         editor.focus();
983                                         editor.windowManager.insertimagebookmark = editor.selection.getBookmark();
984                                 }
985                         }
986 */
987                         workflow = this.get( id );
988
989                         // Redo workflow if state has changed
990                         if ( ! workflow || ( workflow.options && options.state !== workflow.options.state ) ) {
991                                 workflow = this.add( id, options );
992                         }
993
994                         return workflow.open();
995                 },
996
997                 /**
998                  * Bind click event for .insert-media using event delegation
999                  *
1000                  * @global wp.media.view.l10n
1001                  */
1002                 init: function() {
1003                         $(document.body)
1004                                 .on( 'click', '.insert-media', function( event ) {
1005                                         var elem = $( event.currentTarget ),
1006                                                 editor = elem.data('editor'),
1007                                                 options = {
1008                                                         frame:    'post',
1009                                                         state:    'insert',
1010                                                         title:    wp.media.view.l10n.addMedia,
1011                                                         multiple: true
1012                                                 };
1013
1014                                         event.preventDefault();
1015
1016                                         // Remove focus from the `.insert-media` button.
1017                                         // Prevents Opera from showing the outline of the button
1018                                         // above the modal.
1019                                         //
1020                                         // See: http://core.trac.wordpress.org/ticket/22445
1021                                         elem.blur();
1022
1023                                         if ( elem.hasClass( 'gallery' ) ) {
1024                                                 options.state = 'gallery';
1025                                                 options.title = wp.media.view.l10n.createGalleryTitle;
1026                                         }
1027
1028                                         wp.media.editor.open( editor, options );
1029                                 });
1030
1031                         // Initialize and render the Editor drag-and-drop uploader.
1032                         new wp.media.view.EditorUploader().render();
1033                 }
1034         };
1035
1036         _.bindAll( wp.media.editor, 'open' );
1037         $( wp.media.editor.init );
1038 }(jQuery, _));