]> scripts.mit.edu Git - autoinstalls/wordpress.git/blobdiff - wp-includes/js/media-editor.js
WordPress 3.9
[autoinstalls/wordpress.git] / wp-includes / js / media-editor.js
index 75b1c570d90df7ac017114bee3e91a12c4de46cb..eb1efbcbc4a95b07c179e59f84dffde245c1e944 100644 (file)
@@ -2,14 +2,51 @@
 
 // WordPress, TinyMCE, and Media
 // -----------------------------
-(function($){
-       // Stores the editors' `wp.media.controller.Frame` instances.
+(function($, _){
+       /**
+        * Stores the editors' `wp.media.controller.Frame` instances.
+        *
+        * @static
+        */
        var workflows = {};
 
+       /**
+        * A helper mixin function to avoid truthy and falsey values being
+        *   passed as an input that expects booleans. If key is undefined in the map,
+        *   but has a default value, set it.
+        *
+        * @param {object} attrs Map of props from a shortcode or settings.
+        * @param {string} key The key within the passed map to check for a value.
+        * @returns {mixed|undefined} The original or coerced value of key within attrs
+        */
+       wp.media.coerce = function ( attrs, key ) {
+               if ( _.isUndefined( attrs[ key ] ) && ! _.isUndefined( this.defaults[ key ] ) ) {
+                       attrs[ key ] = this.defaults[ key ];
+               } else if ( 'true' === attrs[ key ] ) {
+                       attrs[ key ] = true;
+               } else if ( 'false' === attrs[ key ] ) {
+                       attrs[ key ] = false;
+               }
+               return attrs[ key ];
+       };
+
+       /**
+        * wp.media.string
+        * @namespace
+        */
        wp.media.string = {
-               // Joins the `props` and `attachment` objects,
-               // outputting the proper object format based on the
-               // attachment's type.
+               /**
+                * Joins the `props` and `attachment` objects,
+                * outputting the proper object format based on the
+                * attachment's type.
+                *
+                * @global wp.media.view.settings
+                * @global getUserSetting()
+                *
+                * @param {Object} [props={}] Attachment details (align, link, size, etc).
+                * @param {Object} attachment The attachment object, media version of Post.
+                * @returns {Object} Joined props
+                */
                props: function( props, attachment ) {
                        var link, linkUrl, size, sizes, fallbacks,
                                defaultProps = wp.media.view.settings.defaultProps;
@@ -28,8 +65,9 @@
 
                        props = props ? _.clone( props ) : {};
 
-                       if ( attachment && attachment.type )
+                       if ( attachment && attachment.type ) {
                                props.type = attachment.type;
+                       }
 
                        if ( 'image' === props.type ) {
                                props = _.defaults( props || {}, {
                        }
 
                        // All attachment-specific settings follow.
-                       if ( ! attachment )
+                       if ( ! attachment ) {
                                return fallbacks( props );
+                       }
 
                        props.title = props.title || attachment.title;
 
                        link = props.link || defaultProps.link || getUserSetting( 'urlbutton', 'file' );
-                       if ( 'file' === link || 'embed' === link )
+                       if ( 'file' === link || 'embed' === link ) {
                                linkUrl = attachment.url;
-                       else if ( 'post' === link )
+                       } else if ( 'post' === link ) {
                                linkUrl = attachment.link;
-                       else if ( 'custom' === link )
+                       } else if ( 'custom' === link ) {
                                linkUrl = props.linkUrl;
+                       }
                        props.linkUrl = linkUrl || '';
 
                        // Format properties for images.
 
                        return fallbacks( props );
                },
-
+               /**
+                * Create link markup that is suitable for passing to the editor
+                *
+                * @global wp.html.string
+                *
+                * @param {Object} props Attachment details (align, link, size, etc).
+                * @param {Object} attachment The attachment object, media version of Post.
+                * @returns {string} The link markup
+                */
                link: function( props, attachment ) {
                        var options;
 
                                }
                        };
 
-                       if ( props.rel )
+                       if ( props.rel ) {
                                options.attrs.rel = props.rel;
+                       }
 
                        return wp.html.string( options );
                },
-
+               /**
+                * Create an Audio shortcode string that is suitable for passing to the editor
+                *
+                * @param {Object} props Attachment details (align, link, size, etc).
+                * @param {Object} attachment The attachment object, media version of Post.
+                * @returns {string} The audio shortcode
+                */
                audio: function( props, attachment ) {
                        return wp.media.string._audioVideo( 'audio', props, attachment );
                },
-
+               /**
+                * Create a Video shortcode string that is suitable for passing to the editor
+                *
+                * @param {Object} props Attachment details (align, link, size, etc).
+                * @param {Object} attachment The attachment object, media version of Post.
+                * @returns {string} The video shortcode
+                */
                video: function( props, attachment ) {
                        return wp.media.string._audioVideo( 'video', props, attachment );
                },
-
+               /**
+                * Helper function to create a media shortcode string
+                *
+                * @access private
+                *
+                * @global wp.shortcode
+                * @global wp.media.view.settings
+                *
+                * @param {string} type The shortcode tag name: 'audio' or 'video'.
+                * @param {Object} props Attachment details (align, link, size, etc).
+                * @param {Object} attachment The attachment object, media version of Post.
+                * @returns {string} The media shortcode
+                */
                _audioVideo: function( type, props, attachment ) {
                        var shortcode, html, extension;
 
                        shortcode = {};
 
                        if ( 'video' === type ) {
-                               if ( attachment.width )
+                               if ( attachment.image && -1 === attachment.image.src.indexOf( attachment.icon ) ) {
+                                       shortcode.poster = attachment.image.src;
+                               }
+
+                               if ( attachment.width ) {
                                        shortcode.width = attachment.width;
+                               }
 
-                               if ( attachment.height )
+                               if ( attachment.height ) {
                                        shortcode.height = attachment.height;
+                               }
                        }
 
                        extension = attachment.filename.split('.').pop();
 
                        return html;
                },
-
+               /**
+                * Create image markup, optionally with a link and/or wrapped in a caption shortcode,
+                *  that is suitable for passing to the editor
+                *
+                * @global wp.html
+                * @global wp.shortcode
+                *
+                * @param {Object} props Attachment details (align, link, size, etc).
+                * @param {Object} attachment The attachment object, media version of Post.
+                * @returns {string}
+                */
                image: function( props, attachment ) {
                        var img = {},
                                options, classes, shortcode, html;
                        props = wp.media.string.props( props, attachment );
                        classes = props.classes || [];
 
-                       img.src = typeof attachment !== 'undefined' ? attachment.url : props.url;
+                       img.src = ! _.isUndefined( attachment ) ? attachment.url : props.url;
                        _.extend( img, _.pick( props, 'width', 'height', 'alt' ) );
 
                        // Only assign the align class to the image if we're not printing
                        // a caption, since the alignment is sent to the shortcode.
-                       if ( props.align && ! props.caption )
+                       if ( props.align && ! props.caption ) {
                                classes.push( 'align' + props.align );
+                       }
 
-                       if ( props.size )
+                       if ( props.size ) {
                                classes.push( 'size-' + props.size );
+                       }
 
                        img['class'] = _.compact( classes ).join(' ');
 
                        if ( props.caption ) {
                                shortcode = {};
 
-                               if ( img.width )
+                               if ( img.width ) {
                                        shortcode.width = img.width;
+                               }
 
-                               if ( props.captionId )
+                               if ( props.captionId ) {
                                        shortcode.id = props.captionId;
+                               }
 
-                               if ( props.align )
+                               if ( props.align ) {
                                        shortcode.align = 'align' + props.align;
+                               }
 
                                html = wp.shortcode.string({
                                        tag:     'caption',
                }
        };
 
-       wp.media.gallery = (function() {
-               var galleries = {};
-
-               return {
-                       defaults: {
-                               order:      'ASC',
-                               id:         wp.media.view.settings.post.id,
-                               itemtag:    'dl',
-                               icontag:    'dt',
-                               captiontag: 'dd',
-                               columns:    '3',
-                               link:       'post',
-                               size:       'thumbnail',
-                               orderby:    'menu_order ID'
-                       },
-
+       wp.media.collection = function(attributes) {
+               var collections = {};
+
+               return _.extend( attributes, {
+                       coerce : wp.media.coerce,
+                       /**
+                        * Retrieve attachments based on the properties of the passed shortcode
+                        *
+                        * @global wp.media.query
+                        *
+                        * @param {wp.shortcode} shortcode An instance of wp.shortcode().
+                        * @returns {wp.media.model.Attachments} A Backbone.Collection containing
+                        *      the media items belonging to a collection.
+                        *      The query[ this.tag ] property is a Backbone.Model
+                        *          containing the 'props' for the collection.
+                        */
                        attachments: function( shortcode ) {
                                var shortcodeString = shortcode.string(),
-                                       result = galleries[ shortcodeString ],
-                                       attrs, args, query, others;
+                                       result = collections[ shortcodeString ],
+                                       attrs, args, query, others, self = this;
 
-                               delete galleries[ shortcodeString ];
-
-                               if ( result )
+                               delete collections[ shortcodeString ];
+                               if ( result ) {
                                        return result;
-
+                               }
                                // Fill the default shortcode attributes.
-                               attrs = _.defaults( shortcode.attrs.named, wp.media.gallery.defaults );
+                               attrs = _.defaults( shortcode.attrs.named, this.defaults );
                                args  = _.pick( attrs, 'orderby', 'order' );
 
-                               args.type    = 'image';
+                               args.type    = this.type;
                                args.perPage = -1;
 
                                // Mark the `orderby` override attribute.
-                               if( undefined !== attrs.orderby )
+                               if ( undefined !== attrs.orderby ) {
                                        attrs._orderByField = attrs.orderby;
+                               }
 
-                               if ( 'rand' === attrs.orderby )
+                               if ( 'rand' === attrs.orderby ) {
                                        attrs._orderbyRandom = true;
+                               }
 
                                // Map the `orderby` attribute to the corresponding model property.
-                               if ( ! attrs.orderby || /^menu_order(?: ID)?$/i.test( attrs.orderby ) )
+                               if ( ! attrs.orderby || /^menu_order(?: ID)?$/i.test( attrs.orderby ) ) {
                                        args.orderby = 'menuOrder';
+                               }
 
                                // Map the `ids` param to the correct query args.
                                if ( attrs.ids ) {
                                        args.post__in = attrs.include.split(',');
                                }
 
-                               if ( attrs.exclude )
+                               if ( attrs.exclude ) {
                                        args.post__not_in = attrs.exclude.split(',');
+                               }
 
-                               if ( ! args.post__in )
+                               if ( ! args.post__in ) {
                                        args.uploadedTo = attrs.id;
+                               }
 
                                // Collect the attributes that were not included in `args`.
                                others = _.omit( attrs, 'id', 'ids', 'include', 'exclude', 'orderby', 'order' );
 
+                               _.each( this.defaults, function( value, key ) {
+                                       others[ key ] = self.coerce( others, key );
+                               });
+
                                query = wp.media.query( args );
-                               query.gallery = new Backbone.Model( others );
+                               query[ this.tag ] = new Backbone.Model( others );
                                return query;
                        },
-
+                       /**
+                        * Triggered when clicking 'Insert {label}' or 'Update {label}'
+                        *
+                        * @global wp.shortcode
+                        * @global wp.media.model.Attachments
+                        *
+                        * @param {wp.media.model.Attachments} attachments A Backbone.Collection containing
+                        *      the media items belonging to a collection.
+                        *      The query[ this.tag ] property is a Backbone.Model
+                        *          containing the 'props' for the collection.
+                        * @returns {wp.shortcode}
+                        */
                        shortcode: function( attachments ) {
                                var props = attachments.props.toJSON(),
                                        attrs = _.pick( props, 'orderby', 'order' ),
-                                       shortcode, clone;
+                                       shortcode, clone, self = this;
 
-                               if ( attachments.gallery )
-                                       _.extend( attrs, attachments.gallery.toJSON() );
+                               if ( attachments.type ) {
+                                       attrs.type = attachments.type;
+                                       delete attachments.type;
+                               }
+
+                               if ( attachments[this.tag] ) {
+                                       _.extend( attrs, attachments[this.tag].toJSON() );
+                               }
 
                                // Convert all gallery shortcodes to use the `ids` property.
                                // Ignore `post__in` and `post__not_in`; the attachments in
                                attrs.ids = attachments.pluck('id');
 
                                // Copy the `uploadedTo` post ID.
-                               if ( props.uploadedTo )
+                               if ( props.uploadedTo ) {
                                        attrs.id = props.uploadedTo;
-
+                               }
                                // Check if the gallery is randomly ordered.
                                delete attrs.orderby;
 
-                               if ( attrs._orderbyRandom )
+                               if ( attrs._orderbyRandom ) {
                                        attrs.orderby = 'rand';
-                               else if ( attrs._orderByField && attrs._orderByField != 'rand' )
+                               } else if ( attrs._orderByField && attrs._orderByField != 'rand' ) {
                                        attrs.orderby = attrs._orderByField;
+                               }
 
                                delete attrs._orderbyRandom;
                                delete attrs._orderByField;
 
                                // If the `ids` attribute is set and `orderby` attribute
                                // is the default value, clear it for cleaner output.
-                               if ( attrs.ids && 'post__in' === attrs.orderby )
+                               if ( attrs.ids && 'post__in' === attrs.orderby ) {
                                        delete attrs.orderby;
+                               }
 
                                // Remove default attributes from the shortcode.
-                               _.each( wp.media.gallery.defaults, function( value, key ) {
-                                       if ( value === attrs[ key ] )
+                               _.each( this.defaults, function( value, key ) {
+                                       attrs[ key ] = self.coerce( attrs, key );
+                                       if ( value === attrs[ key ] ) {
                                                delete attrs[ key ];
+                                       }
                                });
 
                                shortcode = new wp.shortcode({
-                                       tag:    'gallery',
+                                       tag:    this.tag,
                                        attrs:  attrs,
                                        type:   'single'
                                });
                                clone = new wp.media.model.Attachments( attachments.models, {
                                        props: props
                                });
-                               clone.gallery = attachments.gallery;
-                               galleries[ shortcode.string() ] = clone;
+                               clone[ this.tag ] = attachments[ this.tag ];
+                               collections[ shortcode.string() ] = clone;
 
                                return shortcode;
                        },
-
+                       /**
+                        * Triggered when double-clicking a collection shortcode placeholder
+                        *   in the editor
+                        *
+                        * @global wp.shortcode
+                        * @global wp.media.model.Selection
+                        * @global wp.media.view.l10n
+                        *
+                        * @param {string} content Content that is searched for possible
+                        *    shortcode markup matching the passed tag name,
+                        *
+                        * @this wp.media.{prop}
+                        *
+                        * @returns {wp.media.view.MediaFrame.Select} A media workflow.
+                        */
                        edit: function( content ) {
-                               var shortcode = wp.shortcode.next( 'gallery', content ),
-                                       defaultPostId = wp.media.gallery.defaults.id,
-                                       attachments, selection;
+                               var shortcode = wp.shortcode.next( this.tag, content ),
+                                       defaultPostId = this.defaults.id,
+                                       attachments, selection, state;
 
                                // Bail if we didn't match the shortcode or all of the content.
-                               if ( ! shortcode || shortcode.content !== content )
+                               if ( ! shortcode || shortcode.content !== content ) {
                                        return;
+                               }
 
                                // Ignore the rest of the match object.
                                shortcode = shortcode.shortcode;
 
-                               if ( _.isUndefined( shortcode.get('id') ) && ! _.isUndefined( defaultPostId ) )
+                               if ( _.isUndefined( shortcode.get('id') ) && ! _.isUndefined( defaultPostId ) ) {
                                        shortcode.set( 'id', defaultPostId );
+                               }
 
-                               attachments = wp.media.gallery.attachments( shortcode );
+                               attachments = this.attachments( shortcode );
 
                                selection = new wp.media.model.Selection( attachments.models, {
                                        props:    attachments.props.toJSON(),
                                        multiple: true
                                });
 
-                               selection.gallery = attachments.gallery;
+                               selection[ this.tag ] = attachments[ this.tag ];
 
                                // Fetch the query's attachments, and then break ties from the
                                // query to allow for sorting.
                                });
 
                                // Destroy the previous gallery frame.
-                               if ( this.frame )
+                               if ( this.frame ) {
                                        this.frame.dispose();
+                               }
+
+                               if ( shortcode.attrs.named.type && 'video' === shortcode.attrs.named.type ) {
+                                       state = 'video-' + this.tag + '-edit';
+                               } else {
+                                       state = this.tag + '-edit';
+                               }
 
-                               // Store the current gallery frame.
+                               // Store the current frame.
                                this.frame = wp.media({
                                        frame:     'post',
-                                       state:     'gallery-edit',
-                                       title:     wp.media.view.l10n.editGalleryTitle,
+                                       state:     state,
+                                       title:     this.editTitle,
                                        editing:   true,
                                        multiple:  true,
                                        selection: selection
 
                                return this.frame;
                        }
-               };
-       }());
+               });
+       };
 
+       wp.media.gallery = new wp.media.collection({
+               tag: 'gallery',
+               type : 'image',
+               editTitle : wp.media.view.l10n.editGalleryTitle,
+               defaults : {
+                       itemtag: 'dl',
+                       icontag: 'dt',
+                       captiontag: 'dd',
+                       columns: '3',
+                       link: 'post',
+                       size: 'thumbnail',
+                       order: 'ASC',
+                       id: wp.media.view.settings.post && wp.media.view.settings.post.id,
+                       orderby : 'menu_order ID'
+               }
+       });
+
+       /**
+        * wp.media.featuredImage
+        * @namespace
+        */
        wp.media.featuredImage = {
+               /**
+                * Get the featured image post ID
+                *
+                * @global wp.media.view.settings
+                *
+                * @returns {wp.media.view.settings.post.featuredImageId|number}
+                */
                get: function() {
                        return wp.media.view.settings.post.featuredImageId;
                },
-
+               /**
+                * Set the featured image id, save the post thumbnail data and
+                * set the HTML in the post meta box to the new featured image.
+                *
+                * @global wp.media.view.settings
+                * @global wp.media.post
+                *
+                * @param {number} id The post ID of the featured image, or -1 to unset it.
+                */
                set: function( id ) {
                        var settings = wp.media.view.settings;
 
                                $( '.inside', '#postimagediv' ).html( html );
                        });
                },
-
+               /**
+                * The Featured Image workflow
+                *
+                * @global wp.media.controller.FeaturedImage
+                * @global wp.media.view.l10n
+                *
+                * @this wp.media.featuredImage
+                *
+                * @returns {wp.media.view.MediaFrame.Select} A media workflow.
+                */
                frame: function() {
-                       if ( this._frame )
+                       if ( this._frame ) {
                                return this._frame;
+                       }
 
                        this._frame = wp.media({
                                state: 'featured-image',
-                               states: [ new wp.media.controller.FeaturedImage() ]
+                               states: [ new wp.media.controller.FeaturedImage() , new wp.media.controller.EditImage() ]
                        });
 
                        this._frame.on( 'toolbar:create:featured-image', function( toolbar ) {
+                               /**
+                                * @this wp.media.view.MediaFrame.Select
+                                */
                                this.createSelectToolbar( toolbar, {
                                        text: wp.media.view.l10n.setFeaturedImage
                                });
                        }, this._frame );
 
+                       this._frame.on( 'content:render:edit-image', function() {
+                               var selection = this.state('featured-image').get('selection'),
+                                       view = new wp.media.view.EditImage( { model: selection.single(), controller: this } ).render();
+
+                               this.content.set( view );
+
+                               // after bringing in the frame, load the actual editor via an ajax call
+                               view.loadEditor();
+
+                       }, this._frame );
+
                        this._frame.state('featured-image').on( 'select', this.select );
                        return this._frame;
                },
-
+               /**
+                * 'select' callback for Featured Image workflow, triggered when
+                *  the 'Set Featured Image' button is clicked in the media modal.
+                *
+                * @global wp.media.view.settings
+                *
+                * @this wp.media.controller.FeaturedImage
+                */
                select: function() {
-                       var settings = wp.media.view.settings,
-                               selection = this.get('selection').single();
+                       var selection = this.get('selection').single();
 
-                       if ( ! settings.post.featuredImageId )
+                       if ( ! wp.media.view.settings.post.featuredImageId ) {
                                return;
+                       }
 
                        wp.media.featuredImage.set( selection ? selection.id : -1 );
                },
-
+               /**
+                * Open the content media manager to the 'featured image' tab when
+                * the post thumbnail is clicked.
+                *
+                * Update the featured image id when the 'remove' link is clicked.
+                *
+                * @global wp.media.view.settings
+                */
                init: function() {
-                       // Open the content media manager to the 'featured image' tab when
-                       // the post thumbnail is clicked.
                        $('#postimagediv').on( 'click', '#set-post-thumbnail', function( event ) {
                                event.preventDefault();
                                // Stop propagation to prevent thickbox from activating.
                                event.stopPropagation();
 
                                wp.media.featuredImage.frame().open();
-
-                       // Update the featured image id when the 'remove' link is clicked.
                        }).on( 'click', '#remove-post-thumbnail', function() {
                                wp.media.view.settings.post.featuredImageId = -1;
                        });
 
        $( wp.media.featuredImage.init );
 
+       /**
+        * wp.media.editor
+        * @namespace
+        */
        wp.media.editor = {
-               insert: function( h ) {
-                       var mce = typeof(tinymce) != 'undefined',
-                               qt = typeof(QTags) != 'undefined',
-                               wpActiveEditor = window.wpActiveEditor,
-                               ed;
+               /**
+                * Send content to the editor
+                *
+                * @global tinymce
+                * @global QTags
+                * @global wpActiveEditor
+                * @global tb_remove() - Possibly overloaded by legacy plugins
+                *
+                * @param {string} html Content to send to the editor
+                */
+               insert: function( html ) {
+                       var editor,
+                               hasTinymce = ! _.isUndefined( window.tinymce ),
+                               hasQuicktags = ! _.isUndefined( window.QTags ),
+                               wpActiveEditor = window.wpActiveEditor;
 
                        // Delegate to the global `send_to_editor` if it exists.
                        // This attempts to play nice with any themes/plugins that have
                        // overridden the insert functionality.
-                       if ( window.send_to_editor )
+                       if ( window.send_to_editor ) {
                                return window.send_to_editor.apply( this, arguments );
+                       }
 
                        if ( ! wpActiveEditor ) {
-                               if ( mce && tinymce.activeEditor ) {
-                                       ed = tinymce.activeEditor;
-                                       wpActiveEditor = window.wpActiveEditor = ed.id;
-                               } else if ( !qt ) {
+                               if ( hasTinymce && tinymce.activeEditor ) {
+                                       editor = tinymce.activeEditor;
+                                       wpActiveEditor = window.wpActiveEditor = editor.id;
+                               } else if ( ! hasQuicktags ) {
                                        return false;
                                }
-                       } else if ( mce ) {
-                               if ( tinymce.activeEditor && (tinymce.activeEditor.id == 'mce_fullscreen' || tinymce.activeEditor.id == 'wp_mce_fullscreen') )
-                                       ed = tinymce.activeEditor;
-                               else
-                                       ed = tinymce.get(wpActiveEditor);
+                       } else if ( hasTinymce ) {
+                               editor = tinymce.get( wpActiveEditor );
                        }
 
-                       if ( ed && !ed.isHidden() ) {
-                               // restore caret position on IE
-                               if ( tinymce.isIE && ed.windowManager.insertimagebookmark )
-                                       ed.selection.moveToBookmark(ed.windowManager.insertimagebookmark);
-
-                               if ( h.indexOf('[caption') !== -1 ) {
-                                       if ( ed.wpSetImgCaption )
-                                               h = ed.wpSetImgCaption(h);
-                               } else if ( h.indexOf('[gallery') !== -1 ) {
-                                       if ( ed.plugins.wpgallery )
-                                               h = ed.plugins.wpgallery._do_gallery(h);
-                               } else if ( h.indexOf('[embed') === 0 ) {
-                                       if ( ed.plugins.wordpress )
-                                               h = ed.plugins.wordpress._setEmbed(h);
-                               }
-
-                               ed.execCommand('mceInsertContent', false, h);
-                       } else if ( qt ) {
-                               QTags.insertContent(h);
+                       if ( editor && ! editor.isHidden() ) {
+                               editor.execCommand( 'mceInsertContent', false, html );
+                       } else if ( hasQuicktags ) {
+                               QTags.insertContent( html );
                        } else {
-                               document.getElementById(wpActiveEditor).value += h;
+                               document.getElementById( wpActiveEditor ).value += html;
                        }
 
                        // If the old thickbox remove function exists, call it in case
                        // a theme/plugin overloaded it.
-                       if ( window.tb_remove )
+                       if ( window.tb_remove ) {
                                try { window.tb_remove(); } catch( e ) {}
+                       }
                },
 
+               /**
+                * Setup 'workflow' and add to the 'workflows' cache. 'open' can
+                *  subsequently be called upon it.
+                *
+                * @global wp.media.view.l10n
+                *
+                * @param {string} id A slug used to identify the workflow.
+                * @param {Object} [options={}]
+                *
+                * @this wp.media.editor
+                *
+                * @returns {wp.media.view.MediaFrame.Select} A media workflow.
+                */
                add: function( id, options ) {
                        var workflow = this.get( id );
 
-                       if ( workflow ) // only add once: if exists return existing
+                       // only add once: if exists return existing
+                       if ( workflow ) {
                                return workflow;
+                       }
 
                        workflow = workflows[ id ] = wp.media( _.defaults( options || {}, {
                                frame:    'post',
 
                                $.when.apply( $, selection.map( function( attachment ) {
                                        var display = state.display( attachment ).toJSON();
+                                       /**
+                                        * @this wp.media.editor
+                                        */
                                        return this.send.attachment( display, attachment.toJSON() );
                                }, this ) ).done( function() {
                                        wp.media.editor.insert( _.toArray( arguments ).join('\n\n') );
                        }, this );
 
                        workflow.state('gallery-edit').on( 'update', function( selection ) {
+                               /**
+                                * @this wp.media.editor
+                                */
                                this.insert( wp.media.gallery.shortcode( selection ).string() );
                        }, this );
 
+                       workflow.state('playlist-edit').on( 'update', function( selection ) {
+                               /**
+                                * @this wp.media.editor
+                                */
+                               this.insert( wp.media.playlist.shortcode( selection ).string() );
+                       }, this );
+
+                       workflow.state('video-playlist-edit').on( 'update', function( selection ) {
+                               /**
+                                * @this wp.media.editor
+                                */
+                               this.insert( wp.media.playlist.shortcode( selection ).string() );
+                       }, this );
+
                        workflow.state('embed').on( 'select', function() {
+                               /**
+                                * @this wp.media.editor
+                                */
                                var state = workflow.state(),
                                        type = state.get('type'),
                                        embed = state.props.toJSON();
                                                link:    'none'
                                        });
 
-                                       if ( 'none' === embed.link )
+                                       if ( 'none' === embed.link ) {
                                                embed.linkUrl = '';
-                                       else if ( 'file' === embed.link )
+                                       } else if ( 'file' === embed.link ) {
                                                embed.linkUrl = embed.url;
+                                       }
 
                                        this.insert( wp.media.string.image( embed ) );
                                }
                        workflow.setState( workflow.options.state );
                        return workflow;
                },
-
+               /**
+                * Determines the proper current workflow id
+                *
+                * @global wpActiveEditor
+                * @global tinymce
+                *
+                * @param {string} [id=''] A slug used to identify the workflow.
+                *
+                * @returns {wpActiveEditor|string|tinymce.activeEditor.id}
+                */
                id: function( id ) {
-                       if ( id )
+                       if ( id ) {
                                return id;
+                       }
 
                        // If an empty `id` is provided, default to `wpActiveEditor`.
                        id = wpActiveEditor;
 
                        // If that doesn't work, fall back to `tinymce.activeEditor.id`.
-                       if ( ! id && typeof tinymce !== 'undefined' && tinymce.activeEditor )
+                       if ( ! id && ! _.isUndefined( window.tinymce ) && tinymce.activeEditor ) {
                                id = tinymce.activeEditor.id;
+                       }
 
                        // Last but not least, fall back to the empty string.
                        id = id || '';
                        return id;
                },
-
+               /**
+                * Return the workflow specified by id
+                *
+                * @param {string} id A slug used to identify the workflow.
+                *
+                * @this wp.media.editor
+                *
+                * @returns {wp.media.view.MediaFrame} A media workflow.
+                */
                get: function( id ) {
                        id = this.id( id );
                        return workflows[ id ];
                },
-
+               /**
+                * Remove the workflow represented by id from the workflow cache
+                *
+                * @param {string} id A slug used to identify the workflow.
+                *
+                * @this wp.media.editor
+                */
                remove: function( id ) {
                        id = this.id( id );
                        delete workflows[ id ];
                },
-
+               /**
+                * @namespace
+                */
                send: {
+                       /**
+                        * Called when sending an attachment to the editor
+                        *   from the medial modal.
+                        *
+                        * @global wp.media.view.settings
+                        * @global wp.media.post
+                        *
+                        * @param {Object} props Attachment details (align, link, size, etc).
+                        * @param {Object} attachment The attachment object, media version of Post.
+                        * @returns {Promise}
+                        */
                        attachment: function( props, attachment ) {
                                var caption = attachment.caption,
                                        options, html;
 
                                // If captions are disabled, clear the caption.
-                               if ( ! wp.media.view.settings.captions )
+                               if ( ! wp.media.view.settings.captions ) {
                                        delete attachment.caption;
+                               }
 
                                props = wp.media.string.props( props, attachment );
 
                                        post_excerpt: caption
                                };
 
-                               if ( props.linkUrl )
+                               if ( props.linkUrl ) {
                                        options.url = props.linkUrl;
+                               }
 
                                if ( 'image' === attachment.type ) {
                                        html = wp.media.string.image( props );
                                        post_id:    wp.media.view.settings.post.id
                                });
                        },
-
+                       /**
+                        * Called when 'Insert From URL' source is not an image. Example: YouTube url.
+                        *
+                        * @global wp.media.view.settings
+                        *
+                        * @param {Object} embed
+                        * @returns {Promise}
+                        */
                        link: function( embed ) {
                                return wp.media.post( 'send-link-to-editor', {
                                        nonce:   wp.media.view.settings.nonce.sendToEditor,
                                });
                        }
                },
-
+               /**
+                * Open a workflow
+                *
+                * @param {string} [id=undefined] Optional. A slug used to identify the workflow.
+                * @param {Object} [options={}]
+                *
+                * @this wp.media.editor
+                *
+                * @returns {wp.media.view.MediaFrame}
+                */
                open: function( id, options ) {
-                       var workflow, editor;
+                       var workflow;
 
                        options = options || {};
 
                        id = this.id( id );
-
+/*
                        // Save a bookmark of the caret position in IE.
-                       if ( typeof tinymce !== 'undefined' ) {
+                       if ( ! _.isUndefined( window.tinymce ) ) {
                                editor = tinymce.get( id );
 
                                if ( tinymce.isIE && editor && ! editor.isHidden() ) {
                                        editor.windowManager.insertimagebookmark = editor.selection.getBookmark();
                                }
                        }
-
+*/
                        workflow = this.get( id );
 
                        // Redo workflow if state has changed
-                       if ( ! workflow || ( workflow.options && options.state !== workflow.options.state ) )
+                       if ( ! workflow || ( workflow.options && options.state !== workflow.options.state ) ) {
                                workflow = this.add( id, options );
+                       }
 
                        return workflow.open();
                },
 
+               /**
+                * Bind click event for .insert-media using event delegation
+                *
+                * @global wp.media.view.l10n
+                */
                init: function() {
-                       $(document.body).on( 'click', '.insert-media', function( event ) {
-                               var $this = $(this),
-                                       editor = $this.data('editor'),
-                                       options = {
-                                               frame:    'post',
-                                               state:    'insert',
-                                               title:    wp.media.view.l10n.addMedia,
-                                               multiple: true
-                                       };
-
-                               event.preventDefault();
-
-                               // Remove focus from the `.insert-media` button.
-                               // Prevents Opera from showing the outline of the button
-                               // above the modal.
-                               //
-                               // See: http://core.trac.wordpress.org/ticket/22445
-                               $this.blur();
-
-                               if ( $this.hasClass( 'gallery' ) ) {
-                                       options.state = 'gallery';
-                                       options.title = wp.media.view.l10n.createGalleryTitle;
-                               }
+                       $(document.body)
+                               .on( 'click', '.insert-media', function( event ) {
+                                       var elem = $( event.currentTarget ),
+                                               editor = elem.data('editor'),
+                                               options = {
+                                                       frame:    'post',
+                                                       state:    'insert',
+                                                       title:    wp.media.view.l10n.addMedia,
+                                                       multiple: true
+                                               };
+
+                                       event.preventDefault();
+
+                                       // Remove focus from the `.insert-media` button.
+                                       // Prevents Opera from showing the outline of the button
+                                       // above the modal.
+                                       //
+                                       // See: http://core.trac.wordpress.org/ticket/22445
+                                       elem.blur();
+
+                                       if ( elem.hasClass( 'gallery' ) ) {
+                                               options.state = 'gallery';
+                                               options.title = wp.media.view.l10n.createGalleryTitle;
+                                       }
+
+                                       wp.media.editor.open( editor, options );
+                               });
 
-                               wp.media.editor.open( editor, options );
-                       });
+                       // Initialize and render the Editor drag-and-drop uploader.
+                       new wp.media.view.EditorUploader().render();
                }
        };
 
        _.bindAll( wp.media.editor, 'open' );
        $( wp.media.editor.init );
-}(jQuery));
+}(jQuery, _));