diff --git a/wp-includes/js/media-editor.js b/wp-includes/js/media-editor.js index 7a95f6b12b..b3975784e2 100644 --- a/wp-includes/js/media-editor.js +++ b/wp-includes/js/media-editor.js @@ -3,13 +3,28 @@ // WordPress, TinyMCE, and Media // ----------------------------- (function($){ - // Stores the editors' `wp.media.controller.Frame` instances. + /** + * Stores the editors' `wp.media.controller.Frame` instances. + * + * @static + */ var workflows = {}; + /** + * wp.media.string + */ 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.defaultProps + * + * @param {Object} props + * @param {Object} attachment + * @returns {Object} Joined props + */ props: function( props, attachment ) { var link, linkUrl, size, sizes, fallbacks, defaultProps = wp.media.view.settings.defaultProps; @@ -28,8 +43,9 @@ props = props ? _.clone( props ) : {}; - if ( attachment && attachment.type ) + if ( attachment && attachment.type ) { props.type = attachment.type; + } if ( 'image' === props.type ) { props = _.defaults( props || {}, { @@ -41,18 +57,20 @@ } // 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. @@ -79,6 +97,15 @@ return fallbacks( props ); }, + /** + * Create the markup for a link + * + * @global wp.html.string + * + * @param {Object} props + * @param {Object} attachment + * @returns {string} The link markup + */ link: function( props, attachment ) { var options; @@ -92,20 +119,45 @@ } }; - if ( props.rel ) + if ( props.rel ) { options.attrs.rel = props.rel; + } return wp.html.string( options ); }, - + /** + * Create an Audio shortcode + * + * @param {Object} props + * @param {Object} attachment + * @returns {string} The audio shortcode + */ audio: function( props, attachment ) { return wp.media.string._audioVideo( 'audio', props, attachment ); }, - + /** + * Create a Video shortcode + * + * @param {Object} props + * @param {Object} attachment + * @returns {string} The video shortcode + */ video: function( props, attachment ) { return wp.media.string._audioVideo( 'video', props, attachment ); }, - + /** + * Helper function to create a media shortcode + * + * @access private + * + * @global wp.shortcode + * @global wp.media.view.settings + * + * @param {string} type The shortcode tag name: 'audio' or 'video'. + * @param {Object} props + * @param {Object} attachment + * @returns {string} The media shortcode + */ _audioVideo: function( type, props, attachment ) { var shortcode, html, extension; @@ -116,11 +168,13 @@ shortcode = {}; if ( 'video' === type ) { - if ( attachment.width ) + if ( attachment.width ) { shortcode.width = attachment.width; + } - if ( attachment.height ) + if ( attachment.height ) { shortcode.height = attachment.height; + } } extension = attachment.filename.split('.').pop(); @@ -139,7 +193,16 @@ return html; }, - + /** + * Create image markup, optionally with a link and/or wrapped in a caption shortcode + * + * @global wp.html + * @global wp.shortcode + * + * @param {Object} props + * @param {Object} attachment + * @returns {string} + */ image: function( props, attachment ) { var img = {}, options, classes, shortcode, html; @@ -152,11 +215,13 @@ // 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(' '); @@ -184,14 +249,17 @@ 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', @@ -204,10 +272,23 @@ } }; + /** + * wp.media.gallery + * + * @type {Object} + */ wp.media.gallery = (function() { + /** + * + * @static + * @type object + */ var galleries = {}; return { + /** + * @global wp.media.view.settings + */ defaults: { order: 'ASC', id: wp.media.view.settings.post.id, @@ -220,6 +301,12 @@ orderby: 'menu_order ID' }, + /** + * @global wp.media.query + * + * @param {Object} shortcode + * @returns {Object} + */ attachments: function( shortcode ) { var shortcodeString = shortcode.string(), result = galleries[ shortcodeString ], @@ -227,8 +314,9 @@ delete galleries[ shortcodeString ]; - if ( result ) + if ( result ) { return result; + } // Fill the default shortcode attributes. attrs = _.defaults( shortcode.attrs.named, wp.media.gallery.defaults ); @@ -238,15 +326,17 @@ 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 ) { @@ -256,11 +346,13 @@ 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' ); @@ -270,13 +362,21 @@ return query; }, + /** + * @global wp.shortcode + * @global wp.media.model.Attachments + * + * @param {Object} attachments + * @returns {wp.shortcode} + */ shortcode: function( attachments ) { var props = attachments.props.toJSON(), attrs = _.pick( props, 'orderby', 'order' ), shortcode, clone; - if ( attachments.gallery ) + if ( attachments.gallery ) { _.extend( attrs, attachments.gallery.toJSON() ); + } // Convert all gallery shortcodes to use the `ids` property. // Ignore `post__in` and `post__not_in`; the attachments in @@ -284,24 +384,27 @@ 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 ) { @@ -324,21 +427,30 @@ return shortcode; }, - + /** + * @global wp.shortcode + * @global wp.media.model.Selection + * @global wp.media.view.l10n + * + * @param {string} content + * @returns {wp.media.view.MediaFrame} A media workflow. + */ edit: function( content ) { var shortcode = wp.shortcode.next( 'gallery', content ), defaultPostId = wp.media.gallery.defaults.id, attachments, selection; // 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 ); @@ -359,8 +471,9 @@ }); // Destroy the previous gallery frame. - if ( this.frame ) + if ( this.frame ) { this.frame.dispose(); + } // Store the current gallery frame. this.frame = wp.media({ @@ -377,11 +490,30 @@ }; }()); + /** + * wp.media.featuredImage + */ 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 + */ set: function( id ) { var settings = wp.media.view.settings; @@ -396,10 +528,18 @@ $( '.inside', '#postimagediv' ).html( html ); }); }, - + /** + * The Featured Image workflow + * + * @global wp.media.controller.FeaturedImage + * @global wp.media.view.l10n + * + * @returns {wp.media.view.MediaFrame} A media workflow. + */ frame: function() { - if ( this._frame ) + if ( this._frame ) { return this._frame; + } this._frame = wp.media({ state: 'featured-image', @@ -415,28 +555,35 @@ this._frame.state('featured-image').on( 'select', this.select ); return this._frame; }, - + /** + * @global wp.media.view.settings + */ select: function() { var settings = wp.media.view.settings, selection = this.get('selection').single(); - if ( ! settings.post.featuredImageId ) + if ( ! 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; }); @@ -445,7 +592,16 @@ $( wp.media.featuredImage.init ); + /** + * wp.media.editor + */ wp.media.editor = { + /** + * @global tinymce + * @global QTags + * + * @param {string} html + */ insert: function( html ) { var editor, hasTinymce = typeof tinymce !== 'undefined', @@ -485,11 +641,21 @@ } }, + /** + * @global wp.media.view.l10n + * + * @param {string} id A slug used to identify the workflow. + * @param {Object} [options={}] + * + * @returns {wp.media.view.MediaFrame} 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', @@ -543,10 +709,11 @@ 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 ) ); } @@ -557,40 +724,69 @@ return workflow; }, + /** + * Determines the proper current workflow id + * + * @global wpActiveEditor + * @global tinymce + * + * @param {string} id + * @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 && typeof tinymce !== 'undefined' && 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 + * @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 + */ remove: function( id ) { id = this.id( id ); delete workflows[ id ]; }, send: { + /** + * @global wp.media.view.settings + * @global wp.media.post + * + * @param {Object} props + * @param {Object} attachment + * @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 ); @@ -600,8 +796,9 @@ post_excerpt: caption }; - if ( props.linkUrl ) + if ( props.linkUrl ) { options.url = props.linkUrl; + } if ( 'image' === attachment.type ) { html = wp.media.string.image( props ); @@ -630,7 +827,12 @@ post_id: wp.media.view.settings.post.id }); }, - + /** + * @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, @@ -641,7 +843,11 @@ }); } }, - + /** + * @param {string} id + * @param {Object} options + * @returns {wp.media.view.MediaFrame} + */ open: function( id, options ) { var workflow, editor; @@ -662,12 +868,18 @@ 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),