wp/wp-includes/js/media-editor.js
changeset 5 5e2f62d02dcd
parent 0 d970ebf37754
child 7 cf61fcea0001
equal deleted inserted replaced
4:346c88efed21 5:5e2f62d02dcd
       
     1 /* global getUserSetting, tinymce, QTags */
       
     2 
     1 // WordPress, TinyMCE, and Media
     3 // WordPress, TinyMCE, and Media
     2 // -----------------------------
     4 // -----------------------------
     3 (function($){
     5 (function($, _){
     4 	// Stores the editors' `wp.media.controller.Frame` instances.
     6 	/**
       
     7 	 * Stores the editors' `wp.media.controller.Frame` instances.
       
     8 	 *
       
     9 	 * @static
       
    10 	 */
     5 	var workflows = {};
    11 	var workflows = {};
     6 
    12 
       
    13 	/**
       
    14 	 * A helper mixin function to avoid truthy and falsey values being
       
    15 	 *   passed as an input that expects booleans. If key is undefined in the map,
       
    16 	 *   but has a default value, set it.
       
    17 	 *
       
    18 	 * @param {object} attrs Map of props from a shortcode or settings.
       
    19 	 * @param {string} key The key within the passed map to check for a value.
       
    20 	 * @returns {mixed|undefined} The original or coerced value of key within attrs
       
    21 	 */
       
    22 	wp.media.coerce = function ( attrs, key ) {
       
    23 		if ( _.isUndefined( attrs[ key ] ) && ! _.isUndefined( this.defaults[ key ] ) ) {
       
    24 			attrs[ key ] = this.defaults[ key ];
       
    25 		} else if ( 'true' === attrs[ key ] ) {
       
    26 			attrs[ key ] = true;
       
    27 		} else if ( 'false' === attrs[ key ] ) {
       
    28 			attrs[ key ] = false;
       
    29 		}
       
    30 		return attrs[ key ];
       
    31 	};
       
    32 
       
    33 	/**
       
    34 	 * wp.media.string
       
    35 	 * @namespace
       
    36 	 */
     7 	wp.media.string = {
    37 	wp.media.string = {
     8 		// Joins the `props` and `attachment` objects,
    38 		/**
     9 		// outputting the proper object format based on the
    39 		 * Joins the `props` and `attachment` objects,
    10 		// attachment's type.
    40 		 * outputting the proper object format based on the
       
    41 		 * attachment's type.
       
    42 		 *
       
    43 		 * @global wp.media.view.settings
       
    44 		 * @global getUserSetting()
       
    45 		 *
       
    46 		 * @param {Object} [props={}] Attachment details (align, link, size, etc).
       
    47 		 * @param {Object} attachment The attachment object, media version of Post.
       
    48 		 * @returns {Object} Joined props
       
    49 		 */
    11 		props: function( props, attachment ) {
    50 		props: function( props, attachment ) {
    12 			var link, linkUrl, size, sizes, fallbacks,
    51 			var link, linkUrl, size, sizes, fallbacks,
    13 				defaultProps = wp.media.view.settings.defaultProps;
    52 				defaultProps = wp.media.view.settings.defaultProps;
    14 
    53 
    15 			// Final fallbacks run after all processing has been completed.
    54 			// Final fallbacks run after all processing has been completed.
    24 				return props;
    63 				return props;
    25 			};
    64 			};
    26 
    65 
    27 			props = props ? _.clone( props ) : {};
    66 			props = props ? _.clone( props ) : {};
    28 
    67 
    29 			if ( attachment && attachment.type )
    68 			if ( attachment && attachment.type ) {
    30 				props.type = attachment.type;
    69 				props.type = attachment.type;
       
    70 			}
    31 
    71 
    32 			if ( 'image' === props.type ) {
    72 			if ( 'image' === props.type ) {
    33 				props = _.defaults( props || {}, {
    73 				props = _.defaults( props || {}, {
    34 					align:   defaultProps.align || getUserSetting( 'align', 'none' ),
    74 					align:   defaultProps.align || getUserSetting( 'align', 'none' ),
    35 					size:    defaultProps.size  || getUserSetting( 'imgsize', 'medium' ),
    75 					size:    defaultProps.size  || getUserSetting( 'imgsize', 'medium' ),
    37 					classes: []
    77 					classes: []
    38 				});
    78 				});
    39 			}
    79 			}
    40 
    80 
    41 			// All attachment-specific settings follow.
    81 			// All attachment-specific settings follow.
    42 			if ( ! attachment )
    82 			if ( ! attachment ) {
    43 				return fallbacks( props );
    83 				return fallbacks( props );
       
    84 			}
    44 
    85 
    45 			props.title = props.title || attachment.title;
    86 			props.title = props.title || attachment.title;
    46 
    87 
    47 			link = props.link || defaultProps.link || getUserSetting( 'urlbutton', 'file' );
    88 			link = props.link || defaultProps.link || getUserSetting( 'urlbutton', 'file' );
    48 			if ( 'file' === link || 'embed' === link )
    89 			if ( 'file' === link || 'embed' === link ) {
    49 				linkUrl = attachment.url;
    90 				linkUrl = attachment.url;
    50 			else if ( 'post' === link )
    91 			} else if ( 'post' === link ) {
    51 				linkUrl = attachment.link;
    92 				linkUrl = attachment.link;
    52 			else if ( 'custom' === link )
    93 			} else if ( 'custom' === link ) {
    53 				linkUrl = props.linkUrl;
    94 				linkUrl = props.linkUrl;
       
    95 			}
    54 			props.linkUrl = linkUrl || '';
    96 			props.linkUrl = linkUrl || '';
    55 
    97 
    56 			// Format properties for images.
    98 			// Format properties for images.
    57 			if ( 'image' === attachment.type ) {
    99 			if ( 'image' === attachment.type ) {
    58 				props.classes.push( 'wp-image-' + attachment.id );
   100 				props.classes.push( 'wp-image-' + attachment.id );
    74 				props.rel = props.rel || 'attachment wp-att-' + attachment.id;
   116 				props.rel = props.rel || 'attachment wp-att-' + attachment.id;
    75 			}
   117 			}
    76 
   118 
    77 			return fallbacks( props );
   119 			return fallbacks( props );
    78 		},
   120 		},
    79 
   121 		/**
       
   122 		 * Create link markup that is suitable for passing to the editor
       
   123 		 *
       
   124 		 * @global wp.html.string
       
   125 		 *
       
   126 		 * @param {Object} props Attachment details (align, link, size, etc).
       
   127 		 * @param {Object} attachment The attachment object, media version of Post.
       
   128 		 * @returns {string} The link markup
       
   129 		 */
    80 		link: function( props, attachment ) {
   130 		link: function( props, attachment ) {
    81 			var options;
   131 			var options;
    82 
   132 
    83 			props = wp.media.string.props( props, attachment );
   133 			props = wp.media.string.props( props, attachment );
    84 
   134 
    88 				attrs:   {
   138 				attrs:   {
    89 					href: props.linkUrl
   139 					href: props.linkUrl
    90 				}
   140 				}
    91 			};
   141 			};
    92 
   142 
    93 			if ( props.rel )
   143 			if ( props.rel ) {
    94 				options.attrs.rel = props.rel;
   144 				options.attrs.rel = props.rel;
       
   145 			}
    95 
   146 
    96 			return wp.html.string( options );
   147 			return wp.html.string( options );
    97 		},
   148 		},
    98 
   149 		/**
       
   150 		 * Create an Audio shortcode string that is suitable for passing to the editor
       
   151 		 *
       
   152 		 * @param {Object} props Attachment details (align, link, size, etc).
       
   153 		 * @param {Object} attachment The attachment object, media version of Post.
       
   154 		 * @returns {string} The audio shortcode
       
   155 		 */
    99 		audio: function( props, attachment ) {
   156 		audio: function( props, attachment ) {
   100 			return wp.media.string._audioVideo( 'audio', props, attachment );
   157 			return wp.media.string._audioVideo( 'audio', props, attachment );
   101 		},
   158 		},
   102 
   159 		/**
       
   160 		 * Create a Video shortcode string that is suitable for passing to the editor
       
   161 		 *
       
   162 		 * @param {Object} props Attachment details (align, link, size, etc).
       
   163 		 * @param {Object} attachment The attachment object, media version of Post.
       
   164 		 * @returns {string} The video shortcode
       
   165 		 */
   103 		video: function( props, attachment ) {
   166 		video: function( props, attachment ) {
   104 			return wp.media.string._audioVideo( 'video', props, attachment );
   167 			return wp.media.string._audioVideo( 'video', props, attachment );
   105 		},
   168 		},
   106 
   169 		/**
       
   170 		 * Helper function to create a media shortcode string
       
   171 		 *
       
   172 		 * @access private
       
   173 		 *
       
   174 		 * @global wp.shortcode
       
   175 		 * @global wp.media.view.settings
       
   176 		 *
       
   177 		 * @param {string} type The shortcode tag name: 'audio' or 'video'.
       
   178 		 * @param {Object} props Attachment details (align, link, size, etc).
       
   179 		 * @param {Object} attachment The attachment object, media version of Post.
       
   180 		 * @returns {string} The media shortcode
       
   181 		 */
   107 		_audioVideo: function( type, props, attachment ) {
   182 		_audioVideo: function( type, props, attachment ) {
   108 			var shortcode, html, extension;
   183 			var shortcode, html, extension;
   109 
   184 
   110 			props = wp.media.string.props( props, attachment );
   185 			props = wp.media.string.props( props, attachment );
   111 			if ( props.link !== 'embed' )
   186 			if ( props.link !== 'embed' )
   112 				return wp.media.string.link( props );
   187 				return wp.media.string.link( props );
   113 
   188 
   114 			shortcode = {};
   189 			shortcode = {};
   115 
   190 
   116 			if ( 'video' === type ) {
   191 			if ( 'video' === type ) {
   117 				if ( attachment.width )
   192 				if ( attachment.image && -1 === attachment.image.src.indexOf( attachment.icon ) ) {
       
   193 					shortcode.poster = attachment.image.src;
       
   194 				}
       
   195 
       
   196 				if ( attachment.width ) {
   118 					shortcode.width = attachment.width;
   197 					shortcode.width = attachment.width;
   119 
   198 				}
   120 				if ( attachment.height )
   199 
       
   200 				if ( attachment.height ) {
   121 					shortcode.height = attachment.height;
   201 					shortcode.height = attachment.height;
       
   202 				}
   122 			}
   203 			}
   123 
   204 
   124 			extension = attachment.filename.split('.').pop();
   205 			extension = attachment.filename.split('.').pop();
   125 
   206 
   126 			if ( _.contains( wp.media.view.settings.embedExts, extension ) ) {
   207 			if ( _.contains( wp.media.view.settings.embedExts, extension ) ) {
   135 				attrs:   shortcode
   216 				attrs:   shortcode
   136 			});
   217 			});
   137 
   218 
   138 			return html;
   219 			return html;
   139 		},
   220 		},
   140 
   221 		/**
       
   222 		 * Create image markup, optionally with a link and/or wrapped in a caption shortcode,
       
   223 		 *  that is suitable for passing to the editor
       
   224 		 *
       
   225 		 * @global wp.html
       
   226 		 * @global wp.shortcode
       
   227 		 *
       
   228 		 * @param {Object} props Attachment details (align, link, size, etc).
       
   229 		 * @param {Object} attachment The attachment object, media version of Post.
       
   230 		 * @returns {string}
       
   231 		 */
   141 		image: function( props, attachment ) {
   232 		image: function( props, attachment ) {
   142 			var img = {},
   233 			var img = {},
   143 				options, classes, shortcode, html;
   234 				options, classes, shortcode, html;
   144 
   235 
   145 			props = wp.media.string.props( props, attachment );
   236 			props = wp.media.string.props( props, attachment );
   146 			classes = props.classes || [];
   237 			classes = props.classes || [];
   147 
   238 
   148 			img.src = typeof attachment !== 'undefined' ? attachment.url : props.url;
   239 			img.src = ! _.isUndefined( attachment ) ? attachment.url : props.url;
   149 			_.extend( img, _.pick( props, 'width', 'height', 'alt' ) );
   240 			_.extend( img, _.pick( props, 'width', 'height', 'alt' ) );
   150 
   241 
   151 			// Only assign the align class to the image if we're not printing
   242 			// Only assign the align class to the image if we're not printing
   152 			// a caption, since the alignment is sent to the shortcode.
   243 			// a caption, since the alignment is sent to the shortcode.
   153 			if ( props.align && ! props.caption )
   244 			if ( props.align && ! props.caption ) {
   154 				classes.push( 'align' + props.align );
   245 				classes.push( 'align' + props.align );
   155 
   246 			}
   156 			if ( props.size )
   247 
       
   248 			if ( props.size ) {
   157 				classes.push( 'size-' + props.size );
   249 				classes.push( 'size-' + props.size );
       
   250 			}
   158 
   251 
   159 			img['class'] = _.compact( classes ).join(' ');
   252 			img['class'] = _.compact( classes ).join(' ');
   160 
   253 
   161 			// Generate `img` tag options.
   254 			// Generate `img` tag options.
   162 			options = {
   255 			options = {
   180 
   273 
   181 			// Generate the caption shortcode.
   274 			// Generate the caption shortcode.
   182 			if ( props.caption ) {
   275 			if ( props.caption ) {
   183 				shortcode = {};
   276 				shortcode = {};
   184 
   277 
   185 				if ( img.width )
   278 				if ( img.width ) {
   186 					shortcode.width = img.width;
   279 					shortcode.width = img.width;
   187 
   280 				}
   188 				if ( props.captionId )
   281 
       
   282 				if ( props.captionId ) {
   189 					shortcode.id = props.captionId;
   283 					shortcode.id = props.captionId;
   190 
   284 				}
   191 				if ( props.align )
   285 
       
   286 				if ( props.align ) {
   192 					shortcode.align = 'align' + props.align;
   287 					shortcode.align = 'align' + props.align;
       
   288 				}
   193 
   289 
   194 				html = wp.shortcode.string({
   290 				html = wp.shortcode.string({
   195 					tag:     'caption',
   291 					tag:     'caption',
   196 					attrs:   shortcode,
   292 					attrs:   shortcode,
   197 					content: html + ' ' + props.caption
   293 					content: html + ' ' + props.caption
   200 
   296 
   201 			return html;
   297 			return html;
   202 		}
   298 		}
   203 	};
   299 	};
   204 
   300 
   205 	wp.media.gallery = (function() {
   301 	wp.media.embed = {
   206 		var galleries = {};
   302 		coerce : wp.media.coerce,
   207 
   303 
   208 		return {
   304 		defaults : {
   209 			defaults: {
   305 			url : '',
   210 				order:      'ASC',
   306 			width: '',
   211 				id:         wp.media.view.settings.post.id,
   307 			height: ''
   212 				itemtag:    'dl',
   308 		},
   213 				icontag:    'dt',
   309 
   214 				captiontag: 'dd',
   310 		edit : function( data, isURL ) {
   215 				columns:    '3',
   311 			var frame, props = {}, shortcode;
   216 				link:       'post',
   312 
   217 				size:       'thumbnail',
   313 			if ( isURL ) {
   218 				orderby:    'menu_order ID'
   314 				props.url = data.replace(/<[^>]+>/g, '');
   219 			},
   315 			} else {
   220 
   316 				shortcode = wp.shortcode.next( 'embed', data ).shortcode;
       
   317 
       
   318 				props = _.defaults( shortcode.attrs.named, this.defaults );
       
   319 				if ( shortcode.content ) {
       
   320 					props.url = shortcode.content;
       
   321 				}
       
   322 			}
       
   323 
       
   324 			frame = wp.media({
       
   325 				frame: 'post',
       
   326 				state: 'embed',
       
   327 				metadata: props
       
   328 			});
       
   329 
       
   330 			return frame;
       
   331 		},
       
   332 
       
   333 		shortcode : function( model ) {
       
   334 			var self = this, content;
       
   335 
       
   336 			_.each( this.defaults, function( value, key ) {
       
   337 				model[ key ] = self.coerce( model, key );
       
   338 
       
   339 				if ( value === model[ key ] ) {
       
   340 					delete model[ key ];
       
   341 				}
       
   342 			});
       
   343 
       
   344 			content = model.url;
       
   345 			delete model.url;
       
   346 
       
   347 			return new wp.shortcode({
       
   348 				tag: 'embed',
       
   349 				attrs: model,
       
   350 				content: content
       
   351 			});
       
   352 		}
       
   353 	};
       
   354 
       
   355 	wp.media.collection = function(attributes) {
       
   356 		var collections = {};
       
   357 
       
   358 		return _.extend( {
       
   359 			coerce : wp.media.coerce,
       
   360 			/**
       
   361 			 * Retrieve attachments based on the properties of the passed shortcode
       
   362 			 *
       
   363 			 * @global wp.media.query
       
   364 			 *
       
   365 			 * @param {wp.shortcode} shortcode An instance of wp.shortcode().
       
   366 			 * @returns {wp.media.model.Attachments} A Backbone.Collection containing
       
   367 			 *      the media items belonging to a collection.
       
   368 			 *      The query[ this.tag ] property is a Backbone.Model
       
   369 			 *          containing the 'props' for the collection.
       
   370 			 */
   221 			attachments: function( shortcode ) {
   371 			attachments: function( shortcode ) {
   222 				var shortcodeString = shortcode.string(),
   372 				var shortcodeString = shortcode.string(),
   223 					result = galleries[ shortcodeString ],
   373 					result = collections[ shortcodeString ],
   224 					attrs, args, query, others;
   374 					attrs, args, query, others, self = this;
   225 
   375 
   226 				delete galleries[ shortcodeString ];
   376 				delete collections[ shortcodeString ];
   227 
   377 				if ( result ) {
   228 				if ( result )
       
   229 					return result;
   378 					return result;
   230 
   379 				}
   231 				// Fill the default shortcode attributes.
   380 				// Fill the default shortcode attributes.
   232 				attrs = _.defaults( shortcode.attrs.named, wp.media.gallery.defaults );
   381 				attrs = _.defaults( shortcode.attrs.named, this.defaults );
   233 				args  = _.pick( attrs, 'orderby', 'order' );
   382 				args  = _.pick( attrs, 'orderby', 'order' );
   234 
   383 
   235 				args.type    = 'image';
   384 				args.type    = this.type;
   236 				args.perPage = -1;
   385 				args.perPage = -1;
   237 
   386 
   238 				// Mark the `orderby` override attribute.
   387 				// Mark the `orderby` override attribute.
   239 				if( undefined !== attrs.orderby )
   388 				if ( undefined !== attrs.orderby ) {
   240 					attrs._orderByField = attrs.orderby;
   389 					attrs._orderByField = attrs.orderby;
   241 
   390 				}
   242 				if ( 'rand' === attrs.orderby )
   391 
       
   392 				if ( 'rand' === attrs.orderby ) {
   243 					attrs._orderbyRandom = true;
   393 					attrs._orderbyRandom = true;
       
   394 				}
   244 
   395 
   245 				// Map the `orderby` attribute to the corresponding model property.
   396 				// Map the `orderby` attribute to the corresponding model property.
   246 				if ( ! attrs.orderby || /^menu_order(?: ID)?$/i.test( attrs.orderby ) )
   397 				if ( ! attrs.orderby || /^menu_order(?: ID)?$/i.test( attrs.orderby ) ) {
   247 					args.orderby = 'menuOrder';
   398 					args.orderby = 'menuOrder';
       
   399 				}
   248 
   400 
   249 				// Map the `ids` param to the correct query args.
   401 				// Map the `ids` param to the correct query args.
   250 				if ( attrs.ids ) {
   402 				if ( attrs.ids ) {
   251 					args.post__in = attrs.ids.split(',');
   403 					args.post__in = attrs.ids.split(',');
   252 					args.orderby  = 'post__in';
   404 					args.orderby  = 'post__in';
   253 				} else if ( attrs.include ) {
   405 				} else if ( attrs.include ) {
   254 					args.post__in = attrs.include.split(',');
   406 					args.post__in = attrs.include.split(',');
   255 				}
   407 				}
   256 
   408 
   257 				if ( attrs.exclude )
   409 				if ( attrs.exclude ) {
   258 					args.post__not_in = attrs.exclude.split(',');
   410 					args.post__not_in = attrs.exclude.split(',');
   259 
   411 				}
   260 				if ( ! args.post__in )
   412 
       
   413 				if ( ! args.post__in ) {
   261 					args.uploadedTo = attrs.id;
   414 					args.uploadedTo = attrs.id;
       
   415 				}
   262 
   416 
   263 				// Collect the attributes that were not included in `args`.
   417 				// Collect the attributes that were not included in `args`.
   264 				others = _.omit( attrs, 'id', 'ids', 'include', 'exclude', 'orderby', 'order' );
   418 				others = _.omit( attrs, 'id', 'ids', 'include', 'exclude', 'orderby', 'order' );
   265 
   419 
       
   420 				_.each( this.defaults, function( value, key ) {
       
   421 					others[ key ] = self.coerce( others, key );
       
   422 				});
       
   423 
   266 				query = wp.media.query( args );
   424 				query = wp.media.query( args );
   267 				query.gallery = new Backbone.Model( others );
   425 				query[ this.tag ] = new Backbone.Model( others );
   268 				return query;
   426 				return query;
   269 			},
   427 			},
   270 
   428 			/**
       
   429 			 * Triggered when clicking 'Insert {label}' or 'Update {label}'
       
   430 			 *
       
   431 			 * @global wp.shortcode
       
   432 			 * @global wp.media.model.Attachments
       
   433 			 *
       
   434 			 * @param {wp.media.model.Attachments} attachments A Backbone.Collection containing
       
   435 			 *      the media items belonging to a collection.
       
   436 			 *      The query[ this.tag ] property is a Backbone.Model
       
   437 			 *          containing the 'props' for the collection.
       
   438 			 * @returns {wp.shortcode}
       
   439 			 */
   271 			shortcode: function( attachments ) {
   440 			shortcode: function( attachments ) {
   272 				var props = attachments.props.toJSON(),
   441 				var props = attachments.props.toJSON(),
   273 					attrs = _.pick( props, 'orderby', 'order' ),
   442 					attrs = _.pick( props, 'orderby', 'order' ),
   274 					shortcode, clone;
   443 					shortcode, clone;
   275 
   444 
   276 				if ( attachments.gallery )
   445 				if ( attachments.type ) {
   277 					_.extend( attrs, attachments.gallery.toJSON() );
   446 					attrs.type = attachments.type;
       
   447 					delete attachments.type;
       
   448 				}
       
   449 
       
   450 				if ( attachments[this.tag] ) {
       
   451 					_.extend( attrs, attachments[this.tag].toJSON() );
       
   452 				}
   278 
   453 
   279 				// Convert all gallery shortcodes to use the `ids` property.
   454 				// Convert all gallery shortcodes to use the `ids` property.
   280 				// Ignore `post__in` and `post__not_in`; the attachments in
   455 				// Ignore `post__in` and `post__not_in`; the attachments in
   281 				// the collection will already reflect those properties.
   456 				// the collection will already reflect those properties.
   282 				attrs.ids = attachments.pluck('id');
   457 				attrs.ids = attachments.pluck('id');
   283 
   458 
   284 				// Copy the `uploadedTo` post ID.
   459 				// Copy the `uploadedTo` post ID.
   285 				if ( props.uploadedTo )
   460 				if ( props.uploadedTo ) {
   286 					attrs.id = props.uploadedTo;
   461 					attrs.id = props.uploadedTo;
   287 
   462 				}
   288 				// Check if the gallery is randomly ordered.
   463 				// Check if the gallery is randomly ordered.
   289 				delete attrs.orderby;
   464 				delete attrs.orderby;
   290 
   465 
   291 				if ( attrs._orderbyRandom )
   466 				if ( attrs._orderbyRandom ) {
   292 					attrs.orderby = 'rand';
   467 					attrs.orderby = 'rand';
   293 				else if ( attrs._orderByField && attrs._orderByField != 'rand' )
   468 				} else if ( attrs._orderByField && attrs._orderByField != 'rand' ) {
   294 					attrs.orderby = attrs._orderByField;
   469 					attrs.orderby = attrs._orderByField;
       
   470 				}
   295 
   471 
   296 				delete attrs._orderbyRandom;
   472 				delete attrs._orderbyRandom;
   297 				delete attrs._orderByField;
   473 				delete attrs._orderByField;
   298 
   474 
   299 				// If the `ids` attribute is set and `orderby` attribute
   475 				// If the `ids` attribute is set and `orderby` attribute
   300 				// is the default value, clear it for cleaner output.
   476 				// is the default value, clear it for cleaner output.
   301 				if ( attrs.ids && 'post__in' === attrs.orderby )
   477 				if ( attrs.ids && 'post__in' === attrs.orderby ) {
   302 					delete attrs.orderby;
   478 					delete attrs.orderby;
   303 
   479 				}
   304 				// Remove default attributes from the shortcode.
   480 
   305 				_.each( wp.media.gallery.defaults, function( value, key ) {
   481 				attrs = this.setDefaults( attrs );
   306 					if ( value === attrs[ key ] )
       
   307 						delete attrs[ key ];
       
   308 				});
       
   309 
   482 
   310 				shortcode = new wp.shortcode({
   483 				shortcode = new wp.shortcode({
   311 					tag:    'gallery',
   484 					tag:    this.tag,
   312 					attrs:  attrs,
   485 					attrs:  attrs,
   313 					type:   'single'
   486 					type:   'single'
   314 				});
   487 				});
   315 
   488 
   316 				// Use a cloned version of the gallery.
   489 				// Use a cloned version of the gallery.
   317 				clone = new wp.media.model.Attachments( attachments.models, {
   490 				clone = new wp.media.model.Attachments( attachments.models, {
   318 					props: props
   491 					props: props
   319 				});
   492 				});
   320 				clone.gallery = attachments.gallery;
   493 				clone[ this.tag ] = attachments[ this.tag ];
   321 				galleries[ shortcode.string() ] = clone;
   494 				collections[ shortcode.string() ] = clone;
   322 
   495 
   323 				return shortcode;
   496 				return shortcode;
   324 			},
   497 			},
   325 
   498 			/**
       
   499 			 * Triggered when double-clicking a collection shortcode placeholder
       
   500 			 *   in the editor
       
   501 			 *
       
   502 			 * @global wp.shortcode
       
   503 			 * @global wp.media.model.Selection
       
   504 			 * @global wp.media.view.l10n
       
   505 			 *
       
   506 			 * @param {string} content Content that is searched for possible
       
   507 			 *    shortcode markup matching the passed tag name,
       
   508 			 *
       
   509 			 * @this wp.media.{prop}
       
   510 			 *
       
   511 			 * @returns {wp.media.view.MediaFrame.Select} A media workflow.
       
   512 			 */
   326 			edit: function( content ) {
   513 			edit: function( content ) {
   327 				var shortcode = wp.shortcode.next( 'gallery', content ),
   514 				var shortcode = wp.shortcode.next( this.tag, content ),
   328 					defaultPostId = wp.media.gallery.defaults.id,
   515 					defaultPostId = this.defaults.id,
   329 					attachments, selection;
   516 					attachments, selection, state;
   330 
   517 
   331 				// Bail if we didn't match the shortcode or all of the content.
   518 				// Bail if we didn't match the shortcode or all of the content.
   332 				if ( ! shortcode || shortcode.content !== content )
   519 				if ( ! shortcode || shortcode.content !== content ) {
   333 					return;
   520 					return;
       
   521 				}
   334 
   522 
   335 				// Ignore the rest of the match object.
   523 				// Ignore the rest of the match object.
   336 				shortcode = shortcode.shortcode;
   524 				shortcode = shortcode.shortcode;
   337 
   525 
   338 				if ( _.isUndefined( shortcode.get('id') ) && ! _.isUndefined( defaultPostId ) )
   526 				if ( _.isUndefined( shortcode.get('id') ) && ! _.isUndefined( defaultPostId ) ) {
   339 					shortcode.set( 'id', defaultPostId );
   527 					shortcode.set( 'id', defaultPostId );
   340 
   528 				}
   341 				attachments = wp.media.gallery.attachments( shortcode );
   529 
       
   530 				attachments = this.attachments( shortcode );
   342 
   531 
   343 				selection = new wp.media.model.Selection( attachments.models, {
   532 				selection = new wp.media.model.Selection( attachments.models, {
   344 					props:    attachments.props.toJSON(),
   533 					props:    attachments.props.toJSON(),
   345 					multiple: true
   534 					multiple: true
   346 				});
   535 				});
   347 
   536 
   348 				selection.gallery = attachments.gallery;
   537 				selection[ this.tag ] = attachments[ this.tag ];
   349 
   538 
   350 				// Fetch the query's attachments, and then break ties from the
   539 				// Fetch the query's attachments, and then break ties from the
   351 				// query to allow for sorting.
   540 				// query to allow for sorting.
   352 				selection.more().done( function() {
   541 				selection.more().done( function() {
   353 					// Break ties with the query.
   542 					// Break ties with the query.
   355 					selection.unmirror();
   544 					selection.unmirror();
   356 					selection.props.unset('orderby');
   545 					selection.props.unset('orderby');
   357 				});
   546 				});
   358 
   547 
   359 				// Destroy the previous gallery frame.
   548 				// Destroy the previous gallery frame.
   360 				if ( this.frame )
   549 				if ( this.frame ) {
   361 					this.frame.dispose();
   550 					this.frame.dispose();
   362 
   551 				}
   363 				// Store the current gallery frame.
   552 
       
   553 				if ( shortcode.attrs.named.type && 'video' === shortcode.attrs.named.type ) {
       
   554 					state = 'video-' + this.tag + '-edit';
       
   555 				} else {
       
   556 					state = this.tag + '-edit';
       
   557 				}
       
   558 
       
   559 				// Store the current frame.
   364 				this.frame = wp.media({
   560 				this.frame = wp.media({
   365 					frame:     'post',
   561 					frame:     'post',
   366 					state:     'gallery-edit',
   562 					state:     state,
   367 					title:     wp.media.view.l10n.editGalleryTitle,
   563 					title:     this.editTitle,
   368 					editing:   true,
   564 					editing:   true,
   369 					multiple:  true,
   565 					multiple:  true,
   370 					selection: selection
   566 					selection: selection
   371 				}).open();
   567 				}).open();
   372 
   568 
   373 				return this.frame;
   569 				return this.frame;
   374 			}
   570 			},
   375 		};
   571 
   376 	}());
   572 			setDefaults: function( attrs ) {
   377 
   573 				var self = this;
       
   574 				// Remove default attributes from the shortcode.
       
   575 				_.each( this.defaults, function( value, key ) {
       
   576 					attrs[ key ] = self.coerce( attrs, key );
       
   577 					if ( value === attrs[ key ] ) {
       
   578 						delete attrs[ key ];
       
   579 					}
       
   580 				});
       
   581 
       
   582 				return attrs;
       
   583 			}
       
   584 		}, attributes );
       
   585 	};
       
   586 
       
   587 	wp.media._galleryDefaults = {
       
   588 		itemtag: 'dl',
       
   589 		icontag: 'dt',
       
   590 		captiontag: 'dd',
       
   591 		columns: '3',
       
   592 		link: 'post',
       
   593 		size: 'thumbnail',
       
   594 		order: 'ASC',
       
   595 		id: wp.media.view.settings.post && wp.media.view.settings.post.id,
       
   596 		orderby : 'menu_order ID'
       
   597 	};
       
   598 
       
   599 	if ( wp.media.view.settings.galleryDefaults ) {
       
   600 		wp.media.galleryDefaults = _.extend( {}, wp.media._galleryDefaults, wp.media.view.settings.galleryDefaults );
       
   601 	} else {
       
   602 		wp.media.galleryDefaults = wp.media._galleryDefaults;
       
   603 	}
       
   604 
       
   605 	wp.media.gallery = new wp.media.collection({
       
   606 		tag: 'gallery',
       
   607 		type : 'image',
       
   608 		editTitle : wp.media.view.l10n.editGalleryTitle,
       
   609 		defaults : wp.media.galleryDefaults,
       
   610 
       
   611 		setDefaults: function( attrs ) {
       
   612 			var self = this, changed = ! _.isEqual( wp.media.galleryDefaults, wp.media._galleryDefaults );
       
   613 			_.each( this.defaults, function( value, key ) {
       
   614 				attrs[ key ] = self.coerce( attrs, key );
       
   615 				if ( value === attrs[ key ] && ( ! changed || value === wp.media._galleryDefaults[ key ] ) ) {
       
   616 					delete attrs[ key ];
       
   617 				}
       
   618 			} );
       
   619 			return attrs;
       
   620 		}
       
   621 	});
       
   622 
       
   623 	/**
       
   624 	 * wp.media.featuredImage
       
   625 	 * @namespace
       
   626 	 */
   378 	wp.media.featuredImage = {
   627 	wp.media.featuredImage = {
       
   628 		/**
       
   629 		 * Get the featured image post ID
       
   630 		 *
       
   631 		 * @global wp.media.view.settings
       
   632 		 *
       
   633 		 * @returns {wp.media.view.settings.post.featuredImageId|number}
       
   634 		 */
   379 		get: function() {
   635 		get: function() {
   380 			return wp.media.view.settings.post.featuredImageId;
   636 			return wp.media.view.settings.post.featuredImageId;
   381 		},
   637 		},
   382 
   638 		/**
       
   639 		 * Set the featured image id, save the post thumbnail data and
       
   640 		 * set the HTML in the post meta box to the new featured image.
       
   641 		 *
       
   642 		 * @global wp.media.view.settings
       
   643 		 * @global wp.media.post
       
   644 		 *
       
   645 		 * @param {number} id The post ID of the featured image, or -1 to unset it.
       
   646 		 */
   383 		set: function( id ) {
   647 		set: function( id ) {
   384 			var settings = wp.media.view.settings;
   648 			var settings = wp.media.view.settings;
   385 
   649 
   386 			settings.post.featuredImageId = id;
   650 			settings.post.featuredImageId = id;
   387 
   651 
   392 				_wpnonce:     settings.post.nonce
   656 				_wpnonce:     settings.post.nonce
   393 			}).done( function( html ) {
   657 			}).done( function( html ) {
   394 				$( '.inside', '#postimagediv' ).html( html );
   658 				$( '.inside', '#postimagediv' ).html( html );
   395 			});
   659 			});
   396 		},
   660 		},
   397 
   661 		/**
       
   662 		 * The Featured Image workflow
       
   663 		 *
       
   664 		 * @global wp.media.controller.FeaturedImage
       
   665 		 * @global wp.media.view.l10n
       
   666 		 *
       
   667 		 * @this wp.media.featuredImage
       
   668 		 *
       
   669 		 * @returns {wp.media.view.MediaFrame.Select} A media workflow.
       
   670 		 */
   398 		frame: function() {
   671 		frame: function() {
   399 			if ( this._frame )
   672 			if ( this._frame ) {
   400 				return this._frame;
   673 				return this._frame;
       
   674 			}
   401 
   675 
   402 			this._frame = wp.media({
   676 			this._frame = wp.media({
   403 				state: 'featured-image',
   677 				state: 'featured-image',
   404 				states: [ new wp.media.controller.FeaturedImage() ]
   678 				states: [ new wp.media.controller.FeaturedImage() , new wp.media.controller.EditImage() ]
   405 			});
   679 			});
   406 
   680 
   407 			this._frame.on( 'toolbar:create:featured-image', function( toolbar ) {
   681 			this._frame.on( 'toolbar:create:featured-image', function( toolbar ) {
       
   682 				/**
       
   683 				 * @this wp.media.view.MediaFrame.Select
       
   684 				 */
   408 				this.createSelectToolbar( toolbar, {
   685 				this.createSelectToolbar( toolbar, {
   409 					text: wp.media.view.l10n.setFeaturedImage
   686 					text: wp.media.view.l10n.setFeaturedImage
   410 				});
   687 				});
   411 			}, this._frame );
   688 			}, this._frame );
   412 
   689 
       
   690 			this._frame.on( 'content:render:edit-image', function() {
       
   691 				var selection = this.state('featured-image').get('selection'),
       
   692 					view = new wp.media.view.EditImage( { model: selection.single(), controller: this } ).render();
       
   693 
       
   694 				this.content.set( view );
       
   695 
       
   696 				// after bringing in the frame, load the actual editor via an ajax call
       
   697 				view.loadEditor();
       
   698 
       
   699 			}, this._frame );
       
   700 
   413 			this._frame.state('featured-image').on( 'select', this.select );
   701 			this._frame.state('featured-image').on( 'select', this.select );
   414 			return this._frame;
   702 			return this._frame;
   415 		},
   703 		},
   416 
   704 		/**
       
   705 		 * 'select' callback for Featured Image workflow, triggered when
       
   706 		 *  the 'Set Featured Image' button is clicked in the media modal.
       
   707 		 *
       
   708 		 * @global wp.media.view.settings
       
   709 		 *
       
   710 		 * @this wp.media.controller.FeaturedImage
       
   711 		 */
   417 		select: function() {
   712 		select: function() {
   418 			var settings = wp.media.view.settings,
   713 			var selection = this.get('selection').single();
   419 				selection = this.get('selection').single();
   714 
   420 
   715 			if ( ! wp.media.view.settings.post.featuredImageId ) {
   421 			if ( ! settings.post.featuredImageId )
       
   422 				return;
   716 				return;
       
   717 			}
   423 
   718 
   424 			wp.media.featuredImage.set( selection ? selection.id : -1 );
   719 			wp.media.featuredImage.set( selection ? selection.id : -1 );
   425 		},
   720 		},
   426 
   721 		/**
       
   722 		 * Open the content media manager to the 'featured image' tab when
       
   723 		 * the post thumbnail is clicked.
       
   724 		 *
       
   725 		 * Update the featured image id when the 'remove' link is clicked.
       
   726 		 *
       
   727 		 * @global wp.media.view.settings
       
   728 		 */
   427 		init: function() {
   729 		init: function() {
   428 			// Open the content media manager to the 'featured image' tab when
       
   429 			// the post thumbnail is clicked.
       
   430 			$('#postimagediv').on( 'click', '#set-post-thumbnail', function( event ) {
   730 			$('#postimagediv').on( 'click', '#set-post-thumbnail', function( event ) {
   431 				event.preventDefault();
   731 				event.preventDefault();
   432 				// Stop propagation to prevent thickbox from activating.
   732 				// Stop propagation to prevent thickbox from activating.
   433 				event.stopPropagation();
   733 				event.stopPropagation();
   434 
   734 
   435 				wp.media.featuredImage.frame().open();
   735 				wp.media.featuredImage.frame().open();
   436 
       
   437 			// Update the featured image id when the 'remove' link is clicked.
       
   438 			}).on( 'click', '#remove-post-thumbnail', function() {
   736 			}).on( 'click', '#remove-post-thumbnail', function() {
   439 				wp.media.view.settings.post.featuredImageId = -1;
   737 				wp.media.view.settings.post.featuredImageId = -1;
   440 			});
   738 			});
   441 		}
   739 		}
   442 	};
   740 	};
   443 
   741 
   444 	$( wp.media.featuredImage.init );
   742 	$( wp.media.featuredImage.init );
   445 
   743 
       
   744 	/**
       
   745 	 * wp.media.editor
       
   746 	 * @namespace
       
   747 	 */
   446 	wp.media.editor = {
   748 	wp.media.editor = {
   447 		insert: function( h ) {
   749 		/**
   448 			var mce = typeof(tinymce) != 'undefined',
   750 		 * Send content to the editor
   449 				qt = typeof(QTags) != 'undefined',
   751 		 *
   450 				wpActiveEditor = window.wpActiveEditor,
   752 		 * @global tinymce
   451 				ed;
   753 		 * @global QTags
       
   754 		 * @global wpActiveEditor
       
   755 		 * @global tb_remove() - Possibly overloaded by legacy plugins
       
   756 		 *
       
   757 		 * @param {string} html Content to send to the editor
       
   758 		 */
       
   759 		insert: function( html ) {
       
   760 			var editor, wpActiveEditor,
       
   761 				hasTinymce = ! _.isUndefined( window.tinymce ),
       
   762 				hasQuicktags = ! _.isUndefined( window.QTags );
       
   763 
       
   764 			if ( this.activeEditor ) {
       
   765 				wpActiveEditor = window.wpActiveEditor = this.activeEditor;
       
   766 			} else {
       
   767 				wpActiveEditor = window.wpActiveEditor;
       
   768 			}
   452 
   769 
   453 			// Delegate to the global `send_to_editor` if it exists.
   770 			// Delegate to the global `send_to_editor` if it exists.
   454 			// This attempts to play nice with any themes/plugins that have
   771 			// This attempts to play nice with any themes/plugins that have
   455 			// overridden the insert functionality.
   772 			// overridden the insert functionality.
   456 			if ( window.send_to_editor )
   773 			if ( window.send_to_editor ) {
   457 				return window.send_to_editor.apply( this, arguments );
   774 				return window.send_to_editor.apply( this, arguments );
       
   775 			}
   458 
   776 
   459 			if ( ! wpActiveEditor ) {
   777 			if ( ! wpActiveEditor ) {
   460 				if ( mce && tinymce.activeEditor ) {
   778 				if ( hasTinymce && tinymce.activeEditor ) {
   461 					ed = tinymce.activeEditor;
   779 					editor = tinymce.activeEditor;
   462 					wpActiveEditor = window.wpActiveEditor = ed.id;
   780 					wpActiveEditor = window.wpActiveEditor = editor.id;
   463 				} else if ( !qt ) {
   781 				} else if ( ! hasQuicktags ) {
   464 					return false;
   782 					return false;
   465 				}
   783 				}
   466 			} else if ( mce ) {
   784 			} else if ( hasTinymce ) {
   467 				if ( tinymce.activeEditor && (tinymce.activeEditor.id == 'mce_fullscreen' || tinymce.activeEditor.id == 'wp_mce_fullscreen') )
   785 				editor = tinymce.get( wpActiveEditor );
   468 					ed = tinymce.activeEditor;
   786 			}
   469 				else
   787 
   470 					ed = tinymce.get(wpActiveEditor);
   788 			if ( editor && ! editor.isHidden() ) {
   471 			}
   789 				editor.execCommand( 'mceInsertContent', false, html );
   472 
   790 			} else if ( hasQuicktags ) {
   473 			if ( ed && !ed.isHidden() ) {
   791 				QTags.insertContent( html );
   474 				// restore caret position on IE
       
   475 				if ( tinymce.isIE && ed.windowManager.insertimagebookmark )
       
   476 					ed.selection.moveToBookmark(ed.windowManager.insertimagebookmark);
       
   477 
       
   478 				if ( h.indexOf('[caption') !== -1 ) {
       
   479 					if ( ed.wpSetImgCaption )
       
   480 						h = ed.wpSetImgCaption(h);
       
   481 				} else if ( h.indexOf('[gallery') !== -1 ) {
       
   482 					if ( ed.plugins.wpgallery )
       
   483 						h = ed.plugins.wpgallery._do_gallery(h);
       
   484 				} else if ( h.indexOf('[embed') === 0 ) {
       
   485 					if ( ed.plugins.wordpress )
       
   486 						h = ed.plugins.wordpress._setEmbed(h);
       
   487 				}
       
   488 
       
   489 				ed.execCommand('mceInsertContent', false, h);
       
   490 			} else if ( qt ) {
       
   491 				QTags.insertContent(h);
       
   492 			} else {
   792 			} else {
   493 				document.getElementById(wpActiveEditor).value += h;
   793 				document.getElementById( wpActiveEditor ).value += html;
   494 			}
   794 			}
   495 
   795 
   496 			// If the old thickbox remove function exists, call it in case
   796 			// If the old thickbox remove function exists, call it in case
   497 			// a theme/plugin overloaded it.
   797 			// a theme/plugin overloaded it.
   498 			if ( window.tb_remove )
   798 			if ( window.tb_remove ) {
   499 				try { window.tb_remove(); } catch( e ) {}
   799 				try { window.tb_remove(); } catch( e ) {}
   500 		},
   800 			}
   501 
   801 		},
       
   802 
       
   803 		/**
       
   804 		 * Setup 'workflow' and add to the 'workflows' cache. 'open' can
       
   805 		 *  subsequently be called upon it.
       
   806 		 *
       
   807 		 * @global wp.media.view.l10n
       
   808 		 *
       
   809 		 * @param {string} id A slug used to identify the workflow.
       
   810 		 * @param {Object} [options={}]
       
   811 		 *
       
   812 		 * @this wp.media.editor
       
   813 		 *
       
   814 		 * @returns {wp.media.view.MediaFrame.Select} A media workflow.
       
   815 		 */
   502 		add: function( id, options ) {
   816 		add: function( id, options ) {
   503 			var workflow = this.get( id );
   817 			var workflow = this.get( id );
   504 
   818 
   505 			if ( workflow ) // only add once: if exists return existing
   819 			// only add once: if exists return existing
       
   820 			if ( workflow ) {
   506 				return workflow;
   821 				return workflow;
       
   822 			}
   507 
   823 
   508 			workflow = workflows[ id ] = wp.media( _.defaults( options || {}, {
   824 			workflow = workflows[ id ] = wp.media( _.defaults( options || {}, {
   509 				frame:    'post',
   825 				frame:    'post',
   510 				state:    'insert',
   826 				state:    'insert',
   511 				title:    wp.media.view.l10n.addMedia,
   827 				title:    wp.media.view.l10n.addMedia,
   520 				if ( ! selection )
   836 				if ( ! selection )
   521 					return;
   837 					return;
   522 
   838 
   523 				$.when.apply( $, selection.map( function( attachment ) {
   839 				$.when.apply( $, selection.map( function( attachment ) {
   524 					var display = state.display( attachment ).toJSON();
   840 					var display = state.display( attachment ).toJSON();
       
   841 					/**
       
   842 					 * @this wp.media.editor
       
   843 					 */
   525 					return this.send.attachment( display, attachment.toJSON() );
   844 					return this.send.attachment( display, attachment.toJSON() );
   526 				}, this ) ).done( function() {
   845 				}, this ) ).done( function() {
   527 					wp.media.editor.insert( _.toArray( arguments ).join("\n\n") );
   846 					wp.media.editor.insert( _.toArray( arguments ).join('\n\n') );
   528 				});
   847 				});
   529 			}, this );
   848 			}, this );
   530 
   849 
   531 			workflow.state('gallery-edit').on( 'update', function( selection ) {
   850 			workflow.state('gallery-edit').on( 'update', function( selection ) {
       
   851 				/**
       
   852 				 * @this wp.media.editor
       
   853 				 */
   532 				this.insert( wp.media.gallery.shortcode( selection ).string() );
   854 				this.insert( wp.media.gallery.shortcode( selection ).string() );
   533 			}, this );
   855 			}, this );
   534 
   856 
       
   857 			workflow.state('playlist-edit').on( 'update', function( selection ) {
       
   858 				/**
       
   859 				 * @this wp.media.editor
       
   860 				 */
       
   861 				this.insert( wp.media.playlist.shortcode( selection ).string() );
       
   862 			}, this );
       
   863 
       
   864 			workflow.state('video-playlist-edit').on( 'update', function( selection ) {
       
   865 				/**
       
   866 				 * @this wp.media.editor
       
   867 				 */
       
   868 				this.insert( wp.media.playlist.shortcode( selection ).string() );
       
   869 			}, this );
       
   870 
   535 			workflow.state('embed').on( 'select', function() {
   871 			workflow.state('embed').on( 'select', function() {
       
   872 				/**
       
   873 				 * @this wp.media.editor
       
   874 				 */
   536 				var state = workflow.state(),
   875 				var state = workflow.state(),
   537 					type = state.get('type'),
   876 					type = state.get('type'),
   538 					embed = state.props.toJSON();
   877 					embed = state.props.toJSON();
   539 
   878 
   540 				embed.url = embed.url || '';
   879 				embed.url = embed.url || '';
   541 
   880 
   542 				if ( 'link' === type ) {
   881 				if ( 'link' === type ) {
   543 					_.defaults( embed, {
   882 					_.defaults( embed, {
   544 						title:   embed.url,
   883 						linkText: embed.url,
   545 						linkUrl: embed.url
   884 						linkUrl: embed.url
   546 					});
   885 					});
   547 
   886 
   548 					this.send.link( embed ).done( function( resp ) {
   887 					this.send.link( embed ).done( function( resp ) {
   549 						wp.media.editor.insert( resp );
   888 						wp.media.editor.insert( resp );
   555 						linkUrl: '',
   894 						linkUrl: '',
   556 						align:   'none',
   895 						align:   'none',
   557 						link:    'none'
   896 						link:    'none'
   558 					});
   897 					});
   559 
   898 
   560 					if ( 'none' === embed.link )
   899 					if ( 'none' === embed.link ) {
   561 						embed.linkUrl = '';
   900 						embed.linkUrl = '';
   562 					else if ( 'file' === embed.link )
   901 					} else if ( 'file' === embed.link ) {
   563 						embed.linkUrl = embed.url;
   902 						embed.linkUrl = embed.url;
       
   903 					}
   564 
   904 
   565 					this.insert( wp.media.string.image( embed ) );
   905 					this.insert( wp.media.string.image( embed ) );
   566 				}
   906 				}
   567 			}, this );
   907 			}, this );
   568 
   908 
   569 			workflow.state('featured-image').on( 'select', wp.media.featuredImage.select );
   909 			workflow.state('featured-image').on( 'select', wp.media.featuredImage.select );
   570 			workflow.setState( workflow.options.state );
   910 			workflow.setState( workflow.options.state );
   571 			return workflow;
   911 			return workflow;
   572 		},
   912 		},
   573 
   913 		/**
       
   914 		 * Determines the proper current workflow id
       
   915 		 *
       
   916 		 * @global wpActiveEditor
       
   917 		 * @global tinymce
       
   918 		 *
       
   919 		 * @param {string} [id=''] A slug used to identify the workflow.
       
   920 		 *
       
   921 		 * @returns {wpActiveEditor|string|tinymce.activeEditor.id}
       
   922 		 */
   574 		id: function( id ) {
   923 		id: function( id ) {
   575 			if ( id )
   924 			if ( id ) {
   576 				return id;
   925 				return id;
       
   926 			}
   577 
   927 
   578 			// If an empty `id` is provided, default to `wpActiveEditor`.
   928 			// If an empty `id` is provided, default to `wpActiveEditor`.
   579 			id = wpActiveEditor;
   929 			id = window.wpActiveEditor;
   580 
   930 
   581 			// If that doesn't work, fall back to `tinymce.activeEditor.id`.
   931 			// If that doesn't work, fall back to `tinymce.activeEditor.id`.
   582 			if ( ! id && typeof tinymce !== 'undefined' && tinymce.activeEditor )
   932 			if ( ! id && ! _.isUndefined( window.tinymce ) && tinymce.activeEditor ) {
   583 				id = tinymce.activeEditor.id;
   933 				id = tinymce.activeEditor.id;
       
   934 			}
   584 
   935 
   585 			// Last but not least, fall back to the empty string.
   936 			// Last but not least, fall back to the empty string.
   586 			id = id || '';
   937 			id = id || '';
   587 			return id;
   938 			return id;
   588 		},
   939 		},
   589 
   940 		/**
       
   941 		 * Return the workflow specified by id
       
   942 		 *
       
   943 		 * @param {string} id A slug used to identify the workflow.
       
   944 		 *
       
   945 		 * @this wp.media.editor
       
   946 		 *
       
   947 		 * @returns {wp.media.view.MediaFrame} A media workflow.
       
   948 		 */
   590 		get: function( id ) {
   949 		get: function( id ) {
   591 			id = this.id( id );
   950 			id = this.id( id );
   592 			return workflows[ id ];
   951 			return workflows[ id ];
   593 		},
   952 		},
   594 
   953 		/**
       
   954 		 * Remove the workflow represented by id from the workflow cache
       
   955 		 *
       
   956 		 * @param {string} id A slug used to identify the workflow.
       
   957 		 *
       
   958 		 * @this wp.media.editor
       
   959 		 */
   595 		remove: function( id ) {
   960 		remove: function( id ) {
   596 			id = this.id( id );
   961 			id = this.id( id );
   597 			delete workflows[ id ];
   962 			delete workflows[ id ];
   598 		},
   963 		},
   599 
   964 		/**
       
   965 		 * @namespace
       
   966 		 */
   600 		send: {
   967 		send: {
       
   968 			/**
       
   969 			 * Called when sending an attachment to the editor
       
   970 			 *   from the medial modal.
       
   971 			 *
       
   972 			 * @global wp.media.view.settings
       
   973 			 * @global wp.media.post
       
   974 			 *
       
   975 			 * @param {Object} props Attachment details (align, link, size, etc).
       
   976 			 * @param {Object} attachment The attachment object, media version of Post.
       
   977 			 * @returns {Promise}
       
   978 			 */
   601 			attachment: function( props, attachment ) {
   979 			attachment: function( props, attachment ) {
   602 				var caption = attachment.caption,
   980 				var caption = attachment.caption,
   603 					options, html;
   981 					options, html;
   604 
   982 
   605 				// If captions are disabled, clear the caption.
   983 				// If captions are disabled, clear the caption.
   606 				if ( ! wp.media.view.settings.captions )
   984 				if ( ! wp.media.view.settings.captions ) {
   607 					delete attachment.caption;
   985 					delete attachment.caption;
       
   986 				}
   608 
   987 
   609 				props = wp.media.string.props( props, attachment );
   988 				props = wp.media.string.props( props, attachment );
   610 
   989 
   611 				options = {
   990 				options = {
   612 					id:           attachment.id,
   991 					id:           attachment.id,
   613 					post_content: attachment.description,
   992 					post_content: attachment.description,
   614 					post_excerpt: caption
   993 					post_excerpt: caption
   615 				};
   994 				};
   616 
   995 
   617 				if ( props.linkUrl )
   996 				if ( props.linkUrl ) {
   618 					options.url = props.linkUrl;
   997 					options.url = props.linkUrl;
       
   998 				}
   619 
   999 
   620 				if ( 'image' === attachment.type ) {
  1000 				if ( 'image' === attachment.type ) {
   621 					html = wp.media.string.image( props );
  1001 					html = wp.media.string.image( props );
   622 
  1002 
   623 					_.each({
  1003 					_.each({
   642 					attachment: options,
  1022 					attachment: options,
   643 					html:       html,
  1023 					html:       html,
   644 					post_id:    wp.media.view.settings.post.id
  1024 					post_id:    wp.media.view.settings.post.id
   645 				});
  1025 				});
   646 			},
  1026 			},
   647 
  1027 			/**
       
  1028 			 * Called when 'Insert From URL' source is not an image. Example: YouTube url.
       
  1029 			 *
       
  1030 			 * @global wp.media.view.settings
       
  1031 			 *
       
  1032 			 * @param {Object} embed
       
  1033 			 * @returns {Promise}
       
  1034 			 */
   648 			link: function( embed ) {
  1035 			link: function( embed ) {
   649 				return wp.media.post( 'send-link-to-editor', {
  1036 				return wp.media.post( 'send-link-to-editor', {
   650 					nonce:   wp.media.view.settings.nonce.sendToEditor,
  1037 					nonce:     wp.media.view.settings.nonce.sendToEditor,
   651 					src:     embed.linkUrl,
  1038 					src:       embed.linkUrl,
   652 					title:   embed.title,
  1039 					link_text: embed.linkText,
   653 					html:    wp.media.string.link( embed ),
  1040 					html:      wp.media.string.link( embed ),
   654 					post_id: wp.media.view.settings.post.id
  1041 					post_id:   wp.media.view.settings.post.id
   655 				});
  1042 				});
   656 			}
  1043 			}
   657 		},
  1044 		},
   658 
  1045 		/**
       
  1046 		 * Open a workflow
       
  1047 		 *
       
  1048 		 * @param {string} [id=undefined] Optional. A slug used to identify the workflow.
       
  1049 		 * @param {Object} [options={}]
       
  1050 		 *
       
  1051 		 * @this wp.media.editor
       
  1052 		 *
       
  1053 		 * @returns {wp.media.view.MediaFrame}
       
  1054 		 */
   659 		open: function( id, options ) {
  1055 		open: function( id, options ) {
   660 			var workflow, editor;
  1056 			var workflow;
   661 
  1057 
   662 			options = options || {};
  1058 			options = options || {};
   663 
  1059 
   664 			id = this.id( id );
  1060 			id = this.id( id );
   665 
  1061 			this.activeEditor = id;
   666 			// Save a bookmark of the caret position in IE.
       
   667 			if ( typeof tinymce !== 'undefined' ) {
       
   668 				editor = tinymce.get( id );
       
   669 
       
   670 				if ( tinymce.isIE && editor && ! editor.isHidden() ) {
       
   671 					editor.focus();
       
   672 					editor.windowManager.insertimagebookmark = editor.selection.getBookmark();
       
   673 				}
       
   674 			}
       
   675 
  1062 
   676 			workflow = this.get( id );
  1063 			workflow = this.get( id );
   677 
  1064 
   678 			// Redo workflow if state has changed
  1065 			// Redo workflow if state has changed
   679 			if ( ! workflow || ( workflow.options && options.state !== workflow.options.state ) )
  1066 			if ( ! workflow || ( workflow.options && options.state !== workflow.options.state ) ) {
   680 				workflow = this.add( id, options );
  1067 				workflow = this.add( id, options );
       
  1068 			}
   681 
  1069 
   682 			return workflow.open();
  1070 			return workflow.open();
   683 		},
  1071 		},
   684 
  1072 
       
  1073 		/**
       
  1074 		 * Bind click event for .insert-media using event delegation
       
  1075 		 *
       
  1076 		 * @global wp.media.view.l10n
       
  1077 		 */
   685 		init: function() {
  1078 		init: function() {
   686 			$(document.body).on( 'click', '.insert-media', function( event ) {
  1079 			$(document.body)
   687 				var $this = $(this),
  1080 				.on( 'click.add-media-button', '.insert-media', function( event ) {
   688 					editor = $this.data('editor'),
  1081 					var elem = $( event.currentTarget ),
   689 					options = {
  1082 						editor = elem.data('editor'),
   690 						frame:    'post',
  1083 						options = {
   691 						state:    'insert',
  1084 							frame:    'post',
   692 						title:    wp.media.view.l10n.addMedia,
  1085 							state:    'insert',
   693 						multiple: true
  1086 							title:    wp.media.view.l10n.addMedia,
   694 					};
  1087 							multiple: true
   695 
  1088 						};
   696 				event.preventDefault();
  1089 
   697 
  1090 					event.preventDefault();
   698 				// Remove focus from the `.insert-media` button.
  1091 
   699 				// Prevents Opera from showing the outline of the button
  1092 					// Remove focus from the `.insert-media` button.
   700 				// above the modal.
  1093 					// Prevents Opera from showing the outline of the button
   701 				//
  1094 					// above the modal.
   702 				// See: http://core.trac.wordpress.org/ticket/22445
  1095 					//
   703 				$this.blur();
  1096 					// See: https://core.trac.wordpress.org/ticket/22445
   704 
  1097 					elem.blur();
   705 				if ( $this.hasClass( 'gallery' ) ) {
  1098 
   706 					options.state = 'gallery';
  1099 					if ( elem.hasClass( 'gallery' ) ) {
   707 					options.title = wp.media.view.l10n.createGalleryTitle;
  1100 						options.state = 'gallery';
   708 				}
  1101 						options.title = wp.media.view.l10n.createGalleryTitle;
   709 
  1102 					}
   710 				wp.media.editor.open( editor, options );
  1103 
   711 			});
  1104 					wp.media.editor.open( editor, options );
       
  1105 				});
       
  1106 
       
  1107 			// Initialize and render the Editor drag-and-drop uploader.
       
  1108 			new wp.media.view.EditorUploader().render();
   712 		}
  1109 		}
   713 	};
  1110 	};
   714 
  1111 
   715 	_.bindAll( wp.media.editor, 'open' );
  1112 	_.bindAll( wp.media.editor, 'open' );
   716 	$( wp.media.editor.init );
  1113 	$( wp.media.editor.init );
   717 }(jQuery));
  1114 }(jQuery, _));