X-Git-Url: https://scripts.mit.edu/gitweb/autoinstalls/wordpress.git/blobdiff_plain/fa11948979fd6a4ea5705dc613b239699a459db3..f5fcdc7994bb67cce809bc4777944ae8b7fad4a4:/wp-includes/js/media-editor.js diff --git a/wp-includes/js/media-editor.js b/wp-includes/js/media-editor.js index 5e2a5fa6..9d84a4b2 100644 --- a/wp-includes/js/media-editor.js +++ b/wp-includes/js/media-editor.js @@ -1,13 +1,52 @@ +/* global getUserSetting, tinymce, QTags */ + // 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; @@ -26,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 || {}, { @@ -39,18 +79,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. @@ -76,7 +118,15 @@ 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; @@ -90,20 +140,45 @@ } }; - 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; @@ -114,11 +189,17 @@ 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(); @@ -137,7 +218,17 @@ 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; @@ -145,16 +236,18 @@ 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(' '); @@ -182,14 +275,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', @@ -202,49 +298,105 @@ } }; - 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.embed = { + coerce : wp.media.coerce, + + defaults : { + url : '', + width: '', + height: '' + }, + + edit : function( data, isURL ) { + var frame, props = {}, shortcode; + + if ( isURL ) { + props.url = data.replace(/<[^>]+>/g, ''); + } else { + shortcode = wp.shortcode.next( 'embed', data ).shortcode; + + props = _.defaults( shortcode.attrs.named, this.defaults ); + if ( shortcode.content ) { + props.url = shortcode.content; + } + } + + frame = wp.media({ + frame: 'post', + state: 'embed', + metadata: props + }); + return frame; + }, + + shortcode : function( model ) { + var self = this, content; + + _.each( this.defaults, function( value, key ) { + model[ key ] = self.coerce( model, key ); + + if ( value === model[ key ] ) { + delete model[ key ]; + } + }); + + content = model.url; + delete model.url; + + return new wp.shortcode({ + tag: 'embed', + attrs: model, + content: content + }); + } + }; + + wp.media.collection = function(attributes) { + var collections = {}; + + return _.extend( { + 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 ) { @@ -254,27 +406,50 @@ 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; - 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 @@ -282,33 +457,31 @@ 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 ] ) - delete attrs[ key ]; - }); + attrs = this.setDefaults( attrs ); shortcode = new wp.shortcode({ - tag: 'gallery', + tag: this.tag, attrs: attrs, type: 'single' }); @@ -317,35 +490,51 @@ 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. @@ -357,29 +546,104 @@ }); // 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 }).open(); return this.frame; + }, + + setDefaults: function( attrs ) { + var self = this; + // Remove default attributes from the shortcode. + _.each( this.defaults, function( value, key ) { + attrs[ key ] = self.coerce( attrs, key ); + if ( value === attrs[ key ] ) { + delete attrs[ key ]; + } + }); + + return attrs; } - }; - }()); + }, attributes ); + }; + + wp.media._galleryDefaults = { + 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' + }; + + if ( wp.media.view.settings.galleryDefaults ) { + wp.media.galleryDefaults = _.extend( {}, wp.media._galleryDefaults, wp.media.view.settings.galleryDefaults ); + } else { + wp.media.galleryDefaults = wp.media._galleryDefaults; + } + + wp.media.gallery = new wp.media.collection({ + tag: 'gallery', + type : 'image', + editTitle : wp.media.view.l10n.editGalleryTitle, + defaults : wp.media.galleryDefaults, + + setDefaults: function( attrs ) { + var self = this, changed = ! _.isEqual( wp.media.galleryDefaults, wp.media._galleryDefaults ); + _.each( this.defaults, function( value, key ) { + attrs[ key ] = self.coerce( attrs, key ); + if ( value === attrs[ key ] && ( ! changed || value === wp.media._galleryDefaults[ key ] ) ) { + delete attrs[ key ]; + } + } ); + return attrs; + } + }); + /** + * 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; @@ -394,47 +658,81 @@ $( '.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; }); @@ -443,67 +741,85 @@ $( 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, wpActiveEditor, + hasTinymce = ! _.isUndefined( window.tinymce ), + hasQuicktags = ! _.isUndefined( window.QTags ); + + if ( this.activeEditor ) { + wpActiveEditor = window.wpActiveEditor = this.activeEditor; + } else { + 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', @@ -522,17 +838,40 @@ $.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") ); + 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(); @@ -541,7 +880,7 @@ if ( 'link' === type ) { _.defaults( embed, { - title: embed.url, + linkText: embed.url, linkUrl: embed.url }); @@ -557,10 +896,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 ) ); } @@ -570,41 +910,80 @@ 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; + id = window.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 ); @@ -614,8 +993,9 @@ post_excerpt: caption }; - if ( props.linkUrl ) + if ( props.linkUrl ) { options.url = props.linkUrl; + } if ( 'image' === attachment.type ) { html = wp.media.string.image( props ); @@ -644,74 +1024,91 @@ 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, - src: embed.linkUrl, - title: embed.title, - html: wp.media.string.link( embed ), - post_id: wp.media.view.settings.post.id + nonce: wp.media.view.settings.nonce.sendToEditor, + src: embed.linkUrl, + link_text: embed.linkText, + html: wp.media.string.link( embed ), + post_id: wp.media.view.settings.post.id }); } }, - + /** + * 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' ) { - editor = tinymce.get( id ); - - if ( tinymce.isIE && editor && ! editor.isHidden() ) { - editor.focus(); - editor.windowManager.insertimagebookmark = editor.selection.getBookmark(); - } - } + this.activeEditor = id; 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.add-media-button', '.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: https://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, _));