PageSourceSearch

https://www.libriincantina.it/wp-includes/js/media-editor.js?ver=7.1.2

js libriincantina.it collected 2026-10-02 07:14:11 UTC 29,460 bytes, 1,072 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.