~canonical-sysadmins/wordpress/4.9.8

1.1.30 by Barry Price
new upstream release 4.4
1
/******/ (function(modules) { // webpackBootstrap
2
/******/ 	// The module cache
3
/******/ 	var installedModules = {};
4
/******/
5
/******/ 	// The require function
6
/******/ 	function __webpack_require__(moduleId) {
7
/******/
8
/******/ 		// Check if module is in cache
9
/******/ 		if(installedModules[moduleId]) {
10
/******/ 			return installedModules[moduleId].exports;
11
/******/ 		}
12
/******/ 		// Create a new module (and put it into the cache)
13
/******/ 		var module = installedModules[moduleId] = {
14
/******/ 			i: moduleId,
15
/******/ 			l: false,
16
/******/ 			exports: {}
17
/******/ 		};
18
/******/
19
/******/ 		// Execute the module function
20
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21
/******/
22
/******/ 		// Flag the module as loaded
23
/******/ 		module.l = true;
24
/******/
25
/******/ 		// Return the exports of the module
26
/******/ 		return module.exports;
27
/******/ 	}
28
/******/
29
/******/
30
/******/ 	// expose the modules object (__webpack_modules__)
31
/******/ 	__webpack_require__.m = modules;
32
/******/
33
/******/ 	// expose the module cache
34
/******/ 	__webpack_require__.c = installedModules;
35
/******/
36
/******/ 	// define getter function for harmony exports
37
/******/ 	__webpack_require__.d = function(exports, name, getter) {
38
/******/ 		if(!__webpack_require__.o(exports, name)) {
39
/******/ 			Object.defineProperty(exports, name, {
40
/******/ 				configurable: false,
41
/******/ 				enumerable: true,
42
/******/ 				get: getter
43
/******/ 			});
44
/******/ 		}
45
/******/ 	};
46
/******/
47
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
48
/******/ 	__webpack_require__.n = function(module) {
49
/******/ 		var getter = module && module.__esModule ?
50
/******/ 			function getDefault() { return module['default']; } :
51
/******/ 			function getModuleExports() { return module; };
52
/******/ 		__webpack_require__.d(getter, 'a', getter);
53
/******/ 		return getter;
54
/******/ 	};
55
/******/
56
/******/ 	// Object.prototype.hasOwnProperty.call
57
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
58
/******/
59
/******/ 	// __webpack_public_path__
60
/******/ 	__webpack_require__.p = "";
61
/******/
62
/******/ 	// Load entry module and return exports
63
/******/ 	return __webpack_require__(__webpack_require__.s = 26);
64
/******/ })
65
/************************************************************************/
66
/******/ (Array(26).concat([
67
/* 26 */
68
/***/ (function(module, exports, __webpack_require__) {
69
70
var media = wp.media,
1.1.4 by Paul Gear
new upstream release 4.2
71
	$ = jQuery,
1.1.30 by Barry Price
new upstream release 4.4
72
	l10n;
73
74
media.isTouchDevice = ( 'ontouchend' in document );
75
76
// Link any localized strings.
77
l10n = media.view.l10n = window._wpMediaViewsL10n || {};
78
79
// Link any settings.
80
media.view.settings = l10n.settings || {};
81
delete l10n.settings;
82
83
// Copy the `post` setting over to the model settings.
84
media.model.settings.post = media.view.settings.post;
85
86
// Check if the browser supports CSS 3.0 transitions
87
$.support.transition = (function(){
88
	var style = document.documentElement.style,
89
		transitions = {
90
			WebkitTransition: 'webkitTransitionEnd',
91
			MozTransition:    'transitionend',
92
			OTransition:      'oTransitionEnd otransitionend',
93
			transition:       'transitionend'
94
		}, transition;
95
96
	transition = _.find( _.keys( transitions ), function( transition ) {
97
		return ! _.isUndefined( style[ transition ] );
98
	});
99
100
	return transition && {
101
		end: transitions[ transition ]
102
	};
103
}());
104
105
/**
106
 * A shared event bus used to provide events into
107
 * the media workflows that 3rd-party devs can use to hook
108
 * in.
109
 */
110
media.events = _.extend( {}, Backbone.Events );
111
112
/**
113
 * Makes it easier to bind events using transitions.
114
 *
115
 * @param {string} selector
116
 * @param {Number} sensitivity
117
 * @returns {Promise}
118
 */
119
media.transition = function( selector, sensitivity ) {
120
	var deferred = $.Deferred();
121
122
	sensitivity = sensitivity || 2000;
123
124
	if ( $.support.transition ) {
125
		if ( ! (selector instanceof $) ) {
126
			selector = $( selector );
127
		}
128
129
		// Resolve the deferred when the first element finishes animating.
130
		selector.first().one( $.support.transition.end, deferred.resolve );
131
132
		// Just in case the event doesn't trigger, fire a callback.
133
		_.delay( deferred.resolve, sensitivity );
134
135
	// Otherwise, execute on the spot.
136
	} else {
137
		deferred.resolve();
138
	}
139
140
	return deferred.promise();
141
};
142
143
media.controller.Region = __webpack_require__( 27 );
144
media.controller.StateMachine = __webpack_require__( 28 );
145
media.controller.State = __webpack_require__( 29 );
146
147
media.selectionSync = __webpack_require__( 30 );
148
media.controller.Library = __webpack_require__( 31 );
149
media.controller.ImageDetails = __webpack_require__( 32 );
150
media.controller.GalleryEdit = __webpack_require__( 33 );
151
media.controller.GalleryAdd = __webpack_require__( 34 );
152
media.controller.CollectionEdit = __webpack_require__( 35 );
153
media.controller.CollectionAdd = __webpack_require__( 36 );
154
media.controller.FeaturedImage = __webpack_require__( 37 );
155
media.controller.ReplaceImage = __webpack_require__( 38 );
156
media.controller.EditImage = __webpack_require__( 39 );
157
media.controller.MediaLibrary = __webpack_require__( 40 );
158
media.controller.Embed = __webpack_require__( 41 );
159
media.controller.Cropper = __webpack_require__( 42 );
160
media.controller.CustomizeImageCropper = __webpack_require__( 43 );
161
media.controller.SiteIconCropper = __webpack_require__( 44 );
162
163
media.View = __webpack_require__( 45 );
164
media.view.Frame = __webpack_require__( 46 );
165
media.view.MediaFrame = __webpack_require__( 47 );
166
media.view.MediaFrame.Select = __webpack_require__( 48 );
167
media.view.MediaFrame.Post = __webpack_require__( 49 );
168
media.view.MediaFrame.ImageDetails = __webpack_require__( 50 );
169
media.view.Modal = __webpack_require__( 51 );
170
media.view.FocusManager = __webpack_require__( 52 );
171
media.view.UploaderWindow = __webpack_require__( 53 );
172
media.view.EditorUploader = __webpack_require__( 54 );
173
media.view.UploaderInline = __webpack_require__( 55 );
174
media.view.UploaderStatus = __webpack_require__( 56 );
175
media.view.UploaderStatusError = __webpack_require__( 57 );
176
media.view.Toolbar = __webpack_require__( 58 );
177
media.view.Toolbar.Select = __webpack_require__( 59 );
178
media.view.Toolbar.Embed = __webpack_require__( 60 );
179
media.view.Button = __webpack_require__( 61 );
180
media.view.ButtonGroup = __webpack_require__( 62 );
181
media.view.PriorityList = __webpack_require__( 63 );
182
media.view.MenuItem = __webpack_require__( 64 );
183
media.view.Menu = __webpack_require__( 65 );
184
media.view.RouterItem = __webpack_require__( 66 );
185
media.view.Router = __webpack_require__( 67 );
186
media.view.Sidebar = __webpack_require__( 68 );
187
media.view.Attachment = __webpack_require__( 69 );
188
media.view.Attachment.Library = __webpack_require__( 70 );
189
media.view.Attachment.EditLibrary = __webpack_require__( 71 );
190
media.view.Attachments = __webpack_require__( 72 );
191
media.view.Search = __webpack_require__( 73 );
192
media.view.AttachmentFilters = __webpack_require__( 74 );
193
media.view.DateFilter = __webpack_require__( 75 );
194
media.view.AttachmentFilters.Uploaded = __webpack_require__( 76 );
195
media.view.AttachmentFilters.All = __webpack_require__( 77 );
196
media.view.AttachmentsBrowser = __webpack_require__( 78 );
197
media.view.Selection = __webpack_require__( 79 );
198
media.view.Attachment.Selection = __webpack_require__( 80 );
199
media.view.Attachments.Selection = __webpack_require__( 81 );
200
media.view.Attachment.EditSelection = __webpack_require__( 82 );
201
media.view.Settings = __webpack_require__( 83 );
202
media.view.Settings.AttachmentDisplay = __webpack_require__( 84 );
203
media.view.Settings.Gallery = __webpack_require__( 85 );
204
media.view.Settings.Playlist = __webpack_require__( 86 );
205
media.view.Attachment.Details = __webpack_require__( 87 );
206
media.view.AttachmentCompat = __webpack_require__( 88 );
207
media.view.Iframe = __webpack_require__( 89 );
208
media.view.Embed = __webpack_require__( 90 );
209
media.view.Label = __webpack_require__( 91 );
210
media.view.EmbedUrl = __webpack_require__( 92 );
211
media.view.EmbedLink = __webpack_require__( 93 );
212
media.view.EmbedImage = __webpack_require__( 94 );
213
media.view.ImageDetails = __webpack_require__( 95 );
214
media.view.Cropper = __webpack_require__( 96 );
215
media.view.SiteIconCropper = __webpack_require__( 97 );
216
media.view.SiteIconPreview = __webpack_require__( 98 );
217
media.view.EditImage = __webpack_require__( 99 );
218
media.view.Spinner = __webpack_require__( 100 );
219
220
221
/***/ }),
222
/* 27 */
223
/***/ (function(module, exports) {
224
1.1.4 by Paul Gear
new upstream release 4.2
225
/**
226
 * wp.media.controller.Region
227
 *
228
 * A region is a persistent application layout area.
229
 *
230
 * A region assumes one mode at any time, and can be switched to another.
231
 *
232
 * When mode changes, events are triggered on the region's parent view.
233
 * The parent view will listen to specific events and fill the region with an
234
 * appropriate view depending on mode. For example, a frame listens for the
235
 * 'browse' mode t be activated on the 'content' view and then fills the region
236
 * with an AttachmentsBrowser view.
237
 *
1.1.30 by Barry Price
new upstream release 4.4
238
 * @memberOf wp.media.controller
239
 *
1.1.4 by Paul Gear
new upstream release 4.2
240
 * @class
241
 *
242
 * @param {object}        options          Options hash for the region.
243
 * @param {string}        options.id       Unique identifier for the region.
244
 * @param {Backbone.View} options.view     A parent view the region exists within.
245
 * @param {string}        options.selector jQuery selector for the region within the parent view.
246
 */
247
var Region = function( options ) {
248
	_.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) );
249
};
250
251
// Use Backbone's self-propagating `extend` inheritance method.
252
Region.extend = Backbone.Model.extend;
253
1.1.30 by Barry Price
new upstream release 4.4
254
_.extend( Region.prototype,/** @lends wp.media.controller.Region.prototype */{
1.1.4 by Paul Gear
new upstream release 4.2
255
	/**
256
	 * Activate a mode.
257
	 *
258
	 * @since 3.5.0
259
	 *
260
	 * @param {string} mode
261
	 *
1.1.30 by Barry Price
new upstream release 4.4
262
	 * @fires Region#activate
263
	 * @fires Region#deactivate
1.1.4 by Paul Gear
new upstream release 4.2
264
	 *
265
	 * @returns {wp.media.controller.Region} Returns itself to allow chaining.
266
	 */
267
	mode: function( mode ) {
268
		if ( ! mode ) {
269
			return this._mode;
270
		}
271
		// Bail if we're trying to change to the current mode.
272
		if ( mode === this._mode ) {
273
			return this;
274
		}
275
276
		/**
277
		 * Region mode deactivation event.
278
		 *
1.1.30 by Barry Price
new upstream release 4.4
279
		 * @event wp.media.controller.Region#deactivate
1.1.4 by Paul Gear
new upstream release 4.2
280
		 */
281
		this.trigger('deactivate');
282
283
		this._mode = mode;
284
		this.render( mode );
285
286
		/**
287
		 * Region mode activation event.
288
		 *
1.1.30 by Barry Price
new upstream release 4.4
289
		 * @event wp.media.controller.Region#activate
1.1.4 by Paul Gear
new upstream release 4.2
290
		 */
291
		this.trigger('activate');
292
		return this;
293
	},
294
	/**
295
	 * Render a mode.
296
	 *
297
	 * @since 3.5.0
298
	 *
299
	 * @param {string} mode
300
	 *
1.1.30 by Barry Price
new upstream release 4.4
301
	 * @fires Region#create
302
	 * @fires Region#render
1.1.4 by Paul Gear
new upstream release 4.2
303
	 *
304
	 * @returns {wp.media.controller.Region} Returns itself to allow chaining
305
	 */
306
	render: function( mode ) {
307
		// If the mode isn't active, activate it.
308
		if ( mode && mode !== this._mode ) {
309
			return this.mode( mode );
310
		}
311
312
		var set = { view: null },
313
			view;
314
315
		/**
316
		 * Create region view event.
317
		 *
318
		 * Region view creation takes place in an event callback on the frame.
319
		 *
1.1.30 by Barry Price
new upstream release 4.4
320
		 * @event wp.media.controller.Region#create
321
		 * @type {object}
322
		 * @property {object} view
1.1.4 by Paul Gear
new upstream release 4.2
323
		 */
324
		this.trigger( 'create', set );
325
		view = set.view;
326
327
		/**
328
		 * Render region view event.
329
		 *
330
		 * Region view creation takes place in an event callback on the frame.
331
		 *
1.1.30 by Barry Price
new upstream release 4.4
332
		 * @event wp.media.controller.Region#render
333
		 * @type {object}
1.1.4 by Paul Gear
new upstream release 4.2
334
		 */
335
		this.trigger( 'render', view );
336
		if ( view ) {
337
			this.set( view );
338
		}
339
		return this;
340
	},
341
342
	/**
343
	 * Get the region's view.
344
	 *
345
	 * @since 3.5.0
346
	 *
347
	 * @returns {wp.media.View}
348
	 */
349
	get: function() {
350
		return this.view.views.first( this.selector );
351
	},
352
353
	/**
354
	 * Set the region's view as a subview of the frame.
355
	 *
356
	 * @since 3.5.0
357
	 *
358
	 * @param {Array|Object} views
359
	 * @param {Object} [options={}]
360
	 * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining
361
	 */
362
	set: function( views, options ) {
363
		if ( options ) {
364
			options.add = false;
365
		}
366
		return this.view.views.set( this.selector, views, options );
367
	},
368
369
	/**
370
	 * Trigger regional view events on the frame.
371
	 *
372
	 * @since 3.5.0
373
	 *
374
	 * @param {string} event
375
	 * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining.
376
	 */
377
	trigger: function( event ) {
378
		var base, args;
379
380
		if ( ! this._mode ) {
381
			return;
382
		}
383
384
		args = _.toArray( arguments );
385
		base = this.id + ':' + event;
386
387
		// Trigger `{this.id}:{event}:{this._mode}` event on the frame.
388
		args[0] = base + ':' + this._mode;
389
		this.view.trigger.apply( this.view, args );
390
391
		// Trigger `{this.id}:{event}` event on the frame.
392
		args[0] = base;
393
		this.view.trigger.apply( this.view, args );
394
		return this;
395
	}
396
});
397
398
module.exports = Region;
399
1.1.30 by Barry Price
new upstream release 4.4
400
401
/***/ }),
402
/* 28 */
403
/***/ (function(module, exports) {
404
1.1.4 by Paul Gear
new upstream release 4.2
405
/**
406
 * wp.media.controller.StateMachine
407
 *
408
 * A state machine keeps track of state. It is in one state at a time,
409
 * and can change from one state to another.
410
 *
411
 * States are stored as models in a Backbone collection.
412
 *
1.1.30 by Barry Price
new upstream release 4.4
413
 * @memberOf wp.media.controller
414
 *
1.1.4 by Paul Gear
new upstream release 4.2
415
 * @since 3.5.0
416
 *
417
 * @class
418
 * @augments Backbone.Model
419
 * @mixin
420
 * @mixes Backbone.Events
421
 *
422
 * @param {Array} states
423
 */
424
var StateMachine = function( states ) {
425
	// @todo This is dead code. The states collection gets created in media.view.Frame._createStates.
426
	this.states = new Backbone.Collection( states );
427
};
428
429
// Use Backbone's self-propagating `extend` inheritance method.
430
StateMachine.extend = Backbone.Model.extend;
431
1.1.30 by Barry Price
new upstream release 4.4
432
_.extend( StateMachine.prototype, Backbone.Events,/** @lends wp.media.controller.StateMachine.prototype */{
1.1.4 by Paul Gear
new upstream release 4.2
433
	/**
434
	 * Fetch a state.
435
	 *
436
	 * If no `id` is provided, returns the active state.
437
	 *
438
	 * Implicitly creates states.
439
	 *
440
	 * Ensure that the `states` collection exists so the `StateMachine`
441
	 *   can be used as a mixin.
442
	 *
443
	 * @since 3.5.0
444
	 *
445
	 * @param {string} id
446
	 * @returns {wp.media.controller.State} Returns a State model
447
	 *   from the StateMachine collection
448
	 */
449
	state: function( id ) {
450
		this.states = this.states || new Backbone.Collection();
451
452
		// Default to the active state.
453
		id = id || this._state;
454
455
		if ( id && ! this.states.get( id ) ) {
456
			this.states.add({ id: id });
457
		}
458
		return this.states.get( id );
459
	},
460
461
	/**
462
	 * Sets the active state.
463
	 *
464
	 * Bail if we're trying to select the current state, if we haven't
465
	 * created the `states` collection, or are trying to select a state
466
	 * that does not exist.
467
	 *
468
	 * @since 3.5.0
469
	 *
470
	 * @param {string} id
471
	 *
472
	 * @fires wp.media.controller.State#deactivate
473
	 * @fires wp.media.controller.State#activate
474
	 *
475
	 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining
476
	 */
477
	setState: function( id ) {
478
		var previous = this.state();
479
480
		if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) {
481
			return this;
482
		}
483
484
		if ( previous ) {
485
			previous.trigger('deactivate');
486
			this._lastState = previous.id;
487
		}
488
489
		this._state = id;
490
		this.state().trigger('activate');
491
492
		return this;
493
	},
494
495
	/**
496
	 * Returns the previous active state.
497
	 *
498
	 * Call the `state()` method with no parameters to retrieve the current
499
	 * active state.
500
	 *
501
	 * @since 3.5.0
502
	 *
503
	 * @returns {wp.media.controller.State} Returns a State model
504
	 *    from the StateMachine collection
505
	 */
506
	lastState: function() {
507
		if ( this._lastState ) {
508
			return this.state( this._lastState );
509
		}
510
	}
511
});
512
513
// Map all event binding and triggering on a StateMachine to its `states` collection.
514
_.each([ 'on', 'off', 'trigger' ], function( method ) {
515
	/**
1.1.30 by Barry Price
new upstream release 4.4
516
	 * @function on
517
	 * @memberOf wp.media.controller.StateMachine
518
	 * @instance
519
	 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
520
	 */
521
	/**
522
	 * @function off
523
	 * @memberOf wp.media.controller.StateMachine
524
	 * @instance
525
	 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
526
	 */
527
	/**
528
	 * @function trigger
529
	 * @memberOf wp.media.controller.StateMachine
530
	 * @instance
1.1.4 by Paul Gear
new upstream release 4.2
531
	 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining.
532
	 */
533
	StateMachine.prototype[ method ] = function() {
534
		// Ensure that the `states` collection exists so the `StateMachine`
535
		// can be used as a mixin.
536
		this.states = this.states || new Backbone.Collection();
537
		// Forward the method to the `states` collection.
538
		this.states[ method ].apply( this.states, arguments );
539
		return this;
540
	};
541
});
542
543
module.exports = StateMachine;
544
1.1.30 by Barry Price
new upstream release 4.4
545
546
/***/ }),
547
/* 29 */
548
/***/ (function(module, exports) {
549
1.1.4 by Paul Gear
new upstream release 4.2
550
/**
551
 * wp.media.controller.State
552
 *
553
 * A state is a step in a workflow that when set will trigger the controllers
554
 * for the regions to be updated as specified in the frame.
555
 *
556
 * A state has an event-driven lifecycle:
557
 *
558
 *     'ready'      triggers when a state is added to a state machine's collection.
559
 *     'activate'   triggers when a state is activated by a state machine.
560
 *     'deactivate' triggers when a state is deactivated by a state machine.
561
 *     'reset'      is not triggered automatically. It should be invoked by the
562
 *                  proper controller to reset the state to its default.
563
 *
1.1.30 by Barry Price
new upstream release 4.4
564
 * @memberOf wp.media.controller
565
 *
1.1.4 by Paul Gear
new upstream release 4.2
566
 * @class
567
 * @augments Backbone.Model
568
 */
1.1.30 by Barry Price
new upstream release 4.4
569
var State = Backbone.Model.extend(/** @lends wp.media.controller.State.prototype */{
1.1.4 by Paul Gear
new upstream release 4.2
570
	/**
571
	 * Constructor.
572
	 *
573
	 * @since 3.5.0
574
	 */
575
	constructor: function() {
576
		this.on( 'activate', this._preActivate, this );
577
		this.on( 'activate', this.activate, this );
578
		this.on( 'activate', this._postActivate, this );
579
		this.on( 'deactivate', this._deactivate, this );
580
		this.on( 'deactivate', this.deactivate, this );
581
		this.on( 'reset', this.reset, this );
582
		this.on( 'ready', this._ready, this );
583
		this.on( 'ready', this.ready, this );
584
		/**
585
		 * Call parent constructor with passed arguments
586
		 */
587
		Backbone.Model.apply( this, arguments );
588
		this.on( 'change:menu', this._updateMenu, this );
589
	},
590
	/**
591
	 * Ready event callback.
592
	 *
593
	 * @abstract
594
	 * @since 3.5.0
595
	 */
596
	ready: function() {},
597
598
	/**
599
	 * Activate event callback.
600
	 *
601
	 * @abstract
602
	 * @since 3.5.0
603
	 */
604
	activate: function() {},
605
606
	/**
607
	 * Deactivate event callback.
608
	 *
609
	 * @abstract
610
	 * @since 3.5.0
611
	 */
612
	deactivate: function() {},
613
614
	/**
615
	 * Reset event callback.
616
	 *
617
	 * @abstract
618
	 * @since 3.5.0
619
	 */
620
	reset: function() {},
621
622
	/**
623
	 * @access private
624
	 * @since 3.5.0
625
	 */
626
	_ready: function() {
627
		this._updateMenu();
628
	},
629
630
	/**
631
	 * @access private
632
	 * @since 3.5.0
633
	*/
634
	_preActivate: function() {
635
		this.active = true;
636
	},
637
638
	/**
639
	 * @access private
640
	 * @since 3.5.0
641
	 */
642
	_postActivate: function() {
643
		this.on( 'change:menu', this._menu, this );
644
		this.on( 'change:titleMode', this._title, this );
645
		this.on( 'change:content', this._content, this );
646
		this.on( 'change:toolbar', this._toolbar, this );
647
648
		this.frame.on( 'title:render:default', this._renderTitle, this );
649
650
		this._title();
651
		this._menu();
652
		this._toolbar();
653
		this._content();
654
		this._router();
655
	},
656
657
	/**
658
	 * @access private
659
	 * @since 3.5.0
660
	 */
661
	_deactivate: function() {
662
		this.active = false;
663
664
		this.frame.off( 'title:render:default', this._renderTitle, this );
665
666
		this.off( 'change:menu', this._menu, this );
667
		this.off( 'change:titleMode', this._title, this );
668
		this.off( 'change:content', this._content, this );
669
		this.off( 'change:toolbar', this._toolbar, this );
670
	},
671
672
	/**
673
	 * @access private
674
	 * @since 3.5.0
675
	 */
676
	_title: function() {
677
		this.frame.title.render( this.get('titleMode') || 'default' );
678
	},
679
680
	/**
681
	 * @access private
682
	 * @since 3.5.0
683
	 */
684
	_renderTitle: function( view ) {
685
		view.$el.text( this.get('title') || '' );
686
	},
687
688
	/**
689
	 * @access private
690
	 * @since 3.5.0
691
	 */
692
	_router: function() {
693
		var router = this.frame.router,
694
			mode = this.get('router'),
695
			view;
696
697
		this.frame.$el.toggleClass( 'hide-router', ! mode );
698
		if ( ! mode ) {
699
			return;
700
		}
701
702
		this.frame.router.render( mode );
703
704
		view = router.get();
705
		if ( view && view.select ) {
706
			view.select( this.frame.content.mode() );
707
		}
708
	},
709
710
	/**
711
	 * @access private
712
	 * @since 3.5.0
713
	 */
714
	_menu: function() {
715
		var menu = this.frame.menu,
716
			mode = this.get('menu'),
717
			view;
718
719
		this.frame.$el.toggleClass( 'hide-menu', ! mode );
720
		if ( ! mode ) {
721
			return;
722
		}
723
724
		menu.mode( mode );
725
726
		view = menu.get();
727
		if ( view && view.select ) {
728
			view.select( this.id );
729
		}
730
	},
731
732
	/**
733
	 * @access private
734
	 * @since 3.5.0
735
	 */
736
	_updateMenu: function() {
737
		var previous = this.previous('menu'),
738
			menu = this.get('menu');
739
740
		if ( previous ) {
741
			this.frame.off( 'menu:render:' + previous, this._renderMenu, this );
742
		}
743
744
		if ( menu ) {
745
			this.frame.on( 'menu:render:' + menu, this._renderMenu, this );
746
		}
747
	},
748
749
	/**
750
	 * Create a view in the media menu for the state.
751
	 *
752
	 * @access private
753
	 * @since 3.5.0
754
	 *
755
	 * @param {media.view.Menu} view The menu view.
756
	 */
757
	_renderMenu: function( view ) {
758
		var menuItem = this.get('menuItem'),
759
			title = this.get('title'),
760
			priority = this.get('priority');
761
762
		if ( ! menuItem && title ) {
763
			menuItem = { text: title };
764
765
			if ( priority ) {
766
				menuItem.priority = priority;
767
			}
768
		}
769
770
		if ( ! menuItem ) {
771
			return;
772
		}
773
774
		view.set( this.id, menuItem );
775
	}
776
});
777
778
_.each(['toolbar','content'], function( region ) {
779
	/**
780
	 * @access private
781
	 */
782
	State.prototype[ '_' + region ] = function() {
783
		var mode = this.get( region );
784
		if ( mode ) {
785
			this.frame[ region ].render( mode );
786
		}
787
	};
788
});
789
790
module.exports = State;
791
1.1.30 by Barry Price
new upstream release 4.4
792
793
/***/ }),
794
/* 30 */
795
/***/ (function(module, exports) {
796
1.1.4 by Paul Gear
new upstream release 4.2
797
/**
798
 * wp.media.selectionSync
799
 *
800
 * Sync an attachments selection in a state with another state.
801
 *
1.1.30 by Barry Price
new upstream release 4.4
802
 * Allows for selecting multiple images in the Add Media workflow, and then
1.1.4 by Paul Gear
new upstream release 4.2
803
 * switching to the Insert Gallery workflow while preserving the attachments selection.
804
 *
1.1.30 by Barry Price
new upstream release 4.4
805
 * @memberOf wp.media
806
 *
1.1.4 by Paul Gear
new upstream release 4.2
807
 * @mixin
808
 */
809
var selectionSync = {
810
	/**
811
	 * @since 3.5.0
812
	 */
813
	syncSelection: function() {
814
		var selection = this.get('selection'),
815
			manager = this.frame._selection;
816
817
		if ( ! this.get('syncSelection') || ! manager || ! selection ) {
818
			return;
819
		}
820
821
		// If the selection supports multiple items, validate the stored
822
		// attachments based on the new selection's conditions. Record
823
		// the attachments that are not included; we'll maintain a
824
		// reference to those. Other attachments are considered in flux.
825
		if ( selection.multiple ) {
826
			selection.reset( [], { silent: true });
827
			selection.validateAll( manager.attachments );
828
			manager.difference = _.difference( manager.attachments.models, selection.models );
829
		}
830
831
		// Sync the selection's single item with the master.
832
		selection.single( manager.single );
833
	},
834
835
	/**
836
	 * Record the currently active attachments, which is a combination
837
	 * of the selection's attachments and the set of selected
838
	 * attachments that this specific selection considered invalid.
839
	 * Reset the difference and record the single attachment.
840
	 *
841
	 * @since 3.5.0
842
	 */
843
	recordSelection: function() {
844
		var selection = this.get('selection'),
845
			manager = this.frame._selection;
846
847
		if ( ! this.get('syncSelection') || ! manager || ! selection ) {
848
			return;
849
		}
850
851
		if ( selection.multiple ) {
852
			manager.attachments.reset( selection.toArray().concat( manager.difference ) );
853
			manager.difference = [];
854
		} else {
855
			manager.attachments.add( selection.toArray() );
856
		}
857
858
		manager.single = selection._single;
859
	}
860
};
861
862
module.exports = selectionSync;
863
1.1.30 by Barry Price
new upstream release 4.4
864
865
/***/ }),
866
/* 31 */
867
/***/ (function(module, exports) {
868
869
var l10n = wp.media.view.l10n,
870
	getUserSetting = window.getUserSetting,
871
	setUserSetting = window.setUserSetting,
872
	Library;
873
874
/**
875
 * wp.media.controller.Library
876
 *
877
 * A state for choosing an attachment or group of attachments from the media library.
878
 *
879
 * @memberOf wp.media.controller
880
 *
881
 * @class
882
 * @augments wp.media.controller.State
883
 * @augments Backbone.Model
884
 * @mixes media.selectionSync
885
 *
886
 * @param {object}                          [attributes]                         The attributes hash passed to the state.
887
 * @param {string}                          [attributes.id=library]              Unique identifier.
888
 * @param {string}                          [attributes.title=Media library]     Title for the state. Displays in the media menu and the frame's title region.
889
 * @param {wp.media.model.Attachments}      [attributes.library]                 The attachments collection to browse.
890
 *                                                                               If one is not supplied, a collection of all attachments will be created.
891
 * @param {wp.media.model.Selection|object} [attributes.selection]               A collection to contain attachment selections within the state.
892
 *                                                                               If the 'selection' attribute is a plain JS object,
893
 *                                                                               a Selection will be created using its values as the selection instance's `props` model.
894
 *                                                                               Otherwise, it will copy the library's `props` model.
895
 * @param {boolean}                         [attributes.multiple=false]          Whether multi-select is enabled.
896
 * @param {string}                          [attributes.content=upload]          Initial mode for the content region.
897
 *                                                                               Overridden by persistent user setting if 'contentUserSetting' is true.
898
 * @param {string}                          [attributes.menu=default]            Initial mode for the menu region.
899
 * @param {string}                          [attributes.router=browse]           Initial mode for the router region.
900
 * @param {string}                          [attributes.toolbar=select]          Initial mode for the toolbar region.
901
 * @param {boolean}                         [attributes.searchable=true]         Whether the library is searchable.
902
 * @param {boolean|string}                  [attributes.filterable=false]        Whether the library is filterable, and if so what filters should be shown.
903
 *                                                                               Accepts 'all', 'uploaded', or 'unattached'.
904
 * @param {boolean}                         [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
905
 * @param {boolean}                         [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
906
 * @param {boolean}                         [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
907
 * @param {boolean}                         [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
908
 * @param {boolean}                         [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
909
 */
910
Library = wp.media.controller.State.extend(/** @lends wp.media.controller.Library.prototype */{
911
	defaults: {
912
		id:                 'library',
913
		title:              l10n.mediaLibraryTitle,
914
		multiple:           false,
915
		content:            'upload',
916
		menu:               'default',
917
		router:             'browse',
918
		toolbar:            'select',
919
		searchable:         true,
920
		filterable:         false,
921
		sortable:           true,
922
		autoSelect:         true,
923
		describe:           false,
924
		contentUserSetting: true,
925
		syncSelection:      true
926
	},
927
928
	/**
929
	 * If a library isn't provided, query all media items.
930
	 * If a selection instance isn't provided, create one.
931
	 *
932
	 * @since 3.5.0
933
	 */
934
	initialize: function() {
935
		var selection = this.get('selection'),
936
			props;
937
938
		if ( ! this.get('library') ) {
939
			this.set( 'library', wp.media.query() );
940
		}
941
942
		if ( ! ( selection instanceof wp.media.model.Selection ) ) {
943
			props = selection;
944
945
			if ( ! props ) {
946
				props = this.get('library').props.toJSON();
947
				props = _.omit( props, 'orderby', 'query' );
948
			}
949
950
			this.set( 'selection', new wp.media.model.Selection( null, {
951
				multiple: this.get('multiple'),
952
				props: props
953
			}) );
954
		}
955
956
		this.resetDisplays();
957
	},
958
959
	/**
960
	 * @since 3.5.0
961
	 */
962
	activate: function() {
963
		this.syncSelection();
964
965
		wp.Uploader.queue.on( 'add', this.uploading, this );
966
967
		this.get('selection').on( 'add remove reset', this.refreshContent, this );
968
969
		if ( this.get( 'router' ) && this.get('contentUserSetting') ) {
970
			this.frame.on( 'content:activate', this.saveContentMode, this );
971
			this.set( 'content', getUserSetting( 'libraryContent', this.get('content') ) );
972
		}
973
	},
974
975
	/**
976
	 * @since 3.5.0
977
	 */
978
	deactivate: function() {
979
		this.recordSelection();
980
981
		this.frame.off( 'content:activate', this.saveContentMode, this );
982
983
		// Unbind all event handlers that use this state as the context
984
		// from the selection.
985
		this.get('selection').off( null, null, this );
986
987
		wp.Uploader.queue.off( null, null, this );
988
	},
989
990
	/**
991
	 * Reset the library to its initial state.
992
	 *
993
	 * @since 3.5.0
994
	 */
995
	reset: function() {
996
		this.get('selection').reset();
997
		this.resetDisplays();
998
		this.refreshContent();
999
	},
1000
1001
	/**
1002
	 * Reset the attachment display settings defaults to the site options.
1003
	 *
1004
	 * If site options don't define them, fall back to a persistent user setting.
1005
	 *
1006
	 * @since 3.5.0
1007
	 */
1008
	resetDisplays: function() {
1009
		var defaultProps = wp.media.view.settings.defaultProps;
1010
		this._displays = [];
1011
		this._defaultDisplaySettings = {
1012
			align: getUserSetting( 'align', defaultProps.align ) || 'none',
1013
			size:  getUserSetting( 'imgsize', defaultProps.size ) || 'medium',
1014
			link:  getUserSetting( 'urlbutton', defaultProps.link ) || 'none'
1015
		};
1016
	},
1017
1018
	/**
1019
	 * Create a model to represent display settings (alignment, etc.) for an attachment.
1020
	 *
1021
	 * @since 3.5.0
1022
	 *
1023
	 * @param {wp.media.model.Attachment} attachment
1024
	 * @returns {Backbone.Model}
1025
	 */
1026
	display: function( attachment ) {
1027
		var displays = this._displays;
1028
1029
		if ( ! displays[ attachment.cid ] ) {
1030
			displays[ attachment.cid ] = new Backbone.Model( this.defaultDisplaySettings( attachment ) );
1031
		}
1032
		return displays[ attachment.cid ];
1033
	},
1034
1035
	/**
1036
	 * Given an attachment, create attachment display settings properties.
1037
	 *
1038
	 * @since 3.6.0
1039
	 *
1040
	 * @param {wp.media.model.Attachment} attachment
1041
	 * @returns {Object}
1042
	 */
1043
	defaultDisplaySettings: function( attachment ) {
1044
		var settings = _.clone( this._defaultDisplaySettings );
1045
1046
		if ( settings.canEmbed = this.canEmbed( attachment ) ) {
1047
			settings.link = 'embed';
1048
		} else if ( ! this.isImageAttachment( attachment ) && settings.link === 'none' ) {
1049
			settings.link = 'file';
1050
		}
1051
1052
		return settings;
1053
	},
1054
1055
	/**
1056
	 * Whether an attachment is image.
1057
	 *
1058
	 * @since 4.4.1
1059
	 *
1060
	 * @param {wp.media.model.Attachment} attachment
1061
	 * @returns {Boolean}
1062
	 */
1063
	isImageAttachment: function( attachment ) {
1064
		// If uploading, we know the filename but not the mime type.
1065
		if ( attachment.get('uploading') ) {
1066
			return /\.(jpe?g|png|gif)$/i.test( attachment.get('filename') );
1067
		}
1068
1069
		return attachment.get('type') === 'image';
1070
	},
1071
1072
	/**
1073
	 * Whether an attachment can be embedded (audio or video).
1074
	 *
1075
	 * @since 3.6.0
1076
	 *
1077
	 * @param {wp.media.model.Attachment} attachment
1078
	 * @returns {Boolean}
1079
	 */
1080
	canEmbed: function( attachment ) {
1081
		// If uploading, we know the filename but not the mime type.
1082
		if ( ! attachment.get('uploading') ) {
1083
			var type = attachment.get('type');
1084
			if ( type !== 'audio' && type !== 'video' ) {
1085
				return false;
1086
			}
1087
		}
1088
1089
		return _.contains( wp.media.view.settings.embedExts, attachment.get('filename').split('.').pop() );
1090
	},
1091
1092
1093
	/**
1094
	 * If the state is active, no items are selected, and the current
1095
	 * content mode is not an option in the state's router (provided
1096
	 * the state has a router), reset the content mode to the default.
1097
	 *
1098
	 * @since 3.5.0
1099
	 */
1100
	refreshContent: function() {
1101
		var selection = this.get('selection'),
1102
			frame = this.frame,
1103
			router = frame.router.get(),
1104
			mode = frame.content.mode();
1105
1106
		if ( this.active && ! selection.length && router && ! router.get( mode ) ) {
1107
			this.frame.content.render( this.get('content') );
1108
		}
1109
	},
1110
1111
	/**
1112
	 * Callback handler when an attachment is uploaded.
1113
	 *
1114
	 * Switch to the Media Library if uploaded from the 'Upload Files' tab.
1115
	 *
1116
	 * Adds any uploading attachments to the selection.
1117
	 *
1118
	 * If the state only supports one attachment to be selected and multiple
1119
	 * attachments are uploaded, the last attachment in the upload queue will
1120
	 * be selected.
1121
	 *
1122
	 * @since 3.5.0
1123
	 *
1124
	 * @param {wp.media.model.Attachment} attachment
1125
	 */
1126
	uploading: function( attachment ) {
1127
		var content = this.frame.content;
1128
1129
		if ( 'upload' === content.mode() ) {
1130
			this.frame.content.mode('browse');
1131
		}
1132
1133
		if ( this.get( 'autoSelect' ) ) {
1134
			this.get('selection').add( attachment );
1135
			this.frame.trigger( 'library:selection:add' );
1136
		}
1137
	},
1138
1139
	/**
1140
	 * Persist the mode of the content region as a user setting.
1141
	 *
1142
	 * @since 3.5.0
1143
	 */
1144
	saveContentMode: function() {
1145
		if ( 'browse' !== this.get('router') ) {
1146
			return;
1147
		}
1148
1149
		var mode = this.frame.content.mode(),
1150
			view = this.frame.router.get();
1151
1152
		if ( view && view.get( mode ) ) {
1153
			setUserSetting( 'libraryContent', mode );
1154
		}
1155
	}
1.2.4 by Barry Price
new upstream release 4.9.5
1156
1.1.30 by Barry Price
new upstream release 4.4
1157
});
1158
1159
// Make selectionSync available on any Media Library state.
1160
_.extend( Library.prototype, wp.media.selectionSync );
1161
1162
module.exports = Library;
1163
1164
1165
/***/ }),
1166
/* 32 */
1167
/***/ (function(module, exports) {
1168
1169
var State = wp.media.controller.State,
1170
	Library = wp.media.controller.Library,
1171
	l10n = wp.media.view.l10n,
1172
	ImageDetails;
1173
1174
/**
1175
 * wp.media.controller.ImageDetails
1176
 *
1177
 * A state for editing the attachment display settings of an image that's been
1178
 * inserted into the editor.
1179
 *
1180
 * @memberOf wp.media.controller
1181
 *
1182
 * @class
1183
 * @augments wp.media.controller.State
1184
 * @augments Backbone.Model
1185
 *
1186
 * @param {object}                    [attributes]                       The attributes hash passed to the state.
1187
 * @param {string}                    [attributes.id=image-details]      Unique identifier.
1188
 * @param {string}                    [attributes.title=Image Details]   Title for the state. Displays in the frame's title region.
1189
 * @param {wp.media.model.Attachment} attributes.image                   The image's model.
1190
 * @param {string|false}              [attributes.content=image-details] Initial mode for the content region.
1191
 * @param {string|false}              [attributes.menu=false]            Initial mode for the menu region.
1192
 * @param {string|false}              [attributes.router=false]          Initial mode for the router region.
1193
 * @param {string|false}              [attributes.toolbar=image-details] Initial mode for the toolbar region.
1194
 * @param {boolean}                   [attributes.editing=false]         Unused.
1195
 * @param {int}                       [attributes.priority=60]           Unused.
1196
 *
1197
 * @todo This state inherits some defaults from media.controller.Library.prototype.defaults,
1198
 *       however this may not do anything.
1199
 */
1200
ImageDetails = State.extend(/** @lends wp.media.controller.ImageDetails.prototype */{
1201
	defaults: _.defaults({
1202
		id:       'image-details',
1203
		title:    l10n.imageDetailsTitle,
1204
		content:  'image-details',
1205
		menu:     false,
1206
		router:   false,
1207
		toolbar:  'image-details',
1208
		editing:  false,
1209
		priority: 60
1210
	}, Library.prototype.defaults ),
1211
1212
	/**
1213
	 * @since 3.9.0
1214
	 *
1215
	 * @param options Attributes
1216
	 */
1217
	initialize: function( options ) {
1218
		this.image = options.image;
1219
		State.prototype.initialize.apply( this, arguments );
1220
	},
1221
1222
	/**
1223
	 * @since 3.9.0
1224
	 */
1225
	activate: function() {
1226
		this.frame.modal.$el.addClass('image-details');
1227
	}
1228
});
1229
1230
module.exports = ImageDetails;
1231
1232
1233
/***/ }),
1234
/* 33 */
1235
/***/ (function(module, exports) {
1236
1237
var Library = wp.media.controller.Library,
1238
	l10n = wp.media.view.l10n,
1239
	GalleryEdit;
1240
1241
/**
1242
 * wp.media.controller.GalleryEdit
1243
 *
1244
 * A state for editing a gallery's images and settings.
1245
 *
1246
 * @memberOf wp.media.controller
1247
 *
1248
 * @class
1249
 * @augments wp.media.controller.Library
1250
 * @augments wp.media.controller.State
1251
 * @augments Backbone.Model
1252
 *
1253
 * @param {object}                     [attributes]                       The attributes hash passed to the state.
1254
 * @param {string}                     [attributes.id=gallery-edit]       Unique identifier.
1255
 * @param {string}                     [attributes.title=Edit Gallery]    Title for the state. Displays in the frame's title region.
1256
 * @param {wp.media.model.Attachments} [attributes.library]               The collection of attachments in the gallery.
1257
 *                                                                        If one is not supplied, an empty media.model.Selection collection is created.
1258
 * @param {boolean}                    [attributes.multiple=false]        Whether multi-select is enabled.
1259
 * @param {boolean}                    [attributes.searchable=false]      Whether the library is searchable.
1260
 * @param {boolean}                    [attributes.sortable=true]         Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
1261
 * @param {boolean}                    [attributes.date=true]             Whether to show the date filter in the browser's toolbar.
1262
 * @param {string|false}               [attributes.content=browse]        Initial mode for the content region.
1263
 * @param {string|false}               [attributes.toolbar=image-details] Initial mode for the toolbar region.
1264
 * @param {boolean}                    [attributes.describe=true]         Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
1265
 * @param {boolean}                    [attributes.displaySettings=true]  Whether to show the attachment display settings interface.
1266
 * @param {boolean}                    [attributes.dragInfo=true]         Whether to show instructional text about the attachments being sortable.
1267
 * @param {int}                        [attributes.idealColumnWidth=170]  The ideal column width in pixels for attachments.
1268
 * @param {boolean}                    [attributes.editing=false]         Whether the gallery is being created, or editing an existing instance.
1269
 * @param {int}                        [attributes.priority=60]           The priority for the state link in the media menu.
1270
 * @param {boolean}                    [attributes.syncSelection=false]   Whether the Attachments selection should be persisted from the last state.
1271
 *                                                                        Defaults to false for this state, because the library passed in  *is* the selection.
1272
 * @param {view}                       [attributes.AttachmentView]        The single `Attachment` view to be used in the `Attachments`.
1273
 *                                                                        If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
1274
 */
1275
GalleryEdit = Library.extend(/** @lends wp.media.controller.GalleryEdit.prototype */{
1276
	defaults: {
1277
		id:               'gallery-edit',
1278
		title:            l10n.editGalleryTitle,
1279
		multiple:         false,
1280
		searchable:       false,
1281
		sortable:         true,
1282
		date:             false,
1283
		display:          false,
1284
		content:          'browse',
1285
		toolbar:          'gallery-edit',
1286
		describe:         true,
1287
		displaySettings:  true,
1288
		dragInfo:         true,
1289
		idealColumnWidth: 170,
1290
		editing:          false,
1291
		priority:         60,
1292
		syncSelection:    false
1293
	},
1294
1295
	/**
1296
	 * @since 3.5.0
1297
	 */
1298
	initialize: function() {
1299
		// If we haven't been provided a `library`, create a `Selection`.
1300
		if ( ! this.get('library') ) {
1301
			this.set( 'library', new wp.media.model.Selection() );
1302
		}
1303
1304
		// The single `Attachment` view to be used in the `Attachments` view.
1305
		if ( ! this.get('AttachmentView') ) {
1306
			this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
1307
		}
1308
1309
		Library.prototype.initialize.apply( this, arguments );
1310
	},
1311
1312
	/**
1313
	 * @since 3.5.0
1314
	 */
1315
	activate: function() {
1316
		var library = this.get('library');
1317
1318
		// Limit the library to images only.
1319
		library.props.set( 'type', 'image' );
1320
1321
		// Watch for uploaded attachments.
1322
		this.get('library').observe( wp.Uploader.queue );
1323
1324
		this.frame.on( 'content:render:browse', this.gallerySettings, this );
1325
1326
		Library.prototype.activate.apply( this, arguments );
1327
	},
1328
1329
	/**
1330
	 * @since 3.5.0
1331
	 */
1332
	deactivate: function() {
1333
		// Stop watching for uploaded attachments.
1334
		this.get('library').unobserve( wp.Uploader.queue );
1335
1336
		this.frame.off( 'content:render:browse', this.gallerySettings, this );
1337
1338
		Library.prototype.deactivate.apply( this, arguments );
1339
	},
1340
1341
	/**
1342
	 * @since 3.5.0
1343
	 *
1344
	 * @param browser
1345
	 */
1346
	gallerySettings: function( browser ) {
1347
		if ( ! this.get('displaySettings') ) {
1348
			return;
1349
		}
1350
1351
		var library = this.get('library');
1352
1353
		if ( ! library || ! browser ) {
1354
			return;
1355
		}
1356
1357
		library.gallery = library.gallery || new Backbone.Model();
1358
1359
		browser.sidebar.set({
1360
			gallery: new wp.media.view.Settings.Gallery({
1361
				controller: this,
1362
				model:      library.gallery,
1363
				priority:   40
1364
			})
1365
		});
1366
1367
		browser.toolbar.set( 'reverse', {
1368
			text:     l10n.reverseOrder,
1369
			priority: 80,
1370
1371
			click: function() {
1372
				library.reset( library.toArray().reverse() );
1373
			}
1374
		});
1375
	}
1376
});
1377
1378
module.exports = GalleryEdit;
1379
1380
1381
/***/ }),
1382
/* 34 */
1383
/***/ (function(module, exports) {
1384
1385
var Selection = wp.media.model.Selection,
1386
	Library = wp.media.controller.Library,
1387
	l10n = wp.media.view.l10n,
1388
	GalleryAdd;
1389
1390
/**
1391
 * wp.media.controller.GalleryAdd
1392
 *
1393
 * A state for selecting more images to add to a gallery.
1394
 *
1395
 * @memberOf wp.media.controller
1396
 *
1397
 * @class
1398
 * @augments wp.media.controller.Library
1399
 * @augments wp.media.controller.State
1400
 * @augments Backbone.Model
1401
 *
1402
 * @param {object}                     [attributes]                         The attributes hash passed to the state.
1403
 * @param {string}                     [attributes.id=gallery-library]      Unique identifier.
1404
 * @param {string}                     [attributes.title=Add to Gallery]    Title for the state. Displays in the frame's title region.
1405
 * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
1406
 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
1407
 *                                                                          If one is not supplied, a collection of all images will be created.
1408
 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
1409
 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
1410
 * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
1411
 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
1412
 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
1413
 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
1414
 * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
1415
 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
1416
 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
1417
 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
1418
 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
1419
 * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
1420
 * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
1421
 *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
1422
 */
1423
GalleryAdd = Library.extend(/** @lends wp.media.controller.GalleryAdd.prototype */{
1424
	defaults: _.defaults({
1425
		id:            'gallery-library',
1426
		title:         l10n.addToGalleryTitle,
1427
		multiple:      'add',
1428
		filterable:    'uploaded',
1429
		menu:          'gallery',
1430
		toolbar:       'gallery-add',
1431
		priority:      100,
1432
		syncSelection: false
1433
	}, Library.prototype.defaults ),
1434
1435
	/**
1436
	 * @since 3.5.0
1437
	 */
1438
	initialize: function() {
1439
		// If a library wasn't supplied, create a library of images.
1440
		if ( ! this.get('library') ) {
1441
			this.set( 'library', wp.media.query({ type: 'image' }) );
1442
		}
1443
1444
		Library.prototype.initialize.apply( this, arguments );
1445
	},
1446
1447
	/**
1448
	 * @since 3.5.0
1449
	 */
1450
	activate: function() {
1451
		var library = this.get('library'),
1452
			edit    = this.frame.state('gallery-edit').get('library');
1453
1454
		if ( this.editLibrary && this.editLibrary !== edit ) {
1455
			library.unobserve( this.editLibrary );
1456
		}
1457
1458
		// Accepts attachments that exist in the original library and
1459
		// that do not exist in gallery's library.
1460
		library.validator = function( attachment ) {
1461
			return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
1462
		};
1463
1464
		// Reset the library to ensure that all attachments are re-added
1465
		// to the collection. Do so silently, as calling `observe` will
1466
		// trigger the `reset` event.
1467
		library.reset( library.mirroring.models, { silent: true });
1468
		library.observe( edit );
1469
		this.editLibrary = edit;
1470
1471
		Library.prototype.activate.apply( this, arguments );
1472
	}
1473
});
1474
1475
module.exports = GalleryAdd;
1476
1477
1478
/***/ }),
1479
/* 35 */
1480
/***/ (function(module, exports) {
1481
1482
var Library = wp.media.controller.Library,
1483
	l10n = wp.media.view.l10n,
1484
	$ = jQuery,
1485
	CollectionEdit;
1486
1487
/**
1488
 * wp.media.controller.CollectionEdit
1489
 *
1490
 * A state for editing a collection, which is used by audio and video playlists,
1491
 * and can be used for other collections.
1492
 *
1493
 * @memberOf wp.media.controller
1494
 *
1495
 * @class
1496
 * @augments wp.media.controller.Library
1497
 * @augments wp.media.controller.State
1498
 * @augments Backbone.Model
1499
 *
1500
 * @param {object}                     [attributes]                      The attributes hash passed to the state.
1501
 * @param {string}                     attributes.title                  Title for the state. Displays in the media menu and the frame's title region.
1502
 * @param {wp.media.model.Attachments} [attributes.library]              The attachments collection to edit.
1503
 *                                                                       If one is not supplied, an empty media.model.Selection collection is created.
1504
 * @param {boolean}                    [attributes.multiple=false]       Whether multi-select is enabled.
1505
 * @param {string}                     [attributes.content=browse]       Initial mode for the content region.
1506
 * @param {string}                     attributes.menu                   Initial mode for the menu region. @todo this needs a better explanation.
1507
 * @param {boolean}                    [attributes.searchable=false]     Whether the library is searchable.
1508
 * @param {boolean}                    [attributes.sortable=true]        Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
1509
 * @param {boolean}                    [attributes.date=true]            Whether to show the date filter in the browser's toolbar.
1510
 * @param {boolean}                    [attributes.describe=true]        Whether to offer UI to describe the attachments - e.g. captioning images in a gallery.
1511
 * @param {boolean}                    [attributes.dragInfo=true]        Whether to show instructional text about the attachments being sortable.
1512
 * @param {boolean}                    [attributes.dragInfoText]         Instructional text about the attachments being sortable.
1513
 * @param {int}                        [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments.
1514
 * @param {boolean}                    [attributes.editing=false]        Whether the gallery is being created, or editing an existing instance.
1515
 * @param {int}                        [attributes.priority=60]          The priority for the state link in the media menu.
1516
 * @param {boolean}                    [attributes.syncSelection=false]  Whether the Attachments selection should be persisted from the last state.
1517
 *                                                                       Defaults to false for this state, because the library passed in  *is* the selection.
1518
 * @param {view}                       [attributes.SettingsView]         The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox).
1519
 * @param {view}                       [attributes.AttachmentView]       The single `Attachment` view to be used in the `Attachments`.
1520
 *                                                                       If none supplied, defaults to wp.media.view.Attachment.EditLibrary.
1521
 * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
1522
 * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
1523
 */
1524
CollectionEdit = Library.extend(/** @lends wp.media.controller.CollectionEdit.prototype */{
1525
	defaults: {
1526
		multiple:         false,
1527
		sortable:         true,
1528
		date:             false,
1529
		searchable:       false,
1530
		content:          'browse',
1531
		describe:         true,
1532
		dragInfo:         true,
1533
		idealColumnWidth: 170,
1534
		editing:          false,
1535
		priority:         60,
1536
		SettingsView:     false,
1537
		syncSelection:    false
1538
	},
1539
1540
	/**
1541
	 * @since 3.9.0
1542
	 */
1543
	initialize: function() {
1544
		var collectionType = this.get('collectionType');
1545
1546
		if ( 'video' === this.get( 'type' ) ) {
1547
			collectionType = 'video-' + collectionType;
1548
		}
1549
1550
		this.set( 'id', collectionType + '-edit' );
1551
		this.set( 'toolbar', collectionType + '-edit' );
1552
1553
		// If we haven't been provided a `library`, create a `Selection`.
1554
		if ( ! this.get('library') ) {
1555
			this.set( 'library', new wp.media.model.Selection() );
1556
		}
1557
		// The single `Attachment` view to be used in the `Attachments` view.
1558
		if ( ! this.get('AttachmentView') ) {
1559
			this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary );
1560
		}
1561
		Library.prototype.initialize.apply( this, arguments );
1562
	},
1563
1564
	/**
1565
	 * @since 3.9.0
1566
	 */
1567
	activate: function() {
1568
		var library = this.get('library');
1569
1570
		// Limit the library to images only.
1571
		library.props.set( 'type', this.get( 'type' ) );
1572
1573
		// Watch for uploaded attachments.
1574
		this.get('library').observe( wp.Uploader.queue );
1575
1576
		this.frame.on( 'content:render:browse', this.renderSettings, this );
1577
1578
		Library.prototype.activate.apply( this, arguments );
1579
	},
1580
1581
	/**
1582
	 * @since 3.9.0
1583
	 */
1584
	deactivate: function() {
1585
		// Stop watching for uploaded attachments.
1586
		this.get('library').unobserve( wp.Uploader.queue );
1587
1588
		this.frame.off( 'content:render:browse', this.renderSettings, this );
1589
1590
		Library.prototype.deactivate.apply( this, arguments );
1591
	},
1592
1593
	/**
1594
	 * Render the collection embed settings view in the browser sidebar.
1595
	 *
1596
	 * @todo This is against the pattern elsewhere in media. Typically the frame
1597
	 *       is responsible for adding region mode callbacks. Explain.
1598
	 *
1599
	 * @since 3.9.0
1600
	 *
1601
	 * @param {wp.media.view.attachmentsBrowser} The attachments browser view.
1602
	 */
1603
	renderSettings: function( attachmentsBrowserView ) {
1604
		var library = this.get('library'),
1605
			collectionType = this.get('collectionType'),
1606
			dragInfoText = this.get('dragInfoText'),
1607
			SettingsView = this.get('SettingsView'),
1608
			obj = {};
1609
1610
		if ( ! library || ! attachmentsBrowserView ) {
1611
			return;
1612
		}
1613
1614
		library[ collectionType ] = library[ collectionType ] || new Backbone.Model();
1615
1616
		obj[ collectionType ] = new SettingsView({
1617
			controller: this,
1618
			model:      library[ collectionType ],
1619
			priority:   40
1620
		});
1621
1622
		attachmentsBrowserView.sidebar.set( obj );
1623
1624
		if ( dragInfoText ) {
1625
			attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({
1626
				el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0],
1627
				priority: -40
1628
			}) );
1629
		}
1630
1631
		// Add the 'Reverse order' button to the toolbar.
1632
		attachmentsBrowserView.toolbar.set( 'reverse', {
1633
			text:     l10n.reverseOrder,
1634
			priority: 80,
1635
1636
			click: function() {
1637
				library.reset( library.toArray().reverse() );
1638
			}
1639
		});
1640
	}
1641
});
1642
1643
module.exports = CollectionEdit;
1644
1645
1646
/***/ }),
1647
/* 36 */
1648
/***/ (function(module, exports) {
1649
1650
var Selection = wp.media.model.Selection,
1651
	Library = wp.media.controller.Library,
1652
	CollectionAdd;
1653
1654
/**
1655
 * wp.media.controller.CollectionAdd
1656
 *
1657
 * A state for adding attachments to a collection (e.g. video playlist).
1658
 *
1659
 * @memberOf wp.media.controller
1660
 *
1661
 * @class
1662
 * @augments wp.media.controller.Library
1663
 * @augments wp.media.controller.State
1664
 * @augments Backbone.Model
1665
 *
1666
 * @param {object}                     [attributes]                         The attributes hash passed to the state.
1667
 * @param {string}                     [attributes.id=library]      Unique identifier.
1668
 * @param {string}                     attributes.title                    Title for the state. Displays in the frame's title region.
1669
 * @param {boolean}                    [attributes.multiple=add]            Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean.
1670
 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
1671
 *                                                                          If one is not supplied, a collection of attachments of the specified type will be created.
1672
 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
1673
 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
1674
 * @param {string}                     [attributes.menu=gallery]            Initial mode for the menu region.
1675
 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
1676
 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
1677
 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
1678
 * @param {string}                     [attributes.toolbar=gallery-add]     Initial mode for the toolbar region.
1679
 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
1680
 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
1681
 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
1682
 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
1683
 * @param {int}                        [attributes.priority=100]            The priority for the state link in the media menu.
1684
 * @param {boolean}                    [attributes.syncSelection=false]     Whether the Attachments selection should be persisted from the last state.
1685
 *                                                                          Defaults to false because for this state, because the library of the Edit Gallery state is the selection.
1686
 * @param {string}                     attributes.type                   The collection's media type. (e.g. 'video').
1687
 * @param {string}                     attributes.collectionType         The collection type. (e.g. 'playlist').
1688
 */
1689
CollectionAdd = Library.extend(/** @lends wp.media.controller.CollectionAdd.prototype */{
1690
	defaults: _.defaults( {
1691
		// Selection defaults. @see media.model.Selection
1692
		multiple:      'add',
1693
		// Attachments browser defaults. @see media.view.AttachmentsBrowser
1694
		filterable:    'uploaded',
1695
1696
		priority:      100,
1697
		syncSelection: false
1698
	}, Library.prototype.defaults ),
1699
1700
	/**
1701
	 * @since 3.9.0
1702
	 */
1703
	initialize: function() {
1704
		var collectionType = this.get('collectionType');
1705
1706
		if ( 'video' === this.get( 'type' ) ) {
1707
			collectionType = 'video-' + collectionType;
1708
		}
1709
1710
		this.set( 'id', collectionType + '-library' );
1711
		this.set( 'toolbar', collectionType + '-add' );
1712
		this.set( 'menu', collectionType );
1713
1714
		// If we haven't been provided a `library`, create a `Selection`.
1715
		if ( ! this.get('library') ) {
1716
			this.set( 'library', wp.media.query({ type: this.get('type') }) );
1717
		}
1718
		Library.prototype.initialize.apply( this, arguments );
1719
	},
1720
1721
	/**
1722
	 * @since 3.9.0
1723
	 */
1724
	activate: function() {
1725
		var library = this.get('library'),
1726
			editLibrary = this.get('editLibrary'),
1727
			edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library');
1728
1729
		if ( editLibrary && editLibrary !== edit ) {
1730
			library.unobserve( editLibrary );
1731
		}
1732
1733
		// Accepts attachments that exist in the original library and
1734
		// that do not exist in gallery's library.
1735
		library.validator = function( attachment ) {
1736
			return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments );
1737
		};
1738
1739
		// Reset the library to ensure that all attachments are re-added
1740
		// to the collection. Do so silently, as calling `observe` will
1741
		// trigger the `reset` event.
1742
		library.reset( library.mirroring.models, { silent: true });
1743
		library.observe( edit );
1744
		this.set('editLibrary', edit);
1745
1746
		Library.prototype.activate.apply( this, arguments );
1747
	}
1748
});
1749
1750
module.exports = CollectionAdd;
1751
1752
1753
/***/ }),
1754
/* 37 */
1755
/***/ (function(module, exports) {
1756
1757
var Attachment = wp.media.model.Attachment,
1758
	Library = wp.media.controller.Library,
1759
	l10n = wp.media.view.l10n,
1760
	FeaturedImage;
1761
1762
/**
1763
 * wp.media.controller.FeaturedImage
1764
 *
1765
 * A state for selecting a featured image for a post.
1766
 *
1767
 * @memberOf wp.media.controller
1768
 *
1769
 * @class
1770
 * @augments wp.media.controller.Library
1771
 * @augments wp.media.controller.State
1772
 * @augments Backbone.Model
1773
 *
1774
 * @param {object}                     [attributes]                          The attributes hash passed to the state.
1775
 * @param {string}                     [attributes.id=featured-image]        Unique identifier.
1776
 * @param {string}                     [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region.
1777
 * @param {wp.media.model.Attachments} [attributes.library]                  The attachments collection to browse.
1778
 *                                                                           If one is not supplied, a collection of all images will be created.
1779
 * @param {boolean}                    [attributes.multiple=false]           Whether multi-select is enabled.
1780
 * @param {string}                     [attributes.content=upload]           Initial mode for the content region.
1781
 *                                                                           Overridden by persistent user setting if 'contentUserSetting' is true.
1782
 * @param {string}                     [attributes.menu=default]             Initial mode for the menu region.
1783
 * @param {string}                     [attributes.router=browse]            Initial mode for the router region.
1784
 * @param {string}                     [attributes.toolbar=featured-image]   Initial mode for the toolbar region.
1785
 * @param {int}                        [attributes.priority=60]              The priority for the state link in the media menu.
1786
 * @param {boolean}                    [attributes.searchable=true]          Whether the library is searchable.
1787
 * @param {boolean|string}             [attributes.filterable=false]         Whether the library is filterable, and if so what filters should be shown.
1788
 *                                                                           Accepts 'all', 'uploaded', or 'unattached'.
1789
 * @param {boolean}                    [attributes.sortable=true]            Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
1790
 * @param {boolean}                    [attributes.autoSelect=true]          Whether an uploaded attachment should be automatically added to the selection.
1791
 * @param {boolean}                    [attributes.describe=false]           Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
1792
 * @param {boolean}                    [attributes.contentUserSetting=true]  Whether the content region's mode should be set and persisted per user.
1793
 * @param {boolean}                    [attributes.syncSelection=true]       Whether the Attachments selection should be persisted from the last state.
1794
 */
1795
FeaturedImage = Library.extend(/** @lends wp.media.controller.FeaturedImage.prototype */{
1796
	defaults: _.defaults({
1797
		id:            'featured-image',
1798
		title:         l10n.setFeaturedImageTitle,
1799
		multiple:      false,
1800
		filterable:    'uploaded',
1801
		toolbar:       'featured-image',
1802
		priority:      60,
1803
		syncSelection: true
1804
	}, Library.prototype.defaults ),
1805
1806
	/**
1807
	 * @since 3.5.0
1808
	 */
1809
	initialize: function() {
1810
		var library, comparator;
1811
1812
		// If we haven't been provided a `library`, create a `Selection`.
1813
		if ( ! this.get('library') ) {
1814
			this.set( 'library', wp.media.query({ type: 'image' }) );
1815
		}
1816
1817
		Library.prototype.initialize.apply( this, arguments );
1818
1819
		library    = this.get('library');
1820
		comparator = library.comparator;
1821
1822
		// Overload the library's comparator to push items that are not in
1823
		// the mirrored query to the front of the aggregate collection.
1824
		library.comparator = function( a, b ) {
1825
			var aInQuery = !! this.mirroring.get( a.cid ),
1826
				bInQuery = !! this.mirroring.get( b.cid );
1827
1828
			if ( ! aInQuery && bInQuery ) {
1829
				return -1;
1830
			} else if ( aInQuery && ! bInQuery ) {
1831
				return 1;
1832
			} else {
1833
				return comparator.apply( this, arguments );
1834
			}
1835
		};
1836
1837
		// Add all items in the selection to the library, so any featured
1838
		// images that are not initially loaded still appear.
1839
		library.observe( this.get('selection') );
1840
	},
1841
1842
	/**
1843
	 * @since 3.5.0
1844
	 */
1845
	activate: function() {
1846
		this.updateSelection();
1847
		this.frame.on( 'open', this.updateSelection, this );
1848
1849
		Library.prototype.activate.apply( this, arguments );
1850
	},
1851
1852
	/**
1853
	 * @since 3.5.0
1854
	 */
1855
	deactivate: function() {
1856
		this.frame.off( 'open', this.updateSelection, this );
1857
1858
		Library.prototype.deactivate.apply( this, arguments );
1859
	},
1860
1861
	/**
1862
	 * @since 3.5.0
1863
	 */
1864
	updateSelection: function() {
1865
		var selection = this.get('selection'),
1866
			id = wp.media.view.settings.post.featuredImageId,
1867
			attachment;
1868
1869
		if ( '' !== id && -1 !== id ) {
1870
			attachment = Attachment.get( id );
1871
			attachment.fetch();
1872
		}
1873
1874
		selection.reset( attachment ? [ attachment ] : [] );
1875
	}
1876
});
1877
1878
module.exports = FeaturedImage;
1879
1880
1881
/***/ }),
1882
/* 38 */
1883
/***/ (function(module, exports) {
1884
1885
var Library = wp.media.controller.Library,
1886
	l10n = wp.media.view.l10n,
1887
	ReplaceImage;
1888
1889
/**
1890
 * wp.media.controller.ReplaceImage
1891
 *
1892
 * A state for replacing an image.
1893
 *
1894
 * @memberOf wp.media.controller
1895
 *
1896
 * @class
1897
 * @augments wp.media.controller.Library
1898
 * @augments wp.media.controller.State
1899
 * @augments Backbone.Model
1900
 *
1901
 * @param {object}                     [attributes]                         The attributes hash passed to the state.
1902
 * @param {string}                     [attributes.id=replace-image]        Unique identifier.
1903
 * @param {string}                     [attributes.title=Replace Image]     Title for the state. Displays in the media menu and the frame's title region.
1904
 * @param {wp.media.model.Attachments} [attributes.library]                 The attachments collection to browse.
1905
 *                                                                          If one is not supplied, a collection of all images will be created.
1906
 * @param {boolean}                    [attributes.multiple=false]          Whether multi-select is enabled.
1907
 * @param {string}                     [attributes.content=upload]          Initial mode for the content region.
1908
 *                                                                          Overridden by persistent user setting if 'contentUserSetting' is true.
1909
 * @param {string}                     [attributes.menu=default]            Initial mode for the menu region.
1910
 * @param {string}                     [attributes.router=browse]           Initial mode for the router region.
1911
 * @param {string}                     [attributes.toolbar=replace]         Initial mode for the toolbar region.
1912
 * @param {int}                        [attributes.priority=60]             The priority for the state link in the media menu.
1913
 * @param {boolean}                    [attributes.searchable=true]         Whether the library is searchable.
1914
 * @param {boolean|string}             [attributes.filterable=uploaded]     Whether the library is filterable, and if so what filters should be shown.
1915
 *                                                                          Accepts 'all', 'uploaded', or 'unattached'.
1916
 * @param {boolean}                    [attributes.sortable=true]           Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.
1917
 * @param {boolean}                    [attributes.autoSelect=true]         Whether an uploaded attachment should be automatically added to the selection.
1918
 * @param {boolean}                    [attributes.describe=false]          Whether to offer UI to describe attachments - e.g. captioning images in a gallery.
1919
 * @param {boolean}                    [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user.
1920
 * @param {boolean}                    [attributes.syncSelection=true]      Whether the Attachments selection should be persisted from the last state.
1921
 */
1922
ReplaceImage = Library.extend(/** @lends wp.media.controller.ReplaceImage.prototype */{
1923
	defaults: _.defaults({
1924
		id:            'replace-image',
1925
		title:         l10n.replaceImageTitle,
1926
		multiple:      false,
1927
		filterable:    'uploaded',
1928
		toolbar:       'replace',
1929
		menu:          false,
1930
		priority:      60,
1931
		syncSelection: true
1932
	}, Library.prototype.defaults ),
1933
1934
	/**
1935
	 * @since 3.9.0
1936
	 *
1937
	 * @param options
1938
	 */
1939
	initialize: function( options ) {
1940
		var library, comparator;
1941
1942
		this.image = options.image;
1943
		// If we haven't been provided a `library`, create a `Selection`.
1944
		if ( ! this.get('library') ) {
1945
			this.set( 'library', wp.media.query({ type: 'image' }) );
1946
		}
1947
1948
		Library.prototype.initialize.apply( this, arguments );
1949
1950
		library    = this.get('library');
1951
		comparator = library.comparator;
1952
1953
		// Overload the library's comparator to push items that are not in
1954
		// the mirrored query to the front of the aggregate collection.
1955
		library.comparator = function( a, b ) {
1956
			var aInQuery = !! this.mirroring.get( a.cid ),
1957
				bInQuery = !! this.mirroring.get( b.cid );
1958
1959
			if ( ! aInQuery && bInQuery ) {
1960
				return -1;
1961
			} else if ( aInQuery && ! bInQuery ) {
1962
				return 1;
1963
			} else {
1964
				return comparator.apply( this, arguments );
1965
			}
1966
		};
1967
1968
		// Add all items in the selection to the library, so any featured
1969
		// images that are not initially loaded still appear.
1970
		library.observe( this.get('selection') );
1971
	},
1972
1973
	/**
1974
	 * @since 3.9.0
1975
	 */
1976
	activate: function() {
1977
		this.updateSelection();
1978
		Library.prototype.activate.apply( this, arguments );
1979
	},
1980
1981
	/**
1982
	 * @since 3.9.0
1983
	 */
1984
	updateSelection: function() {
1985
		var selection = this.get('selection'),
1986
			attachment = this.image.attachment;
1987
1988
		selection.reset( attachment ? [ attachment ] : [] );
1989
	}
1990
});
1991
1992
module.exports = ReplaceImage;
1993
1994
1995
/***/ }),
1996
/* 39 */
1997
/***/ (function(module, exports) {
1998
1999
var l10n = wp.media.view.l10n,
2000
	EditImage;
2001
2002
/**
2003
 * wp.media.controller.EditImage
2004
 *
2005
 * A state for editing (cropping, etc.) an image.
2006
 *
2007
 * @memberOf wp.media.controller
2008
 *
2009
 * @class
2010
 * @augments wp.media.controller.State
2011
 * @augments Backbone.Model
2012
 *
2013
 * @param {object}                    attributes                      The attributes hash passed to the state.
2014
 * @param {wp.media.model.Attachment} attributes.model                The attachment.
2015
 * @param {string}                    [attributes.id=edit-image]      Unique identifier.
2016
 * @param {string}                    [attributes.title=Edit Image]   Title for the state. Displays in the media menu and the frame's title region.
2017
 * @param {string}                    [attributes.content=edit-image] Initial mode for the content region.
2018
 * @param {string}                    [attributes.toolbar=edit-image] Initial mode for the toolbar region.
2019
 * @param {string}                    [attributes.menu=false]         Initial mode for the menu region.
2020
 * @param {string}                    [attributes.url]                Unused. @todo Consider removal.
2021
 */
2022
EditImage = wp.media.controller.State.extend(/** @lends wp.media.controller.EditImage.prototype */{
2023
	defaults: {
2024
		id:      'edit-image',
2025
		title:   l10n.editImage,
2026
		menu:    false,
2027
		toolbar: 'edit-image',
2028
		content: 'edit-image',
2029
		url:     ''
2030
	},
2031
2032
	/**
2033
	 * @since 3.9.0
2034
	 */
2035
	activate: function() {
2036
		this.frame.on( 'toolbar:render:edit-image', _.bind( this.toolbar, this ) );
2037
	},
2038
2039
	/**
2040
	 * @since 3.9.0
2041
	 */
2042
	deactivate: function() {
2043
		this.frame.off( 'toolbar:render:edit-image' );
2044
	},
2045
2046
	/**
2047
	 * @since 3.9.0
2048
	 */
2049
	toolbar: function() {
2050
		var frame = this.frame,
2051
			lastState = frame.lastState(),
2052
			previous = lastState && lastState.id;
2053
2054
		frame.toolbar.set( new wp.media.view.Toolbar({
2055
			controller: frame,
2056
			items: {
2057
				back: {
2058
					style: 'primary',
2059
					text:     l10n.back,
2060
					priority: 20,
2061
					click:    function() {
2062
						if ( previous ) {
2063
							frame.setState( previous );
2064
						} else {
2065
							frame.close();
2066
						}
2067
					}
2068
				}
2069
			}
2070
		}) );
2071
	}
2072
});
2073
2074
module.exports = EditImage;
2075
2076
2077
/***/ }),
2078
/* 40 */
2079
/***/ (function(module, exports) {
2080
2081
/**
2082
 * wp.media.controller.MediaLibrary
2083
 *
2084
 * @memberOf wp.media.controller
2085
 *
2086
 * @class
2087
 * @augments wp.media.controller.Library
2088
 * @augments wp.media.controller.State
2089
 * @augments Backbone.Model
2090
 */
2091
var Library = wp.media.controller.Library,
2092
	MediaLibrary;
2093
2094
MediaLibrary = Library.extend(/** @lends wp.media.controller.MediaLibrary.prototype */{
2095
	defaults: _.defaults({
2096
		// Attachments browser defaults. @see media.view.AttachmentsBrowser
2097
		filterable:      'uploaded',
2098
2099
		displaySettings: false,
2100
		priority:        80,
2101
		syncSelection:   false
2102
	}, Library.prototype.defaults ),
2103
2104
	/**
2105
	 * @since 3.9.0
2106
	 *
2107
	 * @param options
2108
	 */
2109
	initialize: function( options ) {
2110
		this.media = options.media;
2111
		this.type = options.type;
2112
		this.set( 'library', wp.media.query({ type: this.type }) );
2113
2114
		Library.prototype.initialize.apply( this, arguments );
2115
	},
2116
2117
	/**
2118
	 * @since 3.9.0
2119
	 */
2120
	activate: function() {
2121
		// @todo this should use this.frame.
2122
		if ( wp.media.frame.lastMime ) {
2123
			this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) );
2124
			delete wp.media.frame.lastMime;
2125
		}
2126
		Library.prototype.activate.apply( this, arguments );
2127
	}
2128
});
2129
2130
module.exports = MediaLibrary;
2131
2132
2133
/***/ }),
2134
/* 41 */
2135
/***/ (function(module, exports) {
2136
2137
var l10n = wp.media.view.l10n,
2138
	$ = Backbone.$,
2139
	Embed;
2140
2141
/**
2142
 * wp.media.controller.Embed
2143
 *
2144
 * A state for embedding media from a URL.
2145
 *
2146
 * @memberOf wp.media.controller
2147
 *
2148
 * @class
2149
 * @augments wp.media.controller.State
2150
 * @augments Backbone.Model
2151
 *
2152
 * @param {object} attributes                         The attributes hash passed to the state.
2153
 * @param {string} [attributes.id=embed]              Unique identifier.
2154
 * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region.
2155
 * @param {string} [attributes.content=embed]         Initial mode for the content region.
2156
 * @param {string} [attributes.menu=default]          Initial mode for the menu region.
2157
 * @param {string} [attributes.toolbar=main-embed]    Initial mode for the toolbar region.
2158
 * @param {string} [attributes.menu=false]            Initial mode for the menu region.
2159
 * @param {int}    [attributes.priority=120]          The priority for the state link in the media menu.
2160
 * @param {string} [attributes.type=link]             The type of embed. Currently only link is supported.
2161
 * @param {string} [attributes.url]                   The embed URL.
2162
 * @param {object} [attributes.metadata={}]           Properties of the embed, which will override attributes.url if set.
2163
 */
2164
Embed = wp.media.controller.State.extend(/** @lends wp.media.controller.Embed.prototype */{
2165
	defaults: {
2166
		id:       'embed',
2167
		title:    l10n.insertFromUrlTitle,
2168
		content:  'embed',
2169
		menu:     'default',
2170
		toolbar:  'main-embed',
2171
		priority: 120,
2172
		type:     'link',
2173
		url:      '',
2174
		metadata: {}
2175
	},
2176
2177
	// The amount of time used when debouncing the scan.
2178
	sensitivity: 400,
2179
2180
	initialize: function(options) {
2181
		this.metadata = options.metadata;
2182
		this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity );
2183
		this.props = new Backbone.Model( this.metadata || { url: '' });
2184
		this.props.on( 'change:url', this.debouncedScan, this );
2185
		this.props.on( 'change:url', this.refresh, this );
2186
		this.on( 'scan', this.scanImage, this );
2187
	},
2188
2189
	/**
2190
	 * Trigger a scan of the embedded URL's content for metadata required to embed.
2191
	 *
2192
	 * @fires wp.media.controller.Embed#scan
2193
	 */
2194
	scan: function() {
2195
		var scanners,
2196
			embed = this,
2197
			attributes = {
2198
				type: 'link',
2199
				scanners: []
2200
			};
2201
2202
		// Scan is triggered with the list of `attributes` to set on the
2203
		// state, useful for the 'type' attribute and 'scanners' attribute,
2204
		// an array of promise objects for asynchronous scan operations.
2205
		if ( this.props.get('url') ) {
2206
			this.trigger( 'scan', attributes );
2207
		}
2208
2209
		if ( attributes.scanners.length ) {
2210
			scanners = attributes.scanners = $.when.apply( $, attributes.scanners );
2211
			scanners.always( function() {
2212
				if ( embed.get('scanners') === scanners ) {
2213
					embed.set( 'loading', false );
2214
				}
2215
			});
2216
		} else {
2217
			attributes.scanners = null;
2218
		}
2219
2220
		attributes.loading = !! attributes.scanners;
2221
		this.set( attributes );
2222
	},
2223
	/**
2224
	 * Try scanning the embed as an image to discover its dimensions.
2225
	 *
2226
	 * @param {Object} attributes
2227
	 */
2228
	scanImage: function( attributes ) {
2229
		var frame = this.frame,
2230
			state = this,
2231
			url = this.props.get('url'),
2232
			image = new Image(),
2233
			deferred = $.Deferred();
2234
2235
		attributes.scanners.push( deferred.promise() );
2236
2237
		// Try to load the image and find its width/height.
2238
		image.onload = function() {
2239
			deferred.resolve();
2240
2241
			if ( state !== frame.state() || url !== state.props.get('url') ) {
2242
				return;
2243
			}
2244
2245
			state.set({
2246
				type: 'image'
2247
			});
2248
2249
			state.props.set({
2250
				width:  image.width,
2251
				height: image.height
2252
			});
2253
		};
2254
2255
		image.onerror = deferred.reject;
2256
		image.src = url;
2257
	},
2258
2259
	refresh: function() {
2260
		this.frame.toolbar.get().refresh();
2261
	},
2262
2263
	reset: function() {
2264
		this.props.clear().set({ url: '' });
2265
2266
		if ( this.active ) {
2267
			this.refresh();
2268
		}
2269
	}
2270
});
2271
2272
module.exports = Embed;
2273
2274
2275
/***/ }),
2276
/* 42 */
2277
/***/ (function(module, exports) {
2278
2279
var l10n = wp.media.view.l10n,
2280
	Cropper;
2281
2282
/**
2283
 * wp.media.controller.Cropper
2284
 *
2285
 * A state for cropping an image.
2286
 *
2287
 * @memberOf wp.media.controller
2288
 *
2289
 * @class
2290
 * @augments wp.media.controller.State
2291
 * @augments Backbone.Model
2292
 */
2293
Cropper = wp.media.controller.State.extend(/** @lends wp.media.controller.Cropper.prototype */{
2294
	defaults: {
2295
		id:          'cropper',
2296
		title:       l10n.cropImage,
2297
		// Region mode defaults.
2298
		toolbar:     'crop',
2299
		content:     'crop',
2300
		router:      false,
2301
		canSkipCrop: false,
2302
2303
		// Default doCrop Ajax arguments to allow the Customizer (for example) to inject state.
2304
		doCropArgs: {}
2305
	},
2306
2307
	activate: function() {
2308
		this.frame.on( 'content:create:crop', this.createCropContent, this );
2309
		this.frame.on( 'close', this.removeCropper, this );
2310
		this.set('selection', new Backbone.Collection(this.frame._selection.single));
2311
	},
2312
2313
	deactivate: function() {
2314
		this.frame.toolbar.mode('browse');
2315
	},
2316
2317
	createCropContent: function() {
2318
		this.cropperView = new wp.media.view.Cropper({
2319
			controller: this,
2320
			attachment: this.get('selection').first()
2321
		});
2322
		this.cropperView.on('image-loaded', this.createCropToolbar, this);
2323
		this.frame.content.set(this.cropperView);
2324
2325
	},
2326
	removeCropper: function() {
2327
		this.imgSelect.cancelSelection();
2328
		this.imgSelect.setOptions({remove: true});
2329
		this.imgSelect.update();
2330
		this.cropperView.remove();
2331
	},
2332
	createCropToolbar: function() {
2333
		var canSkipCrop, toolbarOptions;
2334
2335
		canSkipCrop = this.get('canSkipCrop') || false;
2336
2337
		toolbarOptions = {
2338
			controller: this.frame,
2339
			items: {
2340
				insert: {
2341
					style:    'primary',
2342
					text:     l10n.cropImage,
2343
					priority: 80,
2344
					requires: { library: false, selection: false },
2345
2346
					click: function() {
2347
						var controller = this.controller,
2348
							selection;
2349
2350
						selection = controller.state().get('selection').first();
2351
						selection.set({cropDetails: controller.state().imgSelect.getSelection()});
2352
2353
						this.$el.text(l10n.cropping);
2354
						this.$el.attr('disabled', true);
2355
2356
						controller.state().doCrop( selection ).done( function( croppedImage ) {
2357
							controller.trigger('cropped', croppedImage );
2358
							controller.close();
2359
						}).fail( function() {
2360
							controller.trigger('content:error:crop');
2361
						});
2362
					}
2363
				}
2364
			}
2365
		};
2366
2367
		if ( canSkipCrop ) {
2368
			_.extend( toolbarOptions.items, {
2369
				skip: {
2370
					style:      'secondary',
2371
					text:       l10n.skipCropping,
2372
					priority:   70,
2373
					requires:   { library: false, selection: false },
2374
					click:      function() {
2375
						var selection = this.controller.state().get('selection').first();
2376
						this.controller.state().cropperView.remove();
2377
						this.controller.trigger('skippedcrop', selection);
2378
						this.controller.close();
2379
					}
2380
				}
2381
			});
2382
		}
2383
2384
		this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) );
2385
	},
2386
2387
	doCrop: function( attachment ) {
2388
		return wp.ajax.post( 'custom-header-crop', _.extend(
2389
			{},
2390
			this.defaults.doCropArgs,
2391
			{
2392
				nonce: attachment.get( 'nonces' ).edit,
2393
				id: attachment.get( 'id' ),
2394
				cropDetails: attachment.get( 'cropDetails' )
2395
			}
2396
		) );
2397
	}
2398
});
2399
2400
module.exports = Cropper;
2401
2402
2403
/***/ }),
2404
/* 43 */
2405
/***/ (function(module, exports) {
2406
2407
var Controller = wp.media.controller,
2408
	CustomizeImageCropper;
2409
2410
/**
2411
 * wp.media.controller.CustomizeImageCropper
2412
 *
2413
 * @memberOf wp.media.controller
2414
 *
2415
 * A state for cropping an image.
2416
 *
2417
 * @class
2418
 * @augments wp.media.controller.Cropper
2419
 * @augments wp.media.controller.State
2420
 * @augments Backbone.Model
2421
 */
2422
CustomizeImageCropper = Controller.Cropper.extend(/** @lends wp.media.controller.CustomizeImageCropper.prototype */{
2423
	doCrop: function( attachment ) {
2424
		var cropDetails = attachment.get( 'cropDetails' ),
2425
			control = this.get( 'control' ),
2426
			ratio = cropDetails.width / cropDetails.height;
2427
2428
		// Use crop measurements when flexible in both directions.
2429
		if ( control.params.flex_width && control.params.flex_height ) {
2430
			cropDetails.dst_width  = cropDetails.width;
2431
			cropDetails.dst_height = cropDetails.height;
2432
2433
		// Constrain flexible side based on image ratio and size of the fixed side.
2434
		} else {
2435
			cropDetails.dst_width  = control.params.flex_width  ? control.params.height * ratio : control.params.width;
2436
			cropDetails.dst_height = control.params.flex_height ? control.params.width  / ratio : control.params.height;
2437
		}
2438
2439
		return wp.ajax.post( 'crop-image', {
2440
			wp_customize: 'on',
2441
			nonce: attachment.get( 'nonces' ).edit,
2442
			id: attachment.get( 'id' ),
2443
			context: control.id,
2444
			cropDetails: cropDetails
2445
		} );
2446
	}
2447
});
2448
2449
module.exports = CustomizeImageCropper;
2450
2451
2452
/***/ }),
2453
/* 44 */
2454
/***/ (function(module, exports) {
2455
2456
var Controller = wp.media.controller,
2457
	SiteIconCropper;
2458
2459
/**
2460
 * wp.media.controller.SiteIconCropper
2461
 *
2462
 * A state for cropping a Site Icon.
2463
 *
2464
 * @memberOf wp.media.controller
2465
 *
2466
 * @class
2467
 * @augments wp.media.controller.Cropper
2468
 * @augments wp.media.controller.State
2469
 * @augments Backbone.Model
2470
 */
2471
SiteIconCropper = Controller.Cropper.extend(/** @lends wp.media.controller.SiteIconCropper.prototype */{
2472
	activate: function() {
2473
		this.frame.on( 'content:create:crop', this.createCropContent, this );
2474
		this.frame.on( 'close', this.removeCropper, this );
2475
		this.set('selection', new Backbone.Collection(this.frame._selection.single));
2476
	},
2477
2478
	createCropContent: function() {
2479
		this.cropperView = new wp.media.view.SiteIconCropper({
2480
			controller: this,
2481
			attachment: this.get('selection').first()
2482
		});
2483
		this.cropperView.on('image-loaded', this.createCropToolbar, this);
2484
		this.frame.content.set(this.cropperView);
2485
2486
	},
2487
2488
	doCrop: function( attachment ) {
2489
		var cropDetails = attachment.get( 'cropDetails' ),
2490
			control = this.get( 'control' );
2491
2492
		cropDetails.dst_width  = control.params.width;
2493
		cropDetails.dst_height = control.params.height;
2494
2495
		return wp.ajax.post( 'crop-image', {
2496
			nonce: attachment.get( 'nonces' ).edit,
2497
			id: attachment.get( 'id' ),
2498
			context: 'site-icon',
2499
			cropDetails: cropDetails
2500
		} );
2501
	}
2502
});
2503
2504
module.exports = SiteIconCropper;
2505
2506
2507
/***/ }),
2508
/* 45 */
2509
/***/ (function(module, exports) {
2510
2511
/**
2512
 * wp.media.View
2513
 *
2514
 * The base view class for media.
2515
 *
2516
 * Undelegating events, removing events from the model, and
2517
 * removing events from the controller mirror the code for
2518
 * `Backbone.View.dispose` in Backbone 0.9.8 development.
2519
 *
2520
 * This behavior has since been removed, and should not be used
2521
 * outside of the media manager.
2522
 *
2523
 * @memberOf wp.media
2524
 *
2525
 * @class
2526
 * @augments wp.Backbone.View
2527
 * @augments Backbone.View
2528
 */
2529
var View = wp.Backbone.View.extend(/** @lends wp.media.View.prototype */{
2530
	constructor: function( options ) {
2531
		if ( options && options.controller ) {
2532
			this.controller = options.controller;
2533
		}
2534
		wp.Backbone.View.apply( this, arguments );
2535
	},
2536
	/**
2537
	 * @todo The internal comment mentions this might have been a stop-gap
2538
	 *       before Backbone 0.9.8 came out. Figure out if Backbone core takes
2539
	 *       care of this in Backbone.View now.
2540
	 *
2541
	 * @returns {wp.media.View} Returns itself to allow chaining
2542
	 */
2543
	dispose: function() {
2544
		// Undelegating events, removing events from the model, and
2545
		// removing events from the controller mirror the code for
2546
		// `Backbone.View.dispose` in Backbone 0.9.8 development.
2547
		this.undelegateEvents();
2548
2549
		if ( this.model && this.model.off ) {
2550
			this.model.off( null, null, this );
2551
		}
2552
2553
		if ( this.collection && this.collection.off ) {
2554
			this.collection.off( null, null, this );
2555
		}
2556
2557
		// Unbind controller events.
2558
		if ( this.controller && this.controller.off ) {
2559
			this.controller.off( null, null, this );
2560
		}
2561
2562
		return this;
2563
	},
2564
	/**
2565
	 * @returns {wp.media.View} Returns itself to allow chaining
2566
	 */
2567
	remove: function() {
2568
		this.dispose();
2569
		/**
2570
		 * call 'remove' directly on the parent class
2571
		 */
2572
		return wp.Backbone.View.prototype.remove.apply( this, arguments );
2573
	}
2574
});
2575
2576
module.exports = View;
2577
2578
2579
/***/ }),
2580
/* 46 */
2581
/***/ (function(module, exports) {
2582
2583
/**
2584
 * wp.media.view.Frame
2585
 *
2586
 * A frame is a composite view consisting of one or more regions and one or more
2587
 * states.
2588
 *
2589
 * @memberOf wp.media.view
2590
 *
2591
 * @see wp.media.controller.State
2592
 * @see wp.media.controller.Region
2593
 *
2594
 * @class
2595
 * @augments wp.media.View
2596
 * @augments wp.Backbone.View
2597
 * @augments Backbone.View
2598
 * @mixes wp.media.controller.StateMachine
2599
 */
2600
var Frame = wp.media.View.extend(/** @lends wp.media.view.Frame.prototype */{
2601
	initialize: function() {
2602
		_.defaults( this.options, {
2603
			mode: [ 'select' ]
2604
		});
2605
		this._createRegions();
2606
		this._createStates();
2607
		this._createModes();
2608
	},
2609
2610
	_createRegions: function() {
2611
		// Clone the regions array.
2612
		this.regions = this.regions ? this.regions.slice() : [];
2613
2614
		// Initialize regions.
2615
		_.each( this.regions, function( region ) {
2616
			this[ region ] = new wp.media.controller.Region({
2617
				view:     this,
2618
				id:       region,
2619
				selector: '.media-frame-' + region
2620
			});
2621
		}, this );
2622
	},
2623
	/**
2624
	 * Create the frame's states.
2625
	 *
2626
	 * @see wp.media.controller.State
2627
	 * @see wp.media.controller.StateMachine
2628
	 *
2629
	 * @fires wp.media.controller.State#ready
2630
	 */
2631
	_createStates: function() {
2632
		// Create the default `states` collection.
2633
		this.states = new Backbone.Collection( null, {
2634
			model: wp.media.controller.State
2635
		});
2636
2637
		// Ensure states have a reference to the frame.
2638
		this.states.on( 'add', function( model ) {
2639
			model.frame = this;
2640
			model.trigger('ready');
2641
		}, this );
2642
2643
		if ( this.options.states ) {
2644
			this.states.add( this.options.states );
2645
		}
2646
	},
2647
2648
	/**
2649
	 * A frame can be in a mode or multiple modes at one time.
2650
	 *
2651
	 * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode.
2652
	 */
2653
	_createModes: function() {
2654
		// Store active "modes" that the frame is in. Unrelated to region modes.
2655
		this.activeModes = new Backbone.Collection();
2656
		this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) );
2657
2658
		_.each( this.options.mode, function( mode ) {
2659
			this.activateMode( mode );
2660
		}, this );
2661
	},
2662
	/**
2663
	 * Reset all states on the frame to their defaults.
2664
	 *
2665
	 * @returns {wp.media.view.Frame} Returns itself to allow chaining
2666
	 */
2667
	reset: function() {
2668
		this.states.invoke( 'trigger', 'reset' );
2669
		return this;
2670
	},
2671
	/**
2672
	 * Map activeMode collection events to the frame.
2673
	 */
2674
	triggerModeEvents: function( model, collection, options ) {
2675
		var collectionEvent,
2676
			modeEventMap = {
2677
				add: 'activate',
2678
				remove: 'deactivate'
2679
			},
2680
			eventToTrigger;
2681
		// Probably a better way to do this.
2682
		_.each( options, function( value, key ) {
2683
			if ( value ) {
2684
				collectionEvent = key;
2685
			}
2686
		} );
2687
2688
		if ( ! _.has( modeEventMap, collectionEvent ) ) {
2689
			return;
2690
		}
2691
2692
		eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent];
2693
		this.trigger( eventToTrigger );
2694
	},
2695
	/**
2696
	 * Activate a mode on the frame.
2697
	 *
2698
	 * @param string mode Mode ID.
2699
	 * @returns {this} Returns itself to allow chaining.
2700
	 */
2701
	activateMode: function( mode ) {
2702
		// Bail if the mode is already active.
2703
		if ( this.isModeActive( mode ) ) {
2704
			return;
2705
		}
2706
		this.activeModes.add( [ { id: mode } ] );
2707
		// Add a CSS class to the frame so elements can be styled for the mode.
2708
		this.$el.addClass( 'mode-' + mode );
2709
2710
		return this;
2711
	},
2712
	/**
2713
	 * Deactivate a mode on the frame.
2714
	 *
2715
	 * @param string mode Mode ID.
2716
	 * @returns {this} Returns itself to allow chaining.
2717
	 */
2718
	deactivateMode: function( mode ) {
2719
		// Bail if the mode isn't active.
2720
		if ( ! this.isModeActive( mode ) ) {
2721
			return this;
2722
		}
2723
		this.activeModes.remove( this.activeModes.where( { id: mode } ) );
2724
		this.$el.removeClass( 'mode-' + mode );
2725
		/**
2726
		 * Frame mode deactivation event.
2727
		 *
2728
		 * @event wp.media.view.Frame#{mode}:deactivate
2729
		 */
2730
		this.trigger( mode + ':deactivate' );
2731
2732
		return this;
2733
	},
2734
	/**
2735
	 * Check if a mode is enabled on the frame.
2736
	 *
2737
	 * @param  string mode Mode ID.
2738
	 * @return bool
2739
	 */
2740
	isModeActive: function( mode ) {
2741
		return Boolean( this.activeModes.where( { id: mode } ).length );
2742
	}
2743
});
2744
2745
// Make the `Frame` a `StateMachine`.
2746
_.extend( Frame.prototype, wp.media.controller.StateMachine.prototype );
2747
2748
module.exports = Frame;
2749
2750
2751
/***/ }),
2752
/* 47 */
2753
/***/ (function(module, exports) {
2754
2755
var Frame = wp.media.view.Frame,
2756
	$ = jQuery,
2757
	MediaFrame;
2758
2759
/**
2760
 * wp.media.view.MediaFrame
2761
 *
2762
 * The frame used to create the media modal.
2763
 *
2764
 * @memberOf wp.media.view
2765
 *
2766
 * @class
2767
 * @augments wp.media.view.Frame
2768
 * @augments wp.media.View
2769
 * @augments wp.Backbone.View
2770
 * @augments Backbone.View
2771
 * @mixes wp.media.controller.StateMachine
2772
 */
2773
MediaFrame = Frame.extend(/** @lends wp.media.view.MediaFrame.prototype */{
2774
	className: 'media-frame',
2775
	template:  wp.template('media-frame'),
2776
	regions:   ['menu','title','content','toolbar','router'],
2777
2778
	events: {
2779
		'click div.media-frame-title h1': 'toggleMenu'
2780
	},
2781
2782
	/**
2783
	 * @constructs
2784
	 */
2785
	initialize: function() {
2786
		Frame.prototype.initialize.apply( this, arguments );
2787
2788
		_.defaults( this.options, {
2789
			title:    '',
2790
			modal:    true,
2791
			uploader: true
2792
		});
2793
2794
		// Ensure core UI is enabled.
2795
		this.$el.addClass('wp-core-ui');
2796
2797
		// Initialize modal container view.
2798
		if ( this.options.modal ) {
2799
			this.modal = new wp.media.view.Modal({
2800
				controller: this,
2801
				title:      this.options.title
2802
			});
2803
2804
			this.modal.content( this );
2805
		}
2806
2807
		// Force the uploader off if the upload limit has been exceeded or
2808
		// if the browser isn't supported.
2809
		if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) {
2810
			this.options.uploader = false;
2811
		}
2812
2813
		// Initialize window-wide uploader.
2814
		if ( this.options.uploader ) {
2815
			this.uploader = new wp.media.view.UploaderWindow({
2816
				controller: this,
2817
				uploader: {
2818
					dropzone:  this.modal ? this.modal.$el : this.$el,
2819
					container: this.$el
2820
				}
2821
			});
2822
			this.views.set( '.media-frame-uploader', this.uploader );
2823
		}
2824
2825
		this.on( 'attach', _.bind( this.views.ready, this.views ), this );
2826
2827
		// Bind default title creation.
2828
		this.on( 'title:create:default', this.createTitle, this );
2829
		this.title.mode('default');
2830
2831
		this.on( 'title:render', function( view ) {
2832
			view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' );
2833
		});
2834
2835
		// Bind default menu.
2836
		this.on( 'menu:create:default', this.createMenu, this );
2837
	},
2838
	/**
2839
	 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
2840
	 */
2841
	render: function() {
2842
		// Activate the default state if no active state exists.
2843
		if ( ! this.state() && this.options.state ) {
2844
			this.setState( this.options.state );
2845
		}
2846
		/**
2847
		 * call 'render' directly on the parent class
2848
		 */
2849
		return Frame.prototype.render.apply( this, arguments );
2850
	},
2851
	/**
2852
	 * @param {Object} title
2853
	 * @this wp.media.controller.Region
2854
	 */
2855
	createTitle: function( title ) {
2856
		title.view = new wp.media.View({
2857
			controller: this,
2858
			tagName: 'h1'
2859
		});
2860
	},
2861
	/**
2862
	 * @param {Object} menu
2863
	 * @this wp.media.controller.Region
2864
	 */
2865
	createMenu: function( menu ) {
2866
		menu.view = new wp.media.view.Menu({
2867
			controller: this
2868
		});
2869
	},
2870
2871
	toggleMenu: function() {
2872
		this.$el.find( '.media-menu' ).toggleClass( 'visible' );
2873
	},
2874
2875
	/**
2876
	 * @param {Object} toolbar
2877
	 * @this wp.media.controller.Region
2878
	 */
2879
	createToolbar: function( toolbar ) {
2880
		toolbar.view = new wp.media.view.Toolbar({
2881
			controller: this
2882
		});
2883
	},
2884
	/**
2885
	 * @param {Object} router
2886
	 * @this wp.media.controller.Region
2887
	 */
2888
	createRouter: function( router ) {
2889
		router.view = new wp.media.view.Router({
2890
			controller: this
2891
		});
2892
	},
2893
	/**
2894
	 * @param {Object} options
2895
	 */
2896
	createIframeStates: function( options ) {
2897
		var settings = wp.media.view.settings,
2898
			tabs = settings.tabs,
2899
			tabUrl = settings.tabUrl,
2900
			$postId;
2901
2902
		if ( ! tabs || ! tabUrl ) {
2903
			return;
2904
		}
2905
2906
		// Add the post ID to the tab URL if it exists.
2907
		$postId = $('#post_ID');
2908
		if ( $postId.length ) {
2909
			tabUrl += '&post_id=' + $postId.val();
2910
		}
2911
2912
		// Generate the tab states.
2913
		_.each( tabs, function( title, id ) {
2914
			this.state( 'iframe:' + id ).set( _.defaults({
2915
				tab:     id,
2916
				src:     tabUrl + '&tab=' + id,
2917
				title:   title,
2918
				content: 'iframe',
2919
				menu:    'default'
2920
			}, options ) );
2921
		}, this );
2922
2923
		this.on( 'content:create:iframe', this.iframeContent, this );
2924
		this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this );
2925
		this.on( 'menu:render:default', this.iframeMenu, this );
2926
		this.on( 'open', this.hijackThickbox, this );
2927
		this.on( 'close', this.restoreThickbox, this );
2928
	},
2929
2930
	/**
2931
	 * @param {Object} content
2932
	 * @this wp.media.controller.Region
2933
	 */
2934
	iframeContent: function( content ) {
2935
		this.$el.addClass('hide-toolbar');
2936
		content.view = new wp.media.view.Iframe({
2937
			controller: this
2938
		});
2939
	},
2940
2941
	iframeContentCleanup: function() {
2942
		this.$el.removeClass('hide-toolbar');
2943
	},
2944
2945
	iframeMenu: function( view ) {
2946
		var views = {};
2947
2948
		if ( ! view ) {
2949
			return;
2950
		}
2951
2952
		_.each( wp.media.view.settings.tabs, function( title, id ) {
2953
			views[ 'iframe:' + id ] = {
2954
				text: this.state( 'iframe:' + id ).get('title'),
2955
				priority: 200
2956
			};
2957
		}, this );
2958
2959
		view.set( views );
2960
	},
2961
2962
	hijackThickbox: function() {
2963
		var frame = this;
2964
2965
		if ( ! window.tb_remove || this._tb_remove ) {
2966
			return;
2967
		}
2968
2969
		this._tb_remove = window.tb_remove;
2970
		window.tb_remove = function() {
2971
			frame.close();
2972
			frame.reset();
2973
			frame.setState( frame.options.state );
2974
			frame._tb_remove.call( window );
2975
		};
2976
	},
2977
2978
	restoreThickbox: function() {
2979
		if ( ! this._tb_remove ) {
2980
			return;
2981
		}
2982
2983
		window.tb_remove = this._tb_remove;
2984
		delete this._tb_remove;
2985
	}
2986
});
2987
2988
// Map some of the modal's methods to the frame.
2989
_.each(['open','close','attach','detach','escape'], function( method ) {
2990
	/**
2991
	 * @function open
2992
	 * @memberOf wp.media.view.MediaFrame
2993
	 * @instance
2994
	 *
2995
	 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
2996
	 */
2997
	/**
2998
	 * @function close
2999
	 * @memberOf wp.media.view.MediaFrame
3000
	 * @instance
3001
	 *
3002
	 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
3003
	 */
3004
	/**
3005
	 * @function attach
3006
	 * @memberOf wp.media.view.MediaFrame
3007
	 * @instance
3008
	 *
3009
	 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
3010
	 */
3011
	/**
3012
	 * @function detach
3013
	 * @memberOf wp.media.view.MediaFrame
3014
	 * @instance
3015
	 *
3016
	 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
3017
	 */
3018
	/**
3019
	 * @function escape
3020
	 * @memberOf wp.media.view.MediaFrame
3021
	 * @instance
3022
	 *
3023
	 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining
3024
	 */
3025
	MediaFrame.prototype[ method ] = function() {
3026
		if ( this.modal ) {
3027
			this.modal[ method ].apply( this.modal, arguments );
3028
		}
3029
		return this;
1.1.4 by Paul Gear
new upstream release 4.2
3030
	};
1.1.30 by Barry Price
new upstream release 4.4
3031
});
3032
3033
module.exports = MediaFrame;
3034
3035
3036
/***/ }),
3037
/* 48 */
3038
/***/ (function(module, exports) {
3039
3040
var MediaFrame = wp.media.view.MediaFrame,
3041
	l10n = wp.media.view.l10n,
3042
	Select;
3043
3044
/**
3045
 * wp.media.view.MediaFrame.Select
3046
 *
3047
 * A frame for selecting an item or items from the media library.
3048
 *
3049
 * @memberOf wp.media.view.MediaFrame
3050
 *
3051
 * @class
3052
 * @augments wp.media.view.MediaFrame
3053
 * @augments wp.media.view.Frame
3054
 * @augments wp.media.View
3055
 * @augments wp.Backbone.View
3056
 * @augments Backbone.View
3057
 * @mixes wp.media.controller.StateMachine
3058
 */
3059
Select = MediaFrame.extend(/** @lends wp.media.view.MediaFrame.Select.prototype */{
3060
	initialize: function() {
3061
		// Call 'initialize' directly on the parent class.
3062
		MediaFrame.prototype.initialize.apply( this, arguments );
3063
3064
		_.defaults( this.options, {
3065
			selection: [],
3066
			library:   {},
3067
			multiple:  false,
3068
			state:    'library'
3069
		});
3070
3071
		this.createSelection();
3072
		this.createStates();
3073
		this.bindHandlers();
3074
	},
3075
3076
	/**
3077
	 * Attach a selection collection to the frame.
3078
	 *
3079
	 * A selection is a collection of attachments used for a specific purpose
3080
	 * by a media frame. e.g. Selecting an attachment (or many) to insert into
3081
	 * post content.
3082
	 *
3083
	 * @see media.model.Selection
3084
	 */
3085
	createSelection: function() {
3086
		var selection = this.options.selection;
3087
3088
		if ( ! (selection instanceof wp.media.model.Selection) ) {
3089
			this.options.selection = new wp.media.model.Selection( selection, {
3090
				multiple: this.options.multiple
3091
			});
3092
		}
3093
3094
		this._selection = {
3095
			attachments: new wp.media.model.Attachments(),
3096
			difference: []
3097
		};
3098
	},
3099
3100
	/**
3101
	 * Create the default states on the frame.
3102
	 */
3103
	createStates: function() {
3104
		var options = this.options;
3105
3106
		if ( this.options.states ) {
3107
			return;
3108
		}
3109
3110
		// Add the default states.
3111
		this.states.add([
3112
			// Main states.
3113
			new wp.media.controller.Library({
3114
				library:   wp.media.query( options.library ),
3115
				multiple:  options.multiple,
3116
				title:     options.title,
3117
				priority:  20
3118
			})
3119
		]);
3120
	},
3121
3122
	/**
3123
	 * Bind region mode event callbacks.
3124
	 *
3125
	 * @see media.controller.Region.render
3126
	 */
3127
	bindHandlers: function() {
3128
		this.on( 'router:create:browse', this.createRouter, this );
3129
		this.on( 'router:render:browse', this.browseRouter, this );
3130
		this.on( 'content:create:browse', this.browseContent, this );
3131
		this.on( 'content:render:upload', this.uploadContent, this );
3132
		this.on( 'toolbar:create:select', this.createSelectToolbar, this );
3133
	},
3134
3135
	/**
3136
	 * Render callback for the router region in the `browse` mode.
3137
	 *
3138
	 * @param {wp.media.view.Router} routerView
3139
	 */
3140
	browseRouter: function( routerView ) {
3141
		routerView.set({
3142
			upload: {
3143
				text:     l10n.uploadFilesTitle,
3144
				priority: 20
3145
			},
3146
			browse: {
3147
				text:     l10n.mediaLibraryTitle,
3148
				priority: 40
3149
			}
3150
		});
3151
	},
3152
3153
	/**
3154
	 * Render callback for the content region in the `browse` mode.
3155
	 *
3156
	 * @param {wp.media.controller.Region} contentRegion
3157
	 */
3158
	browseContent: function( contentRegion ) {
3159
		var state = this.state();
3160
3161
		this.$el.removeClass('hide-toolbar');
3162
3163
		// Browse our library of attachments.
3164
		contentRegion.view = new wp.media.view.AttachmentsBrowser({
3165
			controller: this,
3166
			collection: state.get('library'),
3167
			selection:  state.get('selection'),
3168
			model:      state,
3169
			sortable:   state.get('sortable'),
3170
			search:     state.get('searchable'),
3171
			filters:    state.get('filterable'),
3172
			date:       state.get('date'),
3173
			display:    state.has('display') ? state.get('display') : state.get('displaySettings'),
3174
			dragInfo:   state.get('dragInfo'),
3175
3176
			idealColumnWidth: state.get('idealColumnWidth'),
3177
			suggestedWidth:   state.get('suggestedWidth'),
3178
			suggestedHeight:  state.get('suggestedHeight'),
3179
3180
			AttachmentView: state.get('AttachmentView')
3181
		});
3182
	},
3183
3184
	/**
3185
	 * Render callback for the content region in the `upload` mode.
3186
	 */
3187
	uploadContent: function() {
3188
		this.$el.removeClass( 'hide-toolbar' );
3189
		this.content.set( new wp.media.view.UploaderInline({
3190
			controller: this
3191
		}) );
3192
	},
3193
3194
	/**
3195
	 * Toolbars
3196
	 *
3197
	 * @param {Object} toolbar
3198
	 * @param {Object} [options={}]
3199
	 * @this wp.media.controller.Region
3200
	 */
3201
	createSelectToolbar: function( toolbar, options ) {
3202
		options = options || this.options.button || {};
3203
		options.controller = this;
3204
3205
		toolbar.view = new wp.media.view.Toolbar.Select( options );
3206
	}
3207
});
3208
3209
module.exports = Select;
3210
3211
3212
/***/ }),
3213
/* 49 */
3214
/***/ (function(module, exports) {
3215
3216
var Select = wp.media.view.MediaFrame.Select,
3217
	Library = wp.media.controller.Library,
3218
	l10n = wp.media.view.l10n,
3219
	Post;
3220
3221
/**
3222
 * wp.media.view.MediaFrame.Post
3223
 *
3224
 * The frame for manipulating media on the Edit Post page.
3225
 *
3226
 * @memberOf wp.media.view.MediaFrame
3227
 *
3228
 * @class
3229
 * @augments wp.media.view.MediaFrame.Select
3230
 * @augments wp.media.view.MediaFrame
3231
 * @augments wp.media.view.Frame
3232
 * @augments wp.media.View
3233
 * @augments wp.Backbone.View
3234
 * @augments Backbone.View
3235
 * @mixes wp.media.controller.StateMachine
3236
 */
3237
Post = Select.extend(/** @lends wp.media.view.MediaFrame.Post.prototype */{
3238
	initialize: function() {
3239
		this.counts = {
3240
			audio: {
3241
				count: wp.media.view.settings.attachmentCounts.audio,
3242
				state: 'playlist'
3243
			},
3244
			video: {
3245
				count: wp.media.view.settings.attachmentCounts.video,
3246
				state: 'video-playlist'
3247
			}
3248
		};
3249
3250
		_.defaults( this.options, {
3251
			multiple:  true,
3252
			editing:   false,
3253
			state:    'insert',
3254
			metadata:  {}
3255
		});
3256
3257
		// Call 'initialize' directly on the parent class.
3258
		Select.prototype.initialize.apply( this, arguments );
3259
		this.createIframeStates();
3260
3261
	},
3262
3263
	/**
3264
	 * Create the default states.
3265
	 */
3266
	createStates: function() {
3267
		var options = this.options;
3268
3269
		this.states.add([
3270
			// Main states.
3271
			new Library({
3272
				id:         'insert',
3273
				title:      l10n.insertMediaTitle,
3274
				priority:   20,
3275
				toolbar:    'main-insert',
3276
				filterable: 'all',
3277
				library:    wp.media.query( options.library ),
3278
				multiple:   options.multiple ? 'reset' : false,
3279
				editable:   true,
3280
3281
				// If the user isn't allowed to edit fields,
3282
				// can they still edit it locally?
3283
				allowLocalEdits: true,
3284
3285
				// Show the attachment display settings.
3286
				displaySettings: true,
3287
				// Update user settings when users adjust the
3288
				// attachment display settings.
3289
				displayUserSettings: true
3290
			}),
3291
3292
			new Library({
3293
				id:         'gallery',
3294
				title:      l10n.createGalleryTitle,
3295
				priority:   40,
3296
				toolbar:    'main-gallery',
3297
				filterable: 'uploaded',
3298
				multiple:   'add',
3299
				editable:   false,
3300
3301
				library:  wp.media.query( _.defaults({
3302
					type: 'image'
3303
				}, options.library ) )
3304
			}),
3305
3306
			// Embed states.
3307
			new wp.media.controller.Embed( { metadata: options.metadata } ),
3308
3309
			new wp.media.controller.EditImage( { model: options.editImage } ),
3310
3311
			// Gallery states.
3312
			new wp.media.controller.GalleryEdit({
3313
				library: options.selection,
3314
				editing: options.editing,
3315
				menu:    'gallery'
3316
			}),
3317
3318
			new wp.media.controller.GalleryAdd(),
3319
3320
			new Library({
3321
				id:         'playlist',
3322
				title:      l10n.createPlaylistTitle,
3323
				priority:   60,
3324
				toolbar:    'main-playlist',
3325
				filterable: 'uploaded',
3326
				multiple:   'add',
3327
				editable:   false,
3328
3329
				library:  wp.media.query( _.defaults({
3330
					type: 'audio'
3331
				}, options.library ) )
3332
			}),
3333
3334
			// Playlist states.
3335
			new wp.media.controller.CollectionEdit({
3336
				type: 'audio',
3337
				collectionType: 'playlist',
3338
				title:          l10n.editPlaylistTitle,
3339
				SettingsView:   wp.media.view.Settings.Playlist,
3340
				library:        options.selection,
3341
				editing:        options.editing,
3342
				menu:           'playlist',
3343
				dragInfoText:   l10n.playlistDragInfo,
3344
				dragInfo:       false
3345
			}),
3346
3347
			new wp.media.controller.CollectionAdd({
3348
				type: 'audio',
3349
				collectionType: 'playlist',
3350
				title: l10n.addToPlaylistTitle
3351
			}),
3352
3353
			new Library({
3354
				id:         'video-playlist',
3355
				title:      l10n.createVideoPlaylistTitle,
3356
				priority:   60,
3357
				toolbar:    'main-video-playlist',
3358
				filterable: 'uploaded',
3359
				multiple:   'add',
3360
				editable:   false,
3361
3362
				library:  wp.media.query( _.defaults({
3363
					type: 'video'
3364
				}, options.library ) )
3365
			}),
3366
3367
			new wp.media.controller.CollectionEdit({
3368
				type: 'video',
3369
				collectionType: 'playlist',
3370
				title:          l10n.editVideoPlaylistTitle,
3371
				SettingsView:   wp.media.view.Settings.Playlist,
3372
				library:        options.selection,
3373
				editing:        options.editing,
3374
				menu:           'video-playlist',
3375
				dragInfoText:   l10n.videoPlaylistDragInfo,
3376
				dragInfo:       false
3377
			}),
3378
3379
			new wp.media.controller.CollectionAdd({
3380
				type: 'video',
3381
				collectionType: 'playlist',
3382
				title: l10n.addToVideoPlaylistTitle
3383
			})
3384
		]);
3385
3386
		if ( wp.media.view.settings.post.featuredImageId ) {
3387
			this.states.add( new wp.media.controller.FeaturedImage() );
3388
		}
3389
	},
3390
3391
	bindHandlers: function() {
3392
		var handlers, checkCounts;
3393
3394
		Select.prototype.bindHandlers.apply( this, arguments );
3395
3396
		this.on( 'activate', this.activate, this );
3397
3398
		// Only bother checking media type counts if one of the counts is zero
3399
		checkCounts = _.find( this.counts, function( type ) {
3400
			return type.count === 0;
3401
		} );
3402
3403
		if ( typeof checkCounts !== 'undefined' ) {
3404
			this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts );
3405
		}
3406
3407
		this.on( 'menu:create:gallery', this.createMenu, this );
3408
		this.on( 'menu:create:playlist', this.createMenu, this );
3409
		this.on( 'menu:create:video-playlist', this.createMenu, this );
3410
		this.on( 'toolbar:create:main-insert', this.createToolbar, this );
3411
		this.on( 'toolbar:create:main-gallery', this.createToolbar, this );
3412
		this.on( 'toolbar:create:main-playlist', this.createToolbar, this );
3413
		this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this );
3414
		this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this );
3415
		this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );
3416
3417
		handlers = {
3418
			menu: {
3419
				'default': 'mainMenu',
3420
				'gallery': 'galleryMenu',
3421
				'playlist': 'playlistMenu',
3422
				'video-playlist': 'videoPlaylistMenu'
3423
			},
3424
3425
			content: {
3426
				'embed':          'embedContent',
3427
				'edit-image':     'editImageContent',
3428
				'edit-selection': 'editSelectionContent'
3429
			},
3430
3431
			toolbar: {
3432
				'main-insert':      'mainInsertToolbar',
3433
				'main-gallery':     'mainGalleryToolbar',
3434
				'gallery-edit':     'galleryEditToolbar',
3435
				'gallery-add':      'galleryAddToolbar',
3436
				'main-playlist':	'mainPlaylistToolbar',
3437
				'playlist-edit':	'playlistEditToolbar',
3438
				'playlist-add':		'playlistAddToolbar',
3439
				'main-video-playlist': 'mainVideoPlaylistToolbar',
3440
				'video-playlist-edit': 'videoPlaylistEditToolbar',
3441
				'video-playlist-add': 'videoPlaylistAddToolbar'
3442
			}
3443
		};
3444
3445
		_.each( handlers, function( regionHandlers, region ) {
3446
			_.each( regionHandlers, function( callback, handler ) {
3447
				this.on( region + ':render:' + handler, this[ callback ], this );
3448
			}, this );
3449
		}, this );
3450
	},
3451
3452
	activate: function() {
3453
		// Hide menu items for states tied to particular media types if there are no items
3454
		_.each( this.counts, function( type ) {
3455
			if ( type.count < 1 ) {
3456
				this.menuItemVisibility( type.state, 'hide' );
3457
			}
3458
		}, this );
3459
	},
3460
3461
	mediaTypeCounts: function( model, attr ) {
3462
		if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) {
3463
			this.counts[ attr ].count++;
3464
			this.menuItemVisibility( this.counts[ attr ].state, 'show' );
3465
		}
3466
	},
3467
3468
	// Menus
3469
	/**
3470
	 * @param {wp.Backbone.View} view
3471
	 */
3472
	mainMenu: function( view ) {
3473
		view.set({
3474
			'library-separator': new wp.media.View({
3475
				className: 'separator',
3476
				priority: 100
3477
			})
3478
		});
3479
	},
3480
3481
	menuItemVisibility: function( state, visibility ) {
3482
		var menu = this.menu.get();
3483
		if ( visibility === 'hide' ) {
3484
			menu.hide( state );
3485
		} else if ( visibility === 'show' ) {
3486
			menu.show( state );
3487
		}
3488
	},
3489
	/**
3490
	 * @param {wp.Backbone.View} view
3491
	 */
3492
	galleryMenu: function( view ) {
3493
		var lastState = this.lastState(),
3494
			previous = lastState && lastState.id,
3495
			frame = this;
3496
3497
		view.set({
3498
			cancel: {
3499
				text:     l10n.cancelGalleryTitle,
3500
				priority: 20,
3501
				click:    function() {
3502
					if ( previous ) {
3503
						frame.setState( previous );
3504
					} else {
3505
						frame.close();
3506
					}
3507
3508
					// Keep focus inside media modal
3509
					// after canceling a gallery
3510
					this.controller.modal.focusManager.focus();
3511
				}
3512
			},
3513
			separateCancel: new wp.media.View({
3514
				className: 'separator',
3515
				priority: 40
3516
			})
3517
		});
3518
	},
3519
3520
	playlistMenu: function( view ) {
3521
		var lastState = this.lastState(),
3522
			previous = lastState && lastState.id,
3523
			frame = this;
3524
3525
		view.set({
3526
			cancel: {
3527
				text:     l10n.cancelPlaylistTitle,
3528
				priority: 20,
3529
				click:    function() {
3530
					if ( previous ) {
3531
						frame.setState( previous );
3532
					} else {
3533
						frame.close();
3534
					}
3535
				}
3536
			},
3537
			separateCancel: new wp.media.View({
3538
				className: 'separator',
3539
				priority: 40
3540
			})
3541
		});
3542
	},
3543
3544
	videoPlaylistMenu: function( view ) {
3545
		var lastState = this.lastState(),
3546
			previous = lastState && lastState.id,
3547
			frame = this;
3548
3549
		view.set({
3550
			cancel: {
3551
				text:     l10n.cancelVideoPlaylistTitle,
3552
				priority: 20,
3553
				click:    function() {
3554
					if ( previous ) {
3555
						frame.setState( previous );
3556
					} else {
3557
						frame.close();
3558
					}
3559
				}
3560
			},
3561
			separateCancel: new wp.media.View({
3562
				className: 'separator',
3563
				priority: 40
3564
			})
3565
		});
3566
	},
3567
3568
	// Content
3569
	embedContent: function() {
3570
		var view = new wp.media.view.Embed({
3571
			controller: this,
3572
			model:      this.state()
3573
		}).render();
3574
3575
		this.content.set( view );
3576
3577
		if ( ! wp.media.isTouchDevice ) {
3578
			view.url.focus();
3579
		}
3580
	},
3581
3582
	editSelectionContent: function() {
3583
		var state = this.state(),
3584
			selection = state.get('selection'),
3585
			view;
3586
3587
		view = new wp.media.view.AttachmentsBrowser({
3588
			controller: this,
3589
			collection: selection,
3590
			selection:  selection,
3591
			model:      state,
3592
			sortable:   true,
3593
			search:     false,
3594
			date:       false,
3595
			dragInfo:   true,
3596
3597
			AttachmentView: wp.media.view.Attachments.EditSelection
3598
		}).render();
3599
3600
		view.toolbar.set( 'backToLibrary', {
3601
			text:     l10n.returnToLibrary,
3602
			priority: -100,
3603
3604
			click: function() {
3605
				this.controller.content.mode('browse');
3606
			}
3607
		});
3608
3609
		// Browse our library of attachments.
3610
		this.content.set( view );
3611
3612
		// Trigger the controller to set focus
3613
		this.trigger( 'edit:selection', this );
3614
	},
3615
3616
	editImageContent: function() {
3617
		var image = this.state().get('image'),
3618
			view = new wp.media.view.EditImage( { model: image, controller: this } ).render();
3619
3620
		this.content.set( view );
3621
3622
		// after creating the wrapper view, load the actual editor via an ajax call
3623
		view.loadEditor();
3624
3625
	},
3626
3627
	// Toolbars
3628
3629
	/**
3630
	 * @param {wp.Backbone.View} view
3631
	 */
3632
	selectionStatusToolbar: function( view ) {
3633
		var editable = this.state().get('editable');
3634
3635
		view.set( 'selection', new wp.media.view.Selection({
3636
			controller: this,
3637
			collection: this.state().get('selection'),
3638
			priority:   -40,
3639
3640
			// If the selection is editable, pass the callback to
3641
			// switch the content mode.
3642
			editable: editable && function() {
3643
				this.controller.content.mode('edit-selection');
3644
			}
3645
		}).render() );
3646
	},
3647
3648
	/**
3649
	 * @param {wp.Backbone.View} view
3650
	 */
3651
	mainInsertToolbar: function( view ) {
3652
		var controller = this;
3653
3654
		this.selectionStatusToolbar( view );
3655
3656
		view.set( 'insert', {
3657
			style:    'primary',
3658
			priority: 80,
3659
			text:     l10n.insertIntoPost,
3660
			requires: { selection: true },
3661
3662
			/**
3663
			 * @callback
3664
			 * @fires wp.media.controller.State#insert
3665
			 */
3666
			click: function() {
3667
				var state = controller.state(),
3668
					selection = state.get('selection');
3669
3670
				controller.close();
3671
				state.trigger( 'insert', selection ).reset();
3672
			}
3673
		});
3674
	},
3675
3676
	/**
3677
	 * @param {wp.Backbone.View} view
3678
	 */
3679
	mainGalleryToolbar: function( view ) {
3680
		var controller = this;
3681
3682
		this.selectionStatusToolbar( view );
3683
3684
		view.set( 'gallery', {
3685
			style:    'primary',
3686
			text:     l10n.createNewGallery,
3687
			priority: 60,
3688
			requires: { selection: true },
3689
3690
			click: function() {
3691
				var selection = controller.state().get('selection'),
3692
					edit = controller.state('gallery-edit'),
3693
					models = selection.where({ type: 'image' });
3694
3695
				edit.set( 'library', new wp.media.model.Selection( models, {
3696
					props:    selection.props.toJSON(),
3697
					multiple: true
3698
				}) );
3699
3700
				this.controller.setState('gallery-edit');
3701
3702
				// Keep focus inside media modal
3703
				// after jumping to gallery view
3704
				this.controller.modal.focusManager.focus();
3705
			}
3706
		});
3707
	},
3708
3709
	mainPlaylistToolbar: function( view ) {
3710
		var controller = this;
3711
3712
		this.selectionStatusToolbar( view );
3713
3714
		view.set( 'playlist', {
3715
			style:    'primary',
3716
			text:     l10n.createNewPlaylist,
3717
			priority: 100,
3718
			requires: { selection: true },
3719
3720
			click: function() {
3721
				var selection = controller.state().get('selection'),
3722
					edit = controller.state('playlist-edit'),
3723
					models = selection.where({ type: 'audio' });
3724
3725
				edit.set( 'library', new wp.media.model.Selection( models, {
3726
					props:    selection.props.toJSON(),
3727
					multiple: true
3728
				}) );
3729
3730
				this.controller.setState('playlist-edit');
3731
3732
				// Keep focus inside media modal
3733
				// after jumping to playlist view
3734
				this.controller.modal.focusManager.focus();
3735
			}
3736
		});
3737
	},
3738
3739
	mainVideoPlaylistToolbar: function( view ) {
3740
		var controller = this;
3741
3742
		this.selectionStatusToolbar( view );
3743
3744
		view.set( 'video-playlist', {
3745
			style:    'primary',
3746
			text:     l10n.createNewVideoPlaylist,
3747
			priority: 100,
3748
			requires: { selection: true },
3749
3750
			click: function() {
3751
				var selection = controller.state().get('selection'),
3752
					edit = controller.state('video-playlist-edit'),
3753
					models = selection.where({ type: 'video' });
3754
3755
				edit.set( 'library', new wp.media.model.Selection( models, {
3756
					props:    selection.props.toJSON(),
3757
					multiple: true
3758
				}) );
3759
3760
				this.controller.setState('video-playlist-edit');
3761
3762
				// Keep focus inside media modal
3763
				// after jumping to video playlist view
3764
				this.controller.modal.focusManager.focus();
3765
			}
3766
		});
3767
	},
3768
3769
	featuredImageToolbar: function( toolbar ) {
3770
		this.createSelectToolbar( toolbar, {
3771
			text:  l10n.setFeaturedImage,
3772
			state: this.options.state
3773
		});
3774
	},
3775
3776
	mainEmbedToolbar: function( toolbar ) {
3777
		toolbar.view = new wp.media.view.Toolbar.Embed({
3778
			controller: this
3779
		});
3780
	},
3781
3782
	galleryEditToolbar: function() {
3783
		var editing = this.state().get('editing');
3784
		this.toolbar.set( new wp.media.view.Toolbar({
3785
			controller: this,
3786
			items: {
3787
				insert: {
3788
					style:    'primary',
3789
					text:     editing ? l10n.updateGallery : l10n.insertGallery,
3790
					priority: 80,
3791
					requires: { library: true },
3792
3793
					/**
3794
					 * @fires wp.media.controller.State#update
3795
					 */
3796
					click: function() {
3797
						var controller = this.controller,
3798
							state = controller.state();
3799
3800
						controller.close();
3801
						state.trigger( 'update', state.get('library') );
3802
3803
						// Restore and reset the default state.
3804
						controller.setState( controller.options.state );
3805
						controller.reset();
3806
					}
3807
				}
3808
			}
3809
		}) );
3810
	},
3811
3812
	galleryAddToolbar: function() {
3813
		this.toolbar.set( new wp.media.view.Toolbar({
3814
			controller: this,
3815
			items: {
3816
				insert: {
3817
					style:    'primary',
3818
					text:     l10n.addToGallery,
3819
					priority: 80,
3820
					requires: { selection: true },
3821
3822
					/**
3823
					 * @fires wp.media.controller.State#reset
3824
					 */
3825
					click: function() {
3826
						var controller = this.controller,
3827
							state = controller.state(),
3828
							edit = controller.state('gallery-edit');
3829
3830
						edit.get('library').add( state.get('selection').models );
3831
						state.trigger('reset');
3832
						controller.setState('gallery-edit');
3833
					}
3834
				}
3835
			}
3836
		}) );
3837
	},
3838
3839
	playlistEditToolbar: function() {
3840
		var editing = this.state().get('editing');
3841
		this.toolbar.set( new wp.media.view.Toolbar({
3842
			controller: this,
3843
			items: {
3844
				insert: {
3845
					style:    'primary',
3846
					text:     editing ? l10n.updatePlaylist : l10n.insertPlaylist,
3847
					priority: 80,
3848
					requires: { library: true },
3849
3850
					/**
3851
					 * @fires wp.media.controller.State#update
3852
					 */
3853
					click: function() {
3854
						var controller = this.controller,
3855
							state = controller.state();
3856
3857
						controller.close();
3858
						state.trigger( 'update', state.get('library') );
3859
3860
						// Restore and reset the default state.
3861
						controller.setState( controller.options.state );
3862
						controller.reset();
3863
					}
3864
				}
3865
			}
3866
		}) );
3867
	},
3868
3869
	playlistAddToolbar: function() {
3870
		this.toolbar.set( new wp.media.view.Toolbar({
3871
			controller: this,
3872
			items: {
3873
				insert: {
3874
					style:    'primary',
3875
					text:     l10n.addToPlaylist,
3876
					priority: 80,
3877
					requires: { selection: true },
3878
3879
					/**
3880
					 * @fires wp.media.controller.State#reset
3881
					 */
3882
					click: function() {
3883
						var controller = this.controller,
3884
							state = controller.state(),
3885
							edit = controller.state('playlist-edit');
3886
3887
						edit.get('library').add( state.get('selection').models );
3888
						state.trigger('reset');
3889
						controller.setState('playlist-edit');
3890
					}
3891
				}
3892
			}
3893
		}) );
3894
	},
3895
3896
	videoPlaylistEditToolbar: function() {
3897
		var editing = this.state().get('editing');
3898
		this.toolbar.set( new wp.media.view.Toolbar({
3899
			controller: this,
3900
			items: {
3901
				insert: {
3902
					style:    'primary',
3903
					text:     editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist,
3904
					priority: 140,
3905
					requires: { library: true },
3906
3907
					click: function() {
3908
						var controller = this.controller,
3909
							state = controller.state(),
3910
							library = state.get('library');
3911
3912
						library.type = 'video';
3913
3914
						controller.close();
3915
						state.trigger( 'update', library );
3916
3917
						// Restore and reset the default state.
3918
						controller.setState( controller.options.state );
3919
						controller.reset();
3920
					}
3921
				}
3922
			}
3923
		}) );
3924
	},
3925
3926
	videoPlaylistAddToolbar: function() {
3927
		this.toolbar.set( new wp.media.view.Toolbar({
3928
			controller: this,
3929
			items: {
3930
				insert: {
3931
					style:    'primary',
3932
					text:     l10n.addToVideoPlaylist,
3933
					priority: 140,
3934
					requires: { selection: true },
3935
3936
					click: function() {
3937
						var controller = this.controller,
3938
							state = controller.state(),
3939
							edit = controller.state('video-playlist-edit');
3940
3941
						edit.get('library').add( state.get('selection').models );
3942
						state.trigger('reset');
3943
						controller.setState('video-playlist-edit');
3944
					}
3945
				}
3946
			}
3947
		}) );
3948
	}
3949
});
3950
3951
module.exports = Post;
3952
3953
3954
/***/ }),
3955
/* 50 */
3956
/***/ (function(module, exports) {
3957
3958
var Select = wp.media.view.MediaFrame.Select,
3959
	l10n = wp.media.view.l10n,
3960
	ImageDetails;
3961
3962
/**
3963
 * wp.media.view.MediaFrame.ImageDetails
3964
 *
3965
 * A media frame for manipulating an image that's already been inserted
3966
 * into a post.
3967
 *
3968
 * @memberOf wp.media.view.MediaFrame
3969
 *
3970
 * @class
3971
 * @augments wp.media.view.MediaFrame.Select
3972
 * @augments wp.media.view.MediaFrame
3973
 * @augments wp.media.view.Frame
3974
 * @augments wp.media.View
3975
 * @augments wp.Backbone.View
3976
 * @augments Backbone.View
3977
 * @mixes wp.media.controller.StateMachine
3978
 */
3979
ImageDetails = Select.extend(/** @lends wp.media.view.MediaFrame.ImageDetails.prototype */{
3980
	defaults: {
3981
		id:      'image',
3982
		url:     '',
3983
		menu:    'image-details',
3984
		content: 'image-details',
3985
		toolbar: 'image-details',
3986
		type:    'link',
3987
		title:    l10n.imageDetailsTitle,
3988
		priority: 120
3989
	},
3990
3991
	initialize: function( options ) {
3992
		this.image = new wp.media.model.PostImage( options.metadata );
3993
		this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } );
3994
		Select.prototype.initialize.apply( this, arguments );
3995
	},
3996
3997
	bindHandlers: function() {
3998
		Select.prototype.bindHandlers.apply( this, arguments );
3999
		this.on( 'menu:create:image-details', this.createMenu, this );
4000
		this.on( 'content:create:image-details', this.imageDetailsContent, this );
4001
		this.on( 'content:render:edit-image', this.editImageContent, this );
4002
		this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this );
4003
		// override the select toolbar
4004
		this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this );
4005
	},
4006
4007
	createStates: function() {
4008
		this.states.add([
4009
			new wp.media.controller.ImageDetails({
4010
				image: this.image,
4011
				editable: false
4012
			}),
4013
			new wp.media.controller.ReplaceImage({
4014
				id: 'replace-image',
4015
				library: wp.media.query( { type: 'image' } ),
4016
				image: this.image,
4017
				multiple:  false,
4018
				title:     l10n.imageReplaceTitle,
4019
				toolbar: 'replace',
4020
				priority:  80,
4021
				displaySettings: true
4022
			}),
4023
			new wp.media.controller.EditImage( {
4024
				image: this.image,
4025
				selection: this.options.selection
4026
			} )
4027
		]);
4028
	},
4029
4030
	imageDetailsContent: function( options ) {
4031
		options.view = new wp.media.view.ImageDetails({
4032
			controller: this,
4033
			model: this.state().image,
4034
			attachment: this.state().image.attachment
4035
		});
4036
	},
4037
4038
	editImageContent: function() {
4039
		var state = this.state(),
4040
			model = state.get('image'),
4041
			view;
4042
4043
		if ( ! model ) {
4044
			return;
4045
		}
4046
4047
		view = new wp.media.view.EditImage( { model: model, controller: this } ).render();
4048
4049
		this.content.set( view );
4050
4051
		// after bringing in the frame, load the actual editor via an ajax call
4052
		view.loadEditor();
4053
4054
	},
4055
4056
	renderImageDetailsToolbar: function() {
4057
		this.toolbar.set( new wp.media.view.Toolbar({
4058
			controller: this,
4059
			items: {
4060
				select: {
4061
					style:    'primary',
4062
					text:     l10n.update,
4063
					priority: 80,
4064
4065
					click: function() {
4066
						var controller = this.controller,
4067
							state = controller.state();
4068
4069
						controller.close();
4070
4071
						// not sure if we want to use wp.media.string.image which will create a shortcode or
4072
						// perhaps wp.html.string to at least to build the <img />
4073
						state.trigger( 'update', controller.image.toJSON() );
4074
4075
						// Restore and reset the default state.
4076
						controller.setState( controller.options.state );
4077
						controller.reset();
4078
					}
4079
				}
4080
			}
4081
		}) );
4082
	},
4083
4084
	renderReplaceImageToolbar: function() {
4085
		var frame = this,
4086
			lastState = frame.lastState(),
4087
			previous = lastState && lastState.id;
4088
4089
		this.toolbar.set( new wp.media.view.Toolbar({
4090
			controller: this,
4091
			items: {
4092
				back: {
4093
					text:     l10n.back,
4094
					priority: 20,
4095
					click:    function() {
4096
						if ( previous ) {
4097
							frame.setState( previous );
4098
						} else {
4099
							frame.close();
4100
						}
4101
					}
4102
				},
4103
4104
				replace: {
4105
					style:    'primary',
4106
					text:     l10n.replace,
4107
					priority: 80,
4108
					requires: { selection: true },
4109
4110
					click: function() {
4111
						var controller = this.controller,
4112
							state = controller.state(),
4113
							selection = state.get( 'selection' ),
4114
							attachment = selection.single();
4115
4116
						controller.close();
4117
4118
						controller.image.changeAttachment( attachment, state.display( attachment ) );
4119
4120
						// not sure if we want to use wp.media.string.image which will create a shortcode or
4121
						// perhaps wp.html.string to at least to build the <img />
4122
						state.trigger( 'replace', controller.image.toJSON() );
4123
4124
						// Restore and reset the default state.
4125
						controller.setState( controller.options.state );
4126
						controller.reset();
4127
					}
4128
				}
4129
			}
4130
		}) );
4131
	}
4132
4133
});
4134
4135
module.exports = ImageDetails;
4136
4137
4138
/***/ }),
4139
/* 51 */
4140
/***/ (function(module, exports) {
4141
4142
var $ = jQuery,
4143
	Modal;
4144
4145
/**
4146
 * wp.media.view.Modal
4147
 *
4148
 * A modal view, which the media modal uses as its default container.
4149
 *
4150
 * @memberOf wp.media.view
4151
 *
4152
 * @class
4153
 * @augments wp.media.View
4154
 * @augments wp.Backbone.View
4155
 * @augments Backbone.View
4156
 */
4157
Modal = wp.media.View.extend(/** @lends wp.media.view.Modal.prototype */{
4158
	tagName:  'div',
4159
	template: wp.template('media-modal'),
4160
4161
	events: {
4162
		'click .media-modal-backdrop, .media-modal-close': 'escapeHandler',
4163
		'keydown': 'keydown'
4164
	},
4165
4166
	clickedOpenerEl: null,
4167
4168
	initialize: function() {
4169
		_.defaults( this.options, {
4170
			container: document.body,
4171
			title:     '',
1.2.3 by Barry Price
new upstream release 4.9.4
4172
			propagate: true
1.1.30 by Barry Price
new upstream release 4.4
4173
		});
4174
4175
		this.focusManager = new wp.media.view.FocusManager({
4176
			el: this.el
4177
		});
4178
	},
4179
	/**
4180
	 * @returns {Object}
4181
	 */
4182
	prepare: function() {
4183
		return {
4184
			title: this.options.title
4185
		};
4186
	},
4187
4188
	/**
4189
	 * @returns {wp.media.view.Modal} Returns itself to allow chaining
4190
	 */
4191
	attach: function() {
4192
		if ( this.views.attached ) {
4193
			return this;
4194
		}
4195
4196
		if ( ! this.views.rendered ) {
4197
			this.render();
4198
		}
4199
4200
		this.$el.appendTo( this.options.container );
4201
4202
		// Manually mark the view as attached and trigger ready.
4203
		this.views.attached = true;
4204
		this.views.ready();
4205
4206
		return this.propagate('attach');
4207
	},
4208
4209
	/**
4210
	 * @returns {wp.media.view.Modal} Returns itself to allow chaining
4211
	 */
4212
	detach: function() {
4213
		if ( this.$el.is(':visible') ) {
4214
			this.close();
4215
		}
4216
4217
		this.$el.detach();
4218
		this.views.attached = false;
4219
		return this.propagate('detach');
4220
	},
4221
4222
	/**
4223
	 * @returns {wp.media.view.Modal} Returns itself to allow chaining
4224
	 */
4225
	open: function() {
4226
		var $el = this.$el,
4227
			mceEditor;
4228
4229
		if ( $el.is(':visible') ) {
4230
			return this;
4231
		}
4232
4233
		this.clickedOpenerEl = document.activeElement;
4234
4235
		if ( ! this.views.attached ) {
4236
			this.attach();
4237
		}
4238
4239
		// Disable page scrolling.
4240
		$( 'body' ).addClass( 'modal-open' );
4241
4242
		$el.show();
4243
4244
		// Try to close the onscreen keyboard
4245
		if ( 'ontouchend' in document ) {
1.2.3 by Barry Price
new upstream release 4.9.4
4246
			if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor ) && ! mceEditor.isHidden() && mceEditor.iframeElement ) {
1.1.30 by Barry Price
new upstream release 4.4
4247
				mceEditor.iframeElement.focus();
4248
				mceEditor.iframeElement.blur();
4249
4250
				setTimeout( function() {
4251
					mceEditor.iframeElement.blur();
4252
				}, 100 );
4253
			}
4254
		}
4255
1.2.3 by Barry Price
new upstream release 4.9.4
4256
		// Set initial focus on the content instead of this view element, to avoid page scrolling.
4257
		this.$( '.media-modal' ).focus();
1.1.30 by Barry Price
new upstream release 4.4
4258
4259
		return this.propagate('open');
4260
	},
4261
4262
	/**
4263
	 * @param {Object} options
4264
	 * @returns {wp.media.view.Modal} Returns itself to allow chaining
4265
	 */
4266
	close: function( options ) {
4267
		if ( ! this.views.attached || ! this.$el.is(':visible') ) {
4268
			return this;
4269
		}
4270
4271
		// Enable page scrolling.
4272
		$( 'body' ).removeClass( 'modal-open' );
4273
4274
		// Hide modal and remove restricted media modal tab focus once it's closed
4275
		this.$el.hide().undelegate( 'keydown' );
4276
4277
		// Put focus back in useful location once modal is closed.
4278
		if ( null !== this.clickedOpenerEl ) {
4279
			this.clickedOpenerEl.focus();
4280
		} else {
4281
			$( '#wpbody-content' ).focus();
4282
		}
4283
4284
		this.propagate('close');
4285
4286
		if ( options && options.escape ) {
4287
			this.propagate('escape');
4288
		}
4289
4290
		return this;
4291
	},
4292
	/**
4293
	 * @returns {wp.media.view.Modal} Returns itself to allow chaining
4294
	 */
4295
	escape: function() {
4296
		return this.close({ escape: true });
4297
	},
4298
	/**
4299
	 * @param {Object} event
4300
	 */
4301
	escapeHandler: function( event ) {
4302
		event.preventDefault();
4303
		this.escape();
4304
	},
4305
4306
	/**
4307
	 * @param {Array|Object} content Views to register to '.media-modal-content'
4308
	 * @returns {wp.media.view.Modal} Returns itself to allow chaining
4309
	 */
4310
	content: function( content ) {
4311
		this.views.set( '.media-modal-content', content );
4312
		return this;
4313
	},
4314
4315
	/**
4316
	 * Triggers a modal event and if the `propagate` option is set,
4317
	 * forwards events to the modal's controller.
4318
	 *
4319
	 * @param {string} id
4320
	 * @returns {wp.media.view.Modal} Returns itself to allow chaining
4321
	 */
4322
	propagate: function( id ) {
4323
		this.trigger( id );
4324
4325
		if ( this.options.propagate ) {
4326
			this.controller.trigger( id );
4327
		}
4328
4329
		return this;
4330
	},
4331
	/**
4332
	 * @param {Object} event
4333
	 */
4334
	keydown: function( event ) {
4335
		// Close the modal when escape is pressed.
4336
		if ( 27 === event.which && this.$el.is(':visible') ) {
4337
			this.escape();
4338
			event.stopImmediatePropagation();
4339
		}
4340
	}
4341
});
4342
4343
module.exports = Modal;
4344
4345
4346
/***/ }),
4347
/* 52 */
4348
/***/ (function(module, exports) {
4349
4350
/**
4351
 * wp.media.view.FocusManager
4352
 *
4353
 * @memberOf wp.media.view
4354
 *
4355
 * @class
4356
 * @augments wp.media.View
4357
 * @augments wp.Backbone.View
4358
 * @augments Backbone.View
4359
 */
4360
var FocusManager = wp.media.View.extend(/** @lends wp.media.view.FocusManager.prototype */{
4361
4362
	events: {
4363
		'keydown': 'constrainTabbing'
4364
	},
4365
4366
	focus: function() { // Reset focus on first left menu item
4367
		this.$('.media-menu-item').first().focus();
4368
	},
4369
	/**
4370
	 * @param {Object} event
4371
	 */
4372
	constrainTabbing: function( event ) {
4373
		var tabbables;
4374
4375
		// Look for the tab key.
4376
		if ( 9 !== event.keyCode ) {
4377
			return;
4378
		}
4379
4380
		// Skip the file input added by Plupload.
4381
		tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' );
4382
4383
		// Keep tab focus within media modal while it's open
4384
		if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
4385
			tabbables.first().focus();
4386
			return false;
4387
		} else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
4388
			tabbables.last().focus();
4389
			return false;
4390
		}
4391
	}
4392
4393
});
4394
4395
module.exports = FocusManager;
4396
4397
4398
/***/ }),
4399
/* 53 */
4400
/***/ (function(module, exports) {
4401
4402
var $ = jQuery,
4403
	UploaderWindow;
4404
4405
/**
4406
 * wp.media.view.UploaderWindow
4407
 *
4408
 * An uploader window that allows for dragging and dropping media.
4409
 *
4410
 * @memberOf wp.media.view
4411
 *
4412
 * @class
4413
 * @augments wp.media.View
4414
 * @augments wp.Backbone.View
4415
 * @augments Backbone.View
4416
 *
4417
 * @param {object} [options]                   Options hash passed to the view.
4418
 * @param {object} [options.uploader]          Uploader properties.
4419
 * @param {jQuery} [options.uploader.browser]
4420
 * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone.
4421
 * @param {object} [options.uploader.params]
4422
 */
4423
UploaderWindow = wp.media.View.extend(/** @lends wp.media.view.UploaderWindow.prototype */{
4424
	tagName:   'div',
4425
	className: 'uploader-window',
4426
	template:  wp.template('uploader-window'),
4427
4428
	initialize: function() {
4429
		var uploader;
4430
4431
		this.$browser = $( '<button type="button" class="browser" />' ).hide().appendTo( 'body' );
4432
4433
		uploader = this.options.uploader = _.defaults( this.options.uploader || {}, {
4434
			dropzone:  this.$el,
4435
			browser:   this.$browser,
4436
			params:    {}
4437
		});
4438
4439
		// Ensure the dropzone is a jQuery collection.
4440
		if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) {
4441
			uploader.dropzone = $( uploader.dropzone );
4442
		}
4443
4444
		this.controller.on( 'activate', this.refresh, this );
4445
4446
		this.controller.on( 'detach', function() {
4447
			this.$browser.remove();
4448
		}, this );
4449
	},
4450
4451
	refresh: function() {
4452
		if ( this.uploader ) {
4453
			this.uploader.refresh();
4454
		}
4455
	},
4456
4457
	ready: function() {
4458
		var postId = wp.media.view.settings.post.id,
4459
			dropzone;
4460
4461
		// If the uploader already exists, bail.
4462
		if ( this.uploader ) {
4463
			return;
4464
		}
4465
4466
		if ( postId ) {
4467
			this.options.uploader.params.post_id = postId;
4468
		}
4469
		this.uploader = new wp.Uploader( this.options.uploader );
4470
4471
		dropzone = this.uploader.dropzone;
4472
		dropzone.on( 'dropzone:enter', _.bind( this.show, this ) );
4473
		dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) );
4474
4475
		$( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) );
4476
	},
4477
4478
	_ready: function() {
4479
		this.controller.trigger( 'uploader:ready' );
4480
	},
4481
4482
	show: function() {
4483
		var $el = this.$el.show();
4484
4485
		// Ensure that the animation is triggered by waiting until
4486
		// the transparent element is painted into the DOM.
4487
		_.defer( function() {
4488
			$el.css({ opacity: 1 });
4489
		});
4490
	},
4491
4492
	hide: function() {
4493
		var $el = this.$el.css({ opacity: 0 });
4494
4495
		wp.media.transition( $el ).done( function() {
4496
			// Transition end events are subject to race conditions.
4497
			// Make sure that the value is set as intended.
4498
			if ( '0' === $el.css('opacity') ) {
4499
				$el.hide();
4500
			}
4501
		});
4502
4503
		// https://core.trac.wordpress.org/ticket/27341
4504
		_.delay( function() {
4505
			if ( '0' === $el.css('opacity') && $el.is(':visible') ) {
4506
				$el.hide();
4507
			}
4508
		}, 500 );
4509
	}
4510
});
4511
4512
module.exports = UploaderWindow;
4513
4514
4515
/***/ }),
4516
/* 54 */
4517
/***/ (function(module, exports) {
4518
4519
var View = wp.media.View,
4520
	l10n = wp.media.view.l10n,
4521
	$ = jQuery,
4522
	EditorUploader;
4523
4524
/**
4525
 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap)
4526
 * and relays drag'n'dropped files to a media workflow.
4527
 *
4528
 * wp.media.view.EditorUploader
4529
 *
4530
 * @memberOf wp.media.view
4531
 *
4532
 * @class
4533
 * @augments wp.media.View
4534
 * @augments wp.Backbone.View
4535
 * @augments Backbone.View
4536
 */
4537
EditorUploader = View.extend(/** @lends wp.media.view.EditorUploader.prototype */{
4538
	tagName:   'div',
4539
	className: 'uploader-editor',
4540
	template:  wp.template( 'uploader-editor' ),
4541
4542
	localDrag: false,
4543
	overContainer: false,
4544
	overDropzone: false,
4545
	draggingFile: null,
4546
4547
	/**
4548
	 * Bind drag'n'drop events to callbacks.
4549
	 */
4550
	initialize: function() {
4551
		this.initialized = false;
4552
4553
		// Bail if not enabled or UA does not support drag'n'drop or File API.
4554
		if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) {
4555
			return this;
4556
		}
4557
4558
		this.$document = $(document);
4559
		this.dropzones = [];
4560
		this.files = [];
4561
4562
		this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) );
4563
		this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) );
4564
		this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) );
4565
		this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) );
4566
4567
		this.$document.on( 'dragover', _.bind( this.containerDragover, this ) );
4568
		this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) );
4569
4570
		this.$document.on( 'dragstart dragend drop', _.bind( function( event ) {
4571
			this.localDrag = event.type === 'dragstart';
4572
4573
			if ( event.type === 'drop' ) {
4574
				this.containerDragleave();
4575
			}
4576
		}, this ) );
4577
4578
		this.initialized = true;
4579
		return this;
4580
	},
4581
4582
	/**
4583
	 * Check browser support for drag'n'drop.
4584
	 *
4585
	 * @return Boolean
4586
	 */
4587
	browserSupport: function() {
4588
		var supports = false, div = document.createElement('div');
4589
4590
		supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div );
4591
		supports = supports && !! ( window.File && window.FileList && window.FileReader );
4592
		return supports;
4593
	},
4594
4595
	isDraggingFile: function( event ) {
4596
		if ( this.draggingFile !== null ) {
4597
			return this.draggingFile;
4598
		}
4599
4600
		if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) {
4601
			return false;
4602
		}
4603
4604
		this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 &&
4605
			_.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1;
4606
4607
		return this.draggingFile;
4608
	},
4609
4610
	refresh: function( e ) {
4611
		var dropzone_id;
4612
		for ( dropzone_id in this.dropzones ) {
4613
			// Hide the dropzones only if dragging has left the screen.
4614
			this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone );
4615
		}
4616
4617
		if ( ! _.isUndefined( e ) ) {
4618
			$( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone );
4619
		}
4620
4621
		if ( ! this.overContainer && ! this.overDropzone ) {
4622
			this.draggingFile = null;
4623
		}
4624
4625
		return this;
4626
	},
4627
4628
	render: function() {
4629
		if ( ! this.initialized ) {
4630
			return this;
4631
		}
4632
4633
		View.prototype.render.apply( this, arguments );
4634
		$( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) );
4635
		return this;
4636
	},
4637
4638
	attach: function( index, editor ) {
4639
		// Attach a dropzone to an editor.
4640
		var dropzone = this.$el.clone();
4641
		this.dropzones.push( dropzone );
4642
		$( editor ).append( dropzone );
4643
		return this;
4644
	},
4645
4646
	/**
4647
	 * When a file is dropped on the editor uploader, open up an editor media workflow
4648
	 * and upload the file immediately.
4649
	 *
4650
	 * @param  {jQuery.Event} event The 'drop' event.
4651
	 */
4652
	drop: function( event ) {
4653
		var $wrap, uploadView;
4654
4655
		this.containerDragleave( event );
4656
		this.dropzoneDragleave( event );
4657
4658
		this.files = event.originalEvent.dataTransfer.files;
4659
		if ( this.files.length < 1 ) {
4660
			return;
4661
		}
4662
4663
		// Set the active editor to the drop target.
4664
		$wrap = $( event.target ).parents( '.wp-editor-wrap' );
4665
		if ( $wrap.length > 0 && $wrap[0].id ) {
4666
			window.wpActiveEditor = $wrap[0].id.slice( 3, -5 );
4667
		}
4668
4669
		if ( ! this.workflow ) {
4670
			this.workflow = wp.media.editor.open( window.wpActiveEditor, {
4671
				frame:    'post',
4672
				state:    'insert',
4673
				title:    l10n.addMedia,
4674
				multiple: true
4675
			});
4676
4677
			uploadView = this.workflow.uploader;
4678
4679
			if ( uploadView.uploader && uploadView.uploader.ready ) {
4680
				this.addFiles.apply( this );
4681
			} else {
4682
				this.workflow.on( 'uploader:ready', this.addFiles, this );
4683
			}
4684
		} else {
4685
			this.workflow.state().reset();
4686
			this.addFiles.apply( this );
4687
			this.workflow.open();
4688
		}
4689
4690
		return false;
4691
	},
4692
4693
	/**
4694
	 * Add the files to the uploader.
4695
	 */
4696
	addFiles: function() {
4697
		if ( this.files.length ) {
4698
			this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) );
4699
			this.files = [];
4700
		}
4701
		return this;
4702
	},
4703
4704
	containerDragover: function( event ) {
4705
		if ( this.localDrag || ! this.isDraggingFile( event ) ) {
4706
			return;
4707
		}
4708
4709
		this.overContainer = true;
4710
		this.refresh();
4711
	},
4712
4713
	containerDragleave: function() {
4714
		this.overContainer = false;
4715
4716
		// Throttle dragleave because it's called when bouncing from some elements to others.
4717
		_.delay( _.bind( this.refresh, this ), 50 );
4718
	},
4719
4720
	dropzoneDragover: function( event ) {
4721
		if ( this.localDrag || ! this.isDraggingFile( event ) ) {
4722
			return;
4723
		}
4724
4725
		this.overDropzone = true;
4726
		this.refresh( event );
4727
		return false;
4728
	},
4729
4730
	dropzoneDragleave: function( e ) {
4731
		this.overDropzone = false;
4732
		_.delay( _.bind( this.refresh, this, e ), 50 );
4733
	},
4734
4735
	click: function( e ) {
4736
		// In the rare case where the dropzone gets stuck, hide it on click.
4737
		this.containerDragleave( e );
4738
		this.dropzoneDragleave( e );
4739
		this.localDrag = false;
4740
	}
4741
});
4742
4743
module.exports = EditorUploader;
4744
4745
4746
/***/ }),
4747
/* 55 */
4748
/***/ (function(module, exports) {
4749
4750
var View = wp.media.View,
4751
	UploaderInline;
4752
4753
/**
4754
 * wp.media.view.UploaderInline
4755
 *
4756
 * The inline uploader that shows up in the 'Upload Files' tab.
4757
 *
4758
 * @memberOf wp.media.view
4759
 *
4760
 * @class
4761
 * @augments wp.media.View
4762
 * @augments wp.Backbone.View
4763
 * @augments Backbone.View
4764
 */
4765
UploaderInline = View.extend(/** @lends wp.media.view.UploaderInline.prototype */{
4766
	tagName:   'div',
4767
	className: 'uploader-inline',
4768
	template:  wp.template('uploader-inline'),
4769
4770
	events: {
4771
		'click .close': 'hide'
4772
	},
4773
4774
	initialize: function() {
4775
		_.defaults( this.options, {
4776
			message: '',
4777
			status:  true,
4778
			canClose: false
4779
		});
4780
4781
		if ( ! this.options.$browser && this.controller.uploader ) {
4782
			this.options.$browser = this.controller.uploader.$browser;
4783
		}
4784
4785
		if ( _.isUndefined( this.options.postId ) ) {
4786
			this.options.postId = wp.media.view.settings.post.id;
4787
		}
4788
4789
		if ( this.options.status ) {
4790
			this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({
4791
				controller: this.controller
4792
			}) );
4793
		}
4794
	},
4795
4796
	prepare: function() {
4797
		var suggestedWidth = this.controller.state().get('suggestedWidth'),
4798
			suggestedHeight = this.controller.state().get('suggestedHeight'),
4799
			data = {};
4800
4801
		data.message = this.options.message;
4802
		data.canClose = this.options.canClose;
4803
4804
		if ( suggestedWidth && suggestedHeight ) {
4805
			data.suggestedWidth = suggestedWidth;
4806
			data.suggestedHeight = suggestedHeight;
4807
		}
4808
4809
		return data;
4810
	},
4811
	/**
4812
	 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
4813
	 */
4814
	dispose: function() {
4815
		if ( this.disposing ) {
4816
			/**
4817
			 * call 'dispose' directly on the parent class
4818
			 */
4819
			return View.prototype.dispose.apply( this, arguments );
4820
		}
4821
4822
		// Run remove on `dispose`, so we can be sure to refresh the
4823
		// uploader with a view-less DOM. Track whether we're disposing
4824
		// so we don't trigger an infinite loop.
4825
		this.disposing = true;
4826
		return this.remove();
4827
	},
4828
	/**
4829
	 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining
4830
	 */
4831
	remove: function() {
4832
		/**
4833
		 * call 'remove' directly on the parent class
4834
		 */
4835
		var result = View.prototype.remove.apply( this, arguments );
4836
4837
		_.defer( _.bind( this.refresh, this ) );
4838
		return result;
4839
	},
4840
4841
	refresh: function() {
4842
		var uploader = this.controller.uploader;
4843
4844
		if ( uploader ) {
4845
			uploader.refresh();
4846
		}
4847
	},
4848
	/**
4849
	 * @returns {wp.media.view.UploaderInline}
4850
	 */
4851
	ready: function() {
4852
		var $browser = this.options.$browser,
4853
			$placeholder;
4854
4855
		if ( this.controller.uploader ) {
4856
			$placeholder = this.$('.browser');
4857
4858
			// Check if we've already replaced the placeholder.
4859
			if ( $placeholder[0] === $browser[0] ) {
4860
				return;
4861
			}
4862
4863
			$browser.detach().text( $placeholder.text() );
4864
			$browser[0].className = $placeholder[0].className;
4865
			$placeholder.replaceWith( $browser.show() );
4866
		}
4867
4868
		this.refresh();
4869
		return this;
4870
	},
4871
	show: function() {
4872
		this.$el.removeClass( 'hidden' );
4873
		if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) {
4874
			this.controller.$uploaderToggler.attr( 'aria-expanded', 'true' );
4875
		}
4876
	},
4877
	hide: function() {
4878
		this.$el.addClass( 'hidden' );
4879
		if ( this.controller.$uploaderToggler && this.controller.$uploaderToggler.length ) {
4880
			this.controller.$uploaderToggler
4881
				.attr( 'aria-expanded', 'false' )
4882
				// Move focus back to the toggle button when closing the uploader.
4883
				.focus();
4884
		}
4885
	}
4886
4887
});
4888
4889
module.exports = UploaderInline;
4890
4891
4892
/***/ }),
4893
/* 56 */
4894
/***/ (function(module, exports) {
4895
4896
var View = wp.media.View,
4897
	UploaderStatus;
4898
4899
/**
4900
 * wp.media.view.UploaderStatus
4901
 *
4902
 * An uploader status for on-going uploads.
4903
 *
4904
 * @memberOf wp.media.view
4905
 *
4906
 * @class
4907
 * @augments wp.media.View
4908
 * @augments wp.Backbone.View
4909
 * @augments Backbone.View
4910
 */
4911
UploaderStatus = View.extend(/** @lends wp.media.view.UploaderStatus.prototype */{
4912
	className: 'media-uploader-status',
4913
	template:  wp.template('uploader-status'),
4914
4915
	events: {
4916
		'click .upload-dismiss-errors': 'dismiss'
4917
	},
4918
4919
	initialize: function() {
4920
		this.queue = wp.Uploader.queue;
4921
		this.queue.on( 'add remove reset', this.visibility, this );
4922
		this.queue.on( 'add remove reset change:percent', this.progress, this );
4923
		this.queue.on( 'add remove reset change:uploading', this.info, this );
4924
4925
		this.errors = wp.Uploader.errors;
4926
		this.errors.reset();
4927
		this.errors.on( 'add remove reset', this.visibility, this );
4928
		this.errors.on( 'add', this.error, this );
4929
	},
4930
	/**
4931
	 * @returns {wp.media.view.UploaderStatus}
4932
	 */
4933
	dispose: function() {
4934
		wp.Uploader.queue.off( null, null, this );
4935
		/**
4936
		 * call 'dispose' directly on the parent class
4937
		 */
4938
		View.prototype.dispose.apply( this, arguments );
4939
		return this;
4940
	},
4941
4942
	visibility: function() {
4943
		this.$el.toggleClass( 'uploading', !! this.queue.length );
4944
		this.$el.toggleClass( 'errors', !! this.errors.length );
4945
		this.$el.toggle( !! this.queue.length || !! this.errors.length );
4946
	},
4947
4948
	ready: function() {
4949
		_.each({
4950
			'$bar':      '.media-progress-bar div',
4951
			'$index':    '.upload-index',
4952
			'$total':    '.upload-total',
4953
			'$filename': '.upload-filename'
4954
		}, function( selector, key ) {
4955
			this[ key ] = this.$( selector );
4956
		}, this );
4957
4958
		this.visibility();
4959
		this.progress();
4960
		this.info();
4961
	},
4962
4963
	progress: function() {
4964
		var queue = this.queue,
4965
			$bar = this.$bar;
4966
4967
		if ( ! $bar || ! queue.length ) {
4968
			return;
4969
		}
4970
4971
		$bar.width( ( queue.reduce( function( memo, attachment ) {
4972
			if ( ! attachment.get('uploading') ) {
4973
				return memo + 100;
4974
			}
4975
4976
			var percent = attachment.get('percent');
4977
			return memo + ( _.isNumber( percent ) ? percent : 100 );
4978
		}, 0 ) / queue.length ) + '%' );
4979
	},
4980
4981
	info: function() {
4982
		var queue = this.queue,
4983
			index = 0, active;
4984
4985
		if ( ! queue.length ) {
4986
			return;
4987
		}
4988
4989
		active = this.queue.find( function( attachment, i ) {
4990
			index = i;
4991
			return attachment.get('uploading');
4992
		});
4993
4994
		this.$index.text( index + 1 );
4995
		this.$total.text( queue.length );
4996
		this.$filename.html( active ? this.filename( active.get('filename') ) : '' );
4997
	},
4998
	/**
4999
	 * @param {string} filename
5000
	 * @returns {string}
5001
	 */
5002
	filename: function( filename ) {
5003
		return _.escape( filename );
5004
	},
5005
	/**
5006
	 * @param {Backbone.Model} error
5007
	 */
5008
	error: function( error ) {
5009
		this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
5010
			filename: this.filename( error.get('file').name ),
5011
			message:  error.get('message')
5012
		}), { at: 0 });
5013
	},
5014
5015
	/**
5016
	 * @param {Object} event
5017
	 */
5018
	dismiss: function( event ) {
5019
		var errors = this.views.get('.upload-errors');
5020
5021
		event.preventDefault();
5022
5023
		if ( errors ) {
5024
			_.invoke( errors, 'remove' );
5025
		}
5026
		wp.Uploader.errors.reset();
5027
	}
5028
});
5029
5030
module.exports = UploaderStatus;
5031
5032
5033
/***/ }),
5034
/* 57 */
5035
/***/ (function(module, exports) {
5036
5037
/**
5038
 * wp.media.view.UploaderStatusError
5039
 *
5040
 * @memberOf wp.media.view
5041
 *
5042
 * @class
5043
 * @augments wp.media.View
5044
 * @augments wp.Backbone.View
5045
 * @augments Backbone.View
5046
 */
5047
var UploaderStatusError = wp.media.View.extend(/** @lends wp.media.view.UploaderStatusError.prototype */{
5048
	className: 'upload-error',
5049
	template:  wp.template('uploader-status-error')
5050
});
5051
5052
module.exports = UploaderStatusError;
5053
5054
5055
/***/ }),
5056
/* 58 */
5057
/***/ (function(module, exports) {
5058
5059
var View = wp.media.View,
5060
	Toolbar;
5061
5062
/**
5063
 * wp.media.view.Toolbar
5064
 *
5065
 * A toolbar which consists of a primary and a secondary section. Each sections
5066
 * can be filled with views.
5067
 *
5068
 * @memberOf wp.media.view
5069
 *
5070
 * @class
5071
 * @augments wp.media.View
5072
 * @augments wp.Backbone.View
5073
 * @augments Backbone.View
5074
 */
5075
Toolbar = View.extend(/** @lends wp.media.view.Toolbar.prototype */{
5076
	tagName:   'div',
5077
	className: 'media-toolbar',
5078
5079
	initialize: function() {
5080
		var state = this.controller.state(),
5081
			selection = this.selection = state.get('selection'),
5082
			library = this.library = state.get('library');
5083
5084
		this._views = {};
5085
5086
		// The toolbar is composed of two `PriorityList` views.
5087
		this.primary   = new wp.media.view.PriorityList();
5088
		this.secondary = new wp.media.view.PriorityList();
5089
		this.primary.$el.addClass('media-toolbar-primary search-form');
5090
		this.secondary.$el.addClass('media-toolbar-secondary');
5091
5092
		this.views.set([ this.secondary, this.primary ]);
5093
5094
		if ( this.options.items ) {
5095
			this.set( this.options.items, { silent: true });
5096
		}
5097
5098
		if ( ! this.options.silent ) {
5099
			this.render();
5100
		}
5101
5102
		if ( selection ) {
5103
			selection.on( 'add remove reset', this.refresh, this );
5104
		}
5105
5106
		if ( library ) {
5107
			library.on( 'add remove reset', this.refresh, this );
5108
		}
5109
	},
5110
	/**
5111
	 * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining
5112
	 */
5113
	dispose: function() {
5114
		if ( this.selection ) {
5115
			this.selection.off( null, null, this );
5116
		}
5117
5118
		if ( this.library ) {
5119
			this.library.off( null, null, this );
1.1.4 by Paul Gear
new upstream release 4.2
5120
		}
5121
		/**
5122
		 * call 'dispose' directly on the parent class
5123
		 */
5124
		return View.prototype.dispose.apply( this, arguments );
5125
	},
1.1.30 by Barry Price
new upstream release 4.4
5126
5127
	ready: function() {
5128
		this.refresh();
5129
	},
5130
5131
	/**
5132
	 * @param {string} id
5133
	 * @param {Backbone.View|Object} view
5134
	 * @param {Object} [options={}]
5135
	 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
5136
	 */
5137
	set: function( id, view, options ) {
5138
		var list;
5139
		options = options || {};
5140
5141
		// Accept an object with an `id` : `view` mapping.
5142
		if ( _.isObject( id ) ) {
5143
			_.each( id, function( view, id ) {
5144
				this.set( id, view, { silent: true });
5145
			}, this );
5146
5147
		} else {
5148
			if ( ! ( view instanceof Backbone.View ) ) {
5149
				view.classes = [ 'media-button-' + id ].concat( view.classes || [] );
5150
				view = new wp.media.view.Button( view ).render();
5151
			}
5152
5153
			view.controller = view.controller || this.controller;
5154
5155
			this._views[ id ] = view;
5156
5157
			list = view.options.priority < 0 ? 'secondary' : 'primary';
5158
			this[ list ].set( id, view, options );
5159
		}
5160
5161
		if ( ! options.silent ) {
5162
			this.refresh();
5163
		}
5164
5165
		return this;
5166
	},
5167
	/**
5168
	 * @param {string} id
5169
	 * @returns {wp.media.view.Button}
5170
	 */
5171
	get: function( id ) {
5172
		return this._views[ id ];
5173
	},
5174
	/**
5175
	 * @param {string} id
5176
	 * @param {Object} options
5177
	 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining
5178
	 */
5179
	unset: function( id, options ) {
5180
		delete this._views[ id ];
5181
		this.primary.unset( id, options );
5182
		this.secondary.unset( id, options );
5183
5184
		if ( ! options || ! options.silent ) {
5185
			this.refresh();
5186
		}
5187
		return this;
5188
	},
5189
5190
	refresh: function() {
5191
		var state = this.controller.state(),
5192
			library = state.get('library'),
5193
			selection = state.get('selection');
5194
5195
		_.each( this._views, function( button ) {
5196
			if ( ! button.model || ! button.options || ! button.options.requires ) {
5197
				return;
5198
			}
5199
5200
			var requires = button.options.requires,
5201
				disabled = false;
5202
5203
			// Prevent insertion of attachments if any of them are still uploading
5204
			if ( selection && selection.models ) {
5205
				disabled = _.some( selection.models, function( attachment ) {
5206
					return attachment.get('uploading') === true;
5207
				});
5208
			}
5209
5210
			if ( requires.selection && selection && ! selection.length ) {
5211
				disabled = true;
5212
			} else if ( requires.library && library && ! library.length ) {
5213
				disabled = true;
5214
			}
5215
			button.model.set( 'disabled', disabled );
5216
		});
5217
	}
5218
});
5219
5220
module.exports = Toolbar;
5221
5222
5223
/***/ }),
5224
/* 59 */
5225
/***/ (function(module, exports) {
5226
5227
var Toolbar = wp.media.view.Toolbar,
5228
	l10n = wp.media.view.l10n,
5229
	Select;
5230
5231
/**
5232
 * wp.media.view.Toolbar.Select
5233
 *
5234
 * @memberOf wp.media.view.Toolbar
5235
 *
5236
 * @class
5237
 * @augments wp.media.view.Toolbar
5238
 * @augments wp.media.View
5239
 * @augments wp.Backbone.View
5240
 * @augments Backbone.View
5241
 */
5242
Select = Toolbar.extend(/** @lends wp.media.view.Toolbar.Select.prototype */{
5243
	initialize: function() {
5244
		var options = this.options;
5245
5246
		_.bindAll( this, 'clickSelect' );
5247
5248
		_.defaults( options, {
5249
			event: 'select',
5250
			state: false,
5251
			reset: true,
5252
			close: true,
5253
			text:  l10n.select,
5254
5255
			// Does the button rely on the selection?
5256
			requires: {
5257
				selection: true
5258
			}
5259
		});
5260
5261
		options.items = _.defaults( options.items || {}, {
5262
			select: {
5263
				style:    'primary',
5264
				text:     options.text,
5265
				priority: 80,
5266
				click:    this.clickSelect,
5267
				requires: options.requires
5268
			}
5269
		});
5270
		// Call 'initialize' directly on the parent class.
5271
		Toolbar.prototype.initialize.apply( this, arguments );
5272
	},
5273
5274
	clickSelect: function() {
5275
		var options = this.options,
5276
			controller = this.controller;
5277
5278
		if ( options.close ) {
5279
			controller.close();
5280
		}
5281
5282
		if ( options.event ) {
5283
			controller.state().trigger( options.event );
5284
		}
5285
5286
		if ( options.state ) {
5287
			controller.setState( options.state );
5288
		}
5289
5290
		if ( options.reset ) {
5291
			controller.reset();
5292
		}
5293
	}
5294
});
5295
5296
module.exports = Select;
5297
5298
5299
/***/ }),
5300
/* 60 */
5301
/***/ (function(module, exports) {
5302
5303
var Select = wp.media.view.Toolbar.Select,
5304
	l10n = wp.media.view.l10n,
5305
	Embed;
5306
5307
/**
5308
 * wp.media.view.Toolbar.Embed
5309
 *
5310
 * @memberOf wp.media.view.Toolbar
5311
 *
5312
 * @class
5313
 * @augments wp.media.view.Toolbar.Select
5314
 * @augments wp.media.view.Toolbar
5315
 * @augments wp.media.View
5316
 * @augments wp.Backbone.View
5317
 * @augments Backbone.View
5318
 */
5319
Embed = Select.extend(/** @lends wp.media.view.Toolbar.Embed.prototype */{
5320
	initialize: function() {
5321
		_.defaults( this.options, {
5322
			text: l10n.insertIntoPost,
5323
			requires: false
5324
		});
5325
		// Call 'initialize' directly on the parent class.
5326
		Select.prototype.initialize.apply( this, arguments );
5327
	},
5328
5329
	refresh: function() {
5330
		var url = this.controller.state().props.get('url');
5331
		this.get('select').model.set( 'disabled', ! url || url === 'http://' );
5332
		/**
5333
		 * call 'refresh' directly on the parent class
5334
		 */
5335
		Select.prototype.refresh.apply( this, arguments );
5336
	}
5337
});
5338
5339
module.exports = Embed;
5340
5341
5342
/***/ }),
5343
/* 61 */
5344
/***/ (function(module, exports) {
5345
5346
/**
5347
 * wp.media.view.Button
5348
 *
5349
 * @memberOf wp.media.view
5350
 *
5351
 * @class
5352
 * @augments wp.media.View
5353
 * @augments wp.Backbone.View
5354
 * @augments Backbone.View
5355
 */
5356
var Button = wp.media.View.extend(/** @lends wp.media.view.Button.prototype */{
5357
	tagName:    'button',
5358
	className:  'media-button',
5359
	attributes: { type: 'button' },
5360
5361
	events: {
5362
		'click': 'click'
5363
	},
5364
5365
	defaults: {
5366
		text:     '',
5367
		style:    '',
5368
		size:     'large',
5369
		disabled: false
5370
	},
5371
5372
	initialize: function() {
5373
		/**
5374
		 * Create a model with the provided `defaults`.
5375
		 *
5376
		 * @member {Backbone.Model}
5377
		 */
5378
		this.model = new Backbone.Model( this.defaults );
5379
5380
		// If any of the `options` have a key from `defaults`, apply its
5381
		// value to the `model` and remove it from the `options object.
5382
		_.each( this.defaults, function( def, key ) {
5383
			var value = this.options[ key ];
5384
			if ( _.isUndefined( value ) ) {
5385
				return;
5386
			}
5387
5388
			this.model.set( key, value );
5389
			delete this.options[ key ];
5390
		}, this );
5391
5392
		this.listenTo( this.model, 'change', this.render );
5393
	},
5394
	/**
5395
	 * @returns {wp.media.view.Button} Returns itself to allow chaining
5396
	 */
5397
	render: function() {
5398
		var classes = [ 'button', this.className ],
5399
			model = this.model.toJSON();
5400
5401
		if ( model.style ) {
5402
			classes.push( 'button-' + model.style );
5403
		}
5404
5405
		if ( model.size ) {
5406
			classes.push( 'button-' + model.size );
5407
		}
5408
5409
		classes = _.uniq( classes.concat( this.options.classes ) );
5410
		this.el.className = classes.join(' ');
5411
5412
		this.$el.attr( 'disabled', model.disabled );
5413
		this.$el.text( this.model.get('text') );
5414
5415
		return this;
5416
	},
5417
	/**
5418
	 * @param {Object} event
5419
	 */
5420
	click: function( event ) {
5421
		if ( '#' === this.attributes.href ) {
5422
			event.preventDefault();
5423
		}
5424
5425
		if ( this.options.click && ! this.model.get('disabled') ) {
5426
			this.options.click.apply( this, arguments );
5427
		}
5428
	}
5429
});
5430
5431
module.exports = Button;
5432
5433
5434
/***/ }),
5435
/* 62 */
5436
/***/ (function(module, exports) {
5437
5438
var $ = Backbone.$,
5439
	ButtonGroup;
5440
5441
/**
5442
 * wp.media.view.ButtonGroup
5443
 *
5444
 * @memberOf wp.media.view
5445
 *
5446
 * @class
5447
 * @augments wp.media.View
5448
 * @augments wp.Backbone.View
5449
 * @augments Backbone.View
5450
 */
5451
ButtonGroup = wp.media.View.extend(/** @lends wp.media.view.ButtonGroup.prototype */{
5452
	tagName:   'div',
5453
	className: 'button-group button-large media-button-group',
5454
5455
	initialize: function() {
5456
		/**
5457
		 * @member {wp.media.view.Button[]}
5458
		 */
5459
		this.buttons = _.map( this.options.buttons || [], function( button ) {
5460
			if ( button instanceof Backbone.View ) {
5461
				return button;
5462
			} else {
5463
				return new wp.media.view.Button( button ).render();
5464
			}
5465
		});
5466
5467
		delete this.options.buttons;
5468
5469
		if ( this.options.classes ) {
5470
			this.$el.addClass( this.options.classes );
5471
		}
5472
	},
5473
5474
	/**
5475
	 * @returns {wp.media.view.ButtonGroup}
5476
	 */
5477
	render: function() {
5478
		this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() );
5479
		return this;
5480
	}
5481
});
5482
5483
module.exports = ButtonGroup;
5484
5485
5486
/***/ }),
5487
/* 63 */
5488
/***/ (function(module, exports) {
5489
5490
/**
5491
 * wp.media.view.PriorityList
5492
 *
5493
 * @memberOf wp.media.view
5494
 *
5495
 * @class
5496
 * @augments wp.media.View
5497
 * @augments wp.Backbone.View
5498
 * @augments Backbone.View
5499
 */
5500
var PriorityList = wp.media.View.extend(/** @lends wp.media.view.PriorityList.prototype */{
5501
	tagName:   'div',
5502
5503
	initialize: function() {
5504
		this._views = {};
5505
5506
		this.set( _.extend( {}, this._views, this.options.views ), { silent: true });
5507
		delete this.options.views;
5508
5509
		if ( ! this.options.silent ) {
5510
			this.render();
5511
		}
5512
	},
5513
	/**
5514
	 * @param {string} id
5515
	 * @param {wp.media.View|Object} view
5516
	 * @param {Object} options
5517
	 * @returns {wp.media.view.PriorityList} Returns itself to allow chaining
5518
	 */
5519
	set: function( id, view, options ) {
5520
		var priority, views, index;
5521
5522
		options = options || {};
5523
5524
		// Accept an object with an `id` : `view` mapping.
5525
		if ( _.isObject( id ) ) {
5526
			_.each( id, function( view, id ) {
5527
				this.set( id, view );
5528
			}, this );
5529
			return this;
5530
		}
5531
5532
		if ( ! (view instanceof Backbone.View) ) {
5533
			view = this.toView( view, id, options );
5534
		}
5535
		view.controller = view.controller || this.controller;
5536
5537
		this.unset( id );
5538
5539
		priority = view.options.priority || 10;
5540
		views = this.views.get() || [];
5541
5542
		_.find( views, function( existing, i ) {
5543
			if ( existing.options.priority > priority ) {
5544
				index = i;
5545
				return true;
5546
			}
5547
		});
5548
5549
		this._views[ id ] = view;
5550
		this.views.add( view, {
5551
			at: _.isNumber( index ) ? index : views.length || 0
5552
		});
5553
5554
		return this;
5555
	},
5556
	/**
5557
	 * @param {string} id
5558
	 * @returns {wp.media.View}
5559
	 */
5560
	get: function( id ) {
5561
		return this._views[ id ];
5562
	},
5563
	/**
5564
	 * @param {string} id
5565
	 * @returns {wp.media.view.PriorityList}
5566
	 */
5567
	unset: function( id ) {
5568
		var view = this.get( id );
5569
5570
		if ( view ) {
5571
			view.remove();
5572
		}
5573
5574
		delete this._views[ id ];
5575
		return this;
5576
	},
5577
	/**
5578
	 * @param {Object} options
5579
	 * @returns {wp.media.View}
5580
	 */
5581
	toView: function( options ) {
5582
		return new wp.media.View( options );
5583
	}
5584
});
5585
5586
module.exports = PriorityList;
5587
5588
5589
/***/ }),
5590
/* 64 */
5591
/***/ (function(module, exports) {
5592
5593
var $ = jQuery,
5594
	MenuItem;
5595
5596
/**
5597
 * wp.media.view.MenuItem
5598
 *
5599
 * @memberOf wp.media.view
5600
 *
5601
 * @class
5602
 * @augments wp.media.View
5603
 * @augments wp.Backbone.View
5604
 * @augments Backbone.View
5605
 */
5606
MenuItem = wp.media.View.extend(/** @lends wp.media.view.MenuItem.prototype */{
5607
	tagName:   'a',
5608
	className: 'media-menu-item',
5609
5610
	attributes: {
5611
		href: '#'
5612
	},
5613
5614
	events: {
5615
		'click': '_click'
5616
	},
5617
	/**
5618
	 * @param {Object} event
5619
	 */
5620
	_click: function( event ) {
5621
		var clickOverride = this.options.click;
1.1.4 by Paul Gear
new upstream release 4.2
5622
5623
		if ( event ) {
5624
			event.preventDefault();
5625
		}
5626
1.1.30 by Barry Price
new upstream release 4.4
5627
		if ( clickOverride ) {
5628
			clickOverride.call( this );
5629
		} else {
5630
			this.click();
5631
		}
5632
5633
		// When selecting a tab along the left side,
5634
		// focus should be transferred into the main panel
5635
		if ( ! wp.media.isTouchDevice ) {
5636
			$('.media-frame-content input').first().focus();
5637
		}
5638
	},
5639
5640
	click: function() {
5641
		var state = this.options.state;
5642
5643
		if ( state ) {
5644
			this.controller.setState( state );
5645
			this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below
5646
		}
5647
	},
5648
	/**
5649
	 * @returns {wp.media.view.MenuItem} returns itself to allow chaining
5650
	 */
5651
	render: function() {
5652
		var options = this.options;
5653
5654
		if ( options.text ) {
5655
			this.$el.text( options.text );
5656
		} else if ( options.html ) {
5657
			this.$el.html( options.html );
5658
		}
5659
5660
		return this;
1.1.4 by Paul Gear
new upstream release 4.2
5661
	}
5662
});
5663
1.1.30 by Barry Price
new upstream release 4.4
5664
module.exports = MenuItem;
5665
5666
5667
/***/ }),
5668
/* 65 */
5669
/***/ (function(module, exports) {
5670
5671
var MenuItem = wp.media.view.MenuItem,
5672
	PriorityList = wp.media.view.PriorityList,
5673
	Menu;
5674
1.1.4 by Paul Gear
new upstream release 4.2
5675
/**
1.1.30 by Barry Price
new upstream release 4.4
5676
 * wp.media.view.Menu
5677
 *
5678
 * @memberOf wp.media.view
1.1.4 by Paul Gear
new upstream release 4.2
5679
 *
5680
 * @class
1.1.30 by Barry Price
new upstream release 4.4
5681
 * @augments wp.media.view.PriorityList
1.1.4 by Paul Gear
new upstream release 4.2
5682
 * @augments wp.media.View
5683
 * @augments wp.Backbone.View
5684
 * @augments Backbone.View
5685
 */
1.1.30 by Barry Price
new upstream release 4.4
5686
Menu = PriorityList.extend(/** @lends wp.media.view.Menu.prototype */{
5687
	tagName:   'div',
5688
	className: 'media-menu',
5689
	property:  'state',
5690
	ItemView:  MenuItem,
5691
	region:    'menu',
5692
5693
	/* TODO: alternatively hide on any click anywhere
1.1.4 by Paul Gear
new upstream release 4.2
5694
	events: {
1.1.30 by Barry Price
new upstream release 4.4
5695
		'click': 'click'
5696
	},
5697
5698
	click: function() {
5699
		this.$el.removeClass( 'visible' );
5700
	},
5701
	*/
5702
5703
	/**
5704
	 * @param {Object} options
5705
	 * @param {string} id
5706
	 * @returns {wp.media.View}
5707
	 */
5708
	toView: function( options, id ) {
5709
		options = options || {};
5710
		options[ this.property ] = options[ this.property ] || id;
5711
		return new this.ItemView( options ).render();
5712
	},
5713
5714
	ready: function() {
5715
		/**
5716
		 * call 'ready' directly on the parent class
5717
		 */
5718
		PriorityList.prototype.ready.apply( this, arguments );
5719
		this.visibility();
5720
	},
5721
5722
	set: function() {
5723
		/**
5724
		 * call 'set' directly on the parent class
5725
		 */
5726
		PriorityList.prototype.set.apply( this, arguments );
5727
		this.visibility();
5728
	},
5729
5730
	unset: function() {
5731
		/**
5732
		 * call 'unset' directly on the parent class
5733
		 */
5734
		PriorityList.prototype.unset.apply( this, arguments );
5735
		this.visibility();
5736
	},
5737
5738
	visibility: function() {
5739
		var region = this.region,
5740
			view = this.controller[ region ].get(),
5741
			views = this.views.get(),
5742
			hide = ! views || views.length < 2;
5743
5744
		if ( this === view ) {
5745
			this.controller.$el.toggleClass( 'hide-' + region, hide );
5746
		}
5747
	},
5748
	/**
5749
	 * @param {string} id
5750
	 */
5751
	select: function( id ) {
5752
		var view = this.get( id );
5753
5754
		if ( ! view ) {
5755
			return;
5756
		}
5757
5758
		this.deselect();
5759
		view.$el.addClass('active');
5760
	},
5761
5762
	deselect: function() {
5763
		this.$el.children().removeClass('active');
5764
	},
5765
5766
	hide: function( id ) {
5767
		var view = this.get( id );
5768
5769
		if ( ! view ) {
5770
			return;
5771
		}
5772
5773
		view.$el.addClass('hidden');
5774
	},
5775
5776
	show: function( id ) {
5777
		var view = this.get( id );
5778
5779
		if ( ! view ) {
5780
			return;
5781
		}
5782
5783
		view.$el.removeClass('hidden');
5784
	}
5785
});
5786
5787
module.exports = Menu;
5788
5789
5790
/***/ }),
5791
/* 66 */
5792
/***/ (function(module, exports) {
5793
5794
/**
5795
 * wp.media.view.RouterItem
5796
 *
5797
 * @memberOf wp.media.view
5798
 *
5799
 * @class
5800
 * @augments wp.media.view.MenuItem
5801
 * @augments wp.media.View
5802
 * @augments wp.Backbone.View
5803
 * @augments Backbone.View
5804
 */
5805
var RouterItem = wp.media.view.MenuItem.extend(/** @lends wp.media.view.RouterItem.prototype */{
5806
	/**
5807
	 * On click handler to activate the content region's corresponding mode.
5808
	 */
5809
	click: function() {
5810
		var contentMode = this.options.contentMode;
5811
		if ( contentMode ) {
5812
			this.controller.content.mode( contentMode );
5813
		}
5814
	}
5815
});
5816
5817
module.exports = RouterItem;
5818
5819
5820
/***/ }),
5821
/* 67 */
5822
/***/ (function(module, exports) {
5823
5824
var Menu = wp.media.view.Menu,
5825
	Router;
5826
5827
/**
5828
 * wp.media.view.Router
5829
 *
5830
 * @memberOf wp.media.view
5831
 *
5832
 * @class
5833
 * @augments wp.media.view.Menu
5834
 * @augments wp.media.view.PriorityList
5835
 * @augments wp.media.View
5836
 * @augments wp.Backbone.View
5837
 * @augments Backbone.View
5838
 */
5839
Router = Menu.extend(/** @lends wp.media.view.Router.prototype */{
5840
	tagName:   'div',
5841
	className: 'media-router',
5842
	property:  'contentMode',
5843
	ItemView:  wp.media.view.RouterItem,
5844
	region:    'router',
1.1.4 by Paul Gear
new upstream release 4.2
5845
5846
	initialize: function() {
1.1.30 by Barry Price
new upstream release 4.4
5847
		this.controller.on( 'content:render', this.update, this );
5848
		// Call 'initialize' directly on the parent class.
5849
		Menu.prototype.initialize.apply( this, arguments );
5850
	},
5851
5852
	update: function() {
5853
		var mode = this.controller.content.mode();
5854
		if ( mode ) {
5855
			this.select( mode );
5856
		}
5857
	}
5858
});
5859
5860
module.exports = Router;
5861
5862
5863
/***/ }),
5864
/* 68 */
5865
/***/ (function(module, exports) {
5866
5867
/**
5868
 * wp.media.view.Sidebar
5869
 *
5870
 * @memberOf wp.media.view
5871
 *
5872
 * @class
5873
 * @augments wp.media.view.PriorityList
5874
 * @augments wp.media.View
5875
 * @augments wp.Backbone.View
5876
 * @augments Backbone.View
5877
 */
5878
var Sidebar = wp.media.view.PriorityList.extend(/** @lends wp.media.view.Sidebar.prototype */{
5879
	className: 'media-sidebar'
5880
});
5881
5882
module.exports = Sidebar;
5883
5884
5885
/***/ }),
5886
/* 69 */
5887
/***/ (function(module, exports) {
5888
5889
var View = wp.media.View,
5890
	$ = jQuery,
5891
	Attachment;
5892
1.1.4 by Paul Gear
new upstream release 4.2
5893
/**
5894
 * wp.media.view.Attachment
5895
 *
1.1.30 by Barry Price
new upstream release 4.4
5896
 * @memberOf wp.media.view
5897
 *
1.1.4 by Paul Gear
new upstream release 4.2
5898
 * @class
5899
 * @augments wp.media.View
5900
 * @augments wp.Backbone.View
5901
 * @augments Backbone.View
5902
 */
1.1.30 by Barry Price
new upstream release 4.4
5903
Attachment = View.extend(/** @lends wp.media.view.Attachment.prototype */{
1.1.4 by Paul Gear
new upstream release 4.2
5904
	tagName:   'li',
5905
	className: 'attachment',
5906
	template:  wp.template('attachment'),
5907
5908
	attributes: function() {
5909
		return {
5910
			'tabIndex':     0,
5911
			'role':         'checkbox',
5912
			'aria-label':   this.model.get( 'title' ),
5913
			'aria-checked': false,
5914
			'data-id':      this.model.get( 'id' )
5915
		};
5916
	},
5917
5918
	events: {
1.1.30 by Barry Price
new upstream release 4.4
5919
		'click':                          'toggleSelectionHandler',
1.1.4 by Paul Gear
new upstream release 4.2
5920
		'change [data-setting]':          'updateSetting',
5921
		'change [data-setting] input':    'updateSetting',
5922
		'change [data-setting] select':   'updateSetting',
5923
		'change [data-setting] textarea': 'updateSetting',
1.1.9 by Ryan Finnie
new upstream release 4.3
5924
		'click .attachment-close':        'removeFromLibrary',
1.1.4 by Paul Gear
new upstream release 4.2
5925
		'click .check':                   'checkClickHandler',
5926
		'keydown':                        'toggleSelectionHandler'
5927
	},
5928
5929
	buttons: {},
5930
5931
	initialize: function() {
5932
		var selection = this.options.selection,
5933
			options = _.defaults( this.options, {
5934
				rerenderOnModelChange: true
5935
			} );
5936
5937
		if ( options.rerenderOnModelChange ) {
5938
			this.listenTo( this.model, 'change', this.render );
5939
		} else {
5940
			this.listenTo( this.model, 'change:percent', this.progress );
5941
		}
5942
		this.listenTo( this.model, 'change:title', this._syncTitle );
5943
		this.listenTo( this.model, 'change:caption', this._syncCaption );
5944
		this.listenTo( this.model, 'change:artist', this._syncArtist );
5945
		this.listenTo( this.model, 'change:album', this._syncAlbum );
5946
5947
		// Update the selection.
5948
		this.listenTo( this.model, 'add', this.select );
5949
		this.listenTo( this.model, 'remove', this.deselect );
5950
		if ( selection ) {
5951
			selection.on( 'reset', this.updateSelect, this );
5952
			// Update the model's details view.
5953
			this.listenTo( this.model, 'selection:single selection:unsingle', this.details );
5954
			this.details( this.model, this.controller.state().get('selection') );
5955
		}
5956
5957
		this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave );
5958
	},
5959
	/**
5960
	 * @returns {wp.media.view.Attachment} Returns itself to allow chaining
5961
	 */
5962
	dispose: function() {
5963
		var selection = this.options.selection;
5964
5965
		// Make sure all settings are saved before removing the view.
5966
		this.updateAll();
5967
5968
		if ( selection ) {
5969
			selection.off( null, null, this );
5970
		}
5971
		/**
5972
		 * call 'dispose' directly on the parent class
5973
		 */
5974
		View.prototype.dispose.apply( this, arguments );
5975
		return this;
5976
	},
5977
	/**
5978
	 * @returns {wp.media.view.Attachment} Returns itself to allow chaining
5979
	 */
5980
	render: function() {
5981
		var options = _.defaults( this.model.toJSON(), {
5982
				orientation:   'landscape',
5983
				uploading:     false,
5984
				type:          '',
5985
				subtype:       '',
5986
				icon:          '',
5987
				filename:      '',
5988
				caption:       '',
5989
				title:         '',
5990
				dateFormatted: '',
5991
				width:         '',
5992
				height:        '',
5993
				compat:        false,
5994
				alt:           '',
5995
				description:   ''
5996
			}, this.options );
5997
5998
		options.buttons  = this.buttons;
5999
		options.describe = this.controller.state().get('describe');
6000
6001
		if ( 'image' === options.type ) {
6002
			options.size = this.imageSize();
6003
		}
6004
6005
		options.can = {};
6006
		if ( options.nonces ) {
6007
			options.can.remove = !! options.nonces['delete'];
6008
			options.can.save = !! options.nonces.update;
6009
		}
6010
6011
		if ( this.controller.state().get('allowLocalEdits') ) {
6012
			options.allowLocalEdits = true;
6013
		}
6014
6015
		if ( options.uploading && ! options.percent ) {
6016
			options.percent = 0;
6017
		}
6018
6019
		this.views.detach();
6020
		this.$el.html( this.template( options ) );
6021
6022
		this.$el.toggleClass( 'uploading', options.uploading );
6023
6024
		if ( options.uploading ) {
6025
			this.$bar = this.$('.media-progress-bar div');
6026
		} else {
6027
			delete this.$bar;
6028
		}
6029
6030
		// Check if the model is selected.
6031
		this.updateSelect();
6032
6033
		// Update the save status.
6034
		this.updateSave();
6035
6036
		this.views.render();
6037
6038
		return this;
6039
	},
6040
6041
	progress: function() {
6042
		if ( this.$bar && this.$bar.length ) {
6043
			this.$bar.width( this.model.get('percent') + '%' );
6044
		}
6045
	},
6046
6047
	/**
6048
	 * @param {Object} event
6049
	 */
6050
	toggleSelectionHandler: function( event ) {
6051
		var method;
6052
1.1.9 by Ryan Finnie
new upstream release 4.3
6053
		// Don't do anything inside inputs and on the attachment check and remove buttons.
6054
		if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) {
1.1.4 by Paul Gear
new upstream release 4.2
6055
			return;
6056
		}
6057
6058
		// Catch arrow events
6059
		if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
6060
			this.controller.trigger( 'attachment:keydown:arrow', event );
6061
			return;
6062
		}
6063
6064
		// Catch enter and space events
6065
		if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
6066
			return;
6067
		}
6068
6069
		event.preventDefault();
6070
6071
		// In the grid view, bubble up an edit:attachment event to the controller.
6072
		if ( this.controller.isModeActive( 'grid' ) ) {
6073
			if ( this.controller.isModeActive( 'edit' ) ) {
6074
				// Pass the current target to restore focus when closing
6075
				this.controller.trigger( 'edit:attachment', this.model, event.currentTarget );
6076
				return;
6077
			}
6078
6079
			if ( this.controller.isModeActive( 'select' ) ) {
6080
				method = 'toggle';
6081
			}
6082
		}
6083
6084
		if ( event.shiftKey ) {
6085
			method = 'between';
6086
		} else if ( event.ctrlKey || event.metaKey ) {
6087
			method = 'toggle';
6088
		}
6089
6090
		this.toggleSelection({
6091
			method: method
6092
		});
6093
6094
		this.controller.trigger( 'selection:toggle' );
6095
	},
6096
	/**
6097
	 * @param {Object} options
6098
	 */
6099
	toggleSelection: function( options ) {
6100
		var collection = this.collection,
6101
			selection = this.options.selection,
6102
			model = this.model,
6103
			method = options && options.method,
6104
			single, models, singleIndex, modelIndex;
6105
6106
		if ( ! selection ) {
6107
			return;
6108
		}
6109
6110
		single = selection.single();
6111
		method = _.isUndefined( method ) ? selection.multiple : method;
6112
6113
		// If the `method` is set to `between`, select all models that
6114
		// exist between the current and the selected model.
6115
		if ( 'between' === method && single && selection.multiple ) {
6116
			// If the models are the same, short-circuit.
6117
			if ( single === model ) {
6118
				return;
6119
			}
6120
6121
			singleIndex = collection.indexOf( single );
6122
			modelIndex  = collection.indexOf( this.model );
6123
6124
			if ( singleIndex < modelIndex ) {
6125
				models = collection.models.slice( singleIndex, modelIndex + 1 );
6126
			} else {
6127
				models = collection.models.slice( modelIndex, singleIndex + 1 );
6128
			}
6129
6130
			selection.add( models );
6131
			selection.single( model );
6132
			return;
6133
6134
		// If the `method` is set to `toggle`, just flip the selection
6135
		// status, regardless of whether the model is the single model.
6136
		} else if ( 'toggle' === method ) {
6137
			selection[ this.selected() ? 'remove' : 'add' ]( model );
6138
			selection.single( model );
6139
			return;
6140
		} else if ( 'add' === method ) {
6141
			selection.add( model );
6142
			selection.single( model );
6143
			return;
6144
		}
6145
6146
		// Fixes bug that loses focus when selecting a featured image
6147
		if ( ! method ) {
6148
			method = 'add';
6149
		}
6150
6151
		if ( method !== 'add' ) {
6152
			method = 'reset';
6153
		}
6154
6155
		if ( this.selected() ) {
6156
			// If the model is the single model, remove it.
6157
			// If it is not the same as the single model,
6158
			// it now becomes the single model.
6159
			selection[ single === model ? 'remove' : 'single' ]( model );
6160
		} else {
6161
			// If the model is not selected, run the `method` on the
6162
			// selection. By default, we `reset` the selection, but the
6163
			// `method` can be set to `add` the model to the selection.
6164
			selection[ method ]( model );
6165
			selection.single( model );
6166
		}
6167
	},
6168
6169
	updateSelect: function() {
6170
		this[ this.selected() ? 'select' : 'deselect' ]();
6171
	},
6172
	/**
6173
	 * @returns {unresolved|Boolean}
6174
	 */
6175
	selected: function() {
6176
		var selection = this.options.selection;
6177
		if ( selection ) {
6178
			return !! selection.get( this.model.cid );
6179
		}
6180
	},
6181
	/**
6182
	 * @param {Backbone.Model} model
6183
	 * @param {Backbone.Collection} collection
6184
	 */
6185
	select: function( model, collection ) {
6186
		var selection = this.options.selection,
6187
			controller = this.controller;
6188
6189
		// Check if a selection exists and if it's the collection provided.
6190
		// If they're not the same collection, bail; we're in another
6191
		// selection's event loop.
6192
		if ( ! selection || ( collection && collection !== selection ) ) {
6193
			return;
6194
		}
6195
6196
		// Bail if the model is already selected.
6197
		if ( this.$el.hasClass( 'selected' ) ) {
6198
			return;
6199
		}
6200
6201
		// Add 'selected' class to model, set aria-checked to true.
6202
		this.$el.addClass( 'selected' ).attr( 'aria-checked', true );
6203
		//  Make the checkbox tabable, except in media grid (bulk select mode).
6204
		if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) {
6205
			this.$( '.check' ).attr( 'tabindex', '0' );
6206
		}
6207
	},
6208
	/**
6209
	 * @param {Backbone.Model} model
6210
	 * @param {Backbone.Collection} collection
6211
	 */
6212
	deselect: function( model, collection ) {
6213
		var selection = this.options.selection;
6214
6215
		// Check if a selection exists and if it's the collection provided.
6216
		// If they're not the same collection, bail; we're in another
6217
		// selection's event loop.
6218
		if ( ! selection || ( collection && collection !== selection ) ) {
6219
			return;
6220
		}
6221
		this.$el.removeClass( 'selected' ).attr( 'aria-checked', false )
6222
			.find( '.check' ).attr( 'tabindex', '-1' );
6223
	},
6224
	/**
6225
	 * @param {Backbone.Model} model
6226
	 * @param {Backbone.Collection} collection
6227
	 */
6228
	details: function( model, collection ) {
6229
		var selection = this.options.selection,
6230
			details;
6231
6232
		if ( selection !== collection ) {
6233
			return;
6234
		}
6235
6236
		details = selection.single();
6237
		this.$el.toggleClass( 'details', details === this.model );
6238
	},
6239
	/**
6240
	 * @param {string} size
6241
	 * @returns {Object}
6242
	 */
6243
	imageSize: function( size ) {
6244
		var sizes = this.model.get('sizes'), matched = false;
6245
6246
		size = size || 'medium';
6247
6248
		// Use the provided image size if possible.
6249
		if ( sizes ) {
6250
			if ( sizes[ size ] ) {
6251
				matched = sizes[ size ];
6252
			} else if ( sizes.large ) {
6253
				matched = sizes.large;
6254
			} else if ( sizes.thumbnail ) {
6255
				matched = sizes.thumbnail;
6256
			} else if ( sizes.full ) {
6257
				matched = sizes.full;
6258
			}
6259
6260
			if ( matched ) {
6261
				return _.clone( matched );
6262
			}
6263
		}
6264
6265
		return {
6266
			url:         this.model.get('url'),
6267
			width:       this.model.get('width'),
6268
			height:      this.model.get('height'),
6269
			orientation: this.model.get('orientation')
6270
		};
6271
	},
6272
	/**
6273
	 * @param {Object} event
6274
	 */
6275
	updateSetting: function( event ) {
6276
		var $setting = $( event.target ).closest('[data-setting]'),
6277
			setting, value;
6278
6279
		if ( ! $setting.length ) {
6280
			return;
6281
		}
6282
6283
		setting = $setting.data('setting');
6284
		value   = event.target.value;
6285
6286
		if ( this.model.get( setting ) !== value ) {
6287
			this.save( setting, value );
6288
		}
6289
	},
6290
6291
	/**
6292
	 * Pass all the arguments to the model's save method.
6293
	 *
6294
	 * Records the aggregate status of all save requests and updates the
6295
	 * view's classes accordingly.
6296
	 */
6297
	save: function() {
6298
		var view = this,
6299
			save = this._save = this._save || { status: 'ready' },
6300
			request = this.model.save.apply( this.model, arguments ),
6301
			requests = save.requests ? $.when( request, save.requests ) : request;
6302
6303
		// If we're waiting to remove 'Saved.', stop.
6304
		if ( save.savedTimer ) {
6305
			clearTimeout( save.savedTimer );
6306
		}
6307
6308
		this.updateSave('waiting');
6309
		save.requests = requests;
6310
		requests.always( function() {
6311
			// If we've performed another request since this one, bail.
6312
			if ( save.requests !== requests ) {
6313
				return;
6314
			}
6315
6316
			view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' );
6317
			save.savedTimer = setTimeout( function() {
6318
				view.updateSave('ready');
6319
				delete save.savedTimer;
6320
			}, 2000 );
6321
		});
6322
	},
6323
	/**
6324
	 * @param {string} status
6325
	 * @returns {wp.media.view.Attachment} Returns itself to allow chaining
6326
	 */
6327
	updateSave: function( status ) {
6328
		var save = this._save = this._save || { status: 'ready' };
6329
6330
		if ( status && status !== save.status ) {
6331
			this.$el.removeClass( 'save-' + save.status );
6332
			save.status = status;
6333
		}
6334
6335
		this.$el.addClass( 'save-' + save.status );
6336
		return this;
6337
	},
6338
6339
	updateAll: function() {
6340
		var $settings = this.$('[data-setting]'),
6341
			model = this.model,
6342
			changed;
6343
6344
		changed = _.chain( $settings ).map( function( el ) {
6345
			var $input = $('input, textarea, select, [value]', el ),
6346
				setting, value;
6347
6348
			if ( ! $input.length ) {
6349
				return;
6350
			}
6351
6352
			setting = $(el).data('setting');
6353
			value = $input.val();
6354
6355
			// Record the value if it changed.
6356
			if ( model.get( setting ) !== value ) {
6357
				return [ setting, value ];
6358
			}
6359
		}).compact().object().value();
6360
6361
		if ( ! _.isEmpty( changed ) ) {
6362
			model.save( changed );
6363
		}
6364
	},
6365
	/**
6366
	 * @param {Object} event
6367
	 */
6368
	removeFromLibrary: function( event ) {
6369
		// Catch enter and space events
6370
		if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) {
6371
			return;
6372
		}
6373
6374
		// Stop propagation so the model isn't selected.
6375
		event.stopPropagation();
6376
6377
		this.collection.remove( this.model );
6378
	},
6379
6380
	/**
6381
	 * Add the model if it isn't in the selection, if it is in the selection,
6382
	 * remove it.
6383
	 *
6384
	 * @param  {[type]} event [description]
6385
	 * @return {[type]}       [description]
6386
	 */
6387
	checkClickHandler: function ( event ) {
6388
		var selection = this.options.selection;
6389
		if ( ! selection ) {
6390
			return;
6391
		}
6392
		event.stopPropagation();
6393
		if ( selection.where( { id: this.model.get( 'id' ) } ).length ) {
6394
			selection.remove( this.model );
6395
			// Move focus back to the attachment tile (from the check).
1 by Jacek Nykis
Initial commit
6396
			this.$el.focus();
1.1.4 by Paul Gear
new upstream release 4.2
6397
		} else {
6398
			selection.add( this.model );
6399
		}
6400
	}
6401
});
6402
6403
// Ensure settings remain in sync between attachment views.
6404
_.each({
6405
	caption: '_syncCaption',
6406
	title:   '_syncTitle',
6407
	artist:  '_syncArtist',
6408
	album:   '_syncAlbum'
6409
}, function( method, setting ) {
6410
	/**
1.1.30 by Barry Price
new upstream release 4.4
6411
	 * @function _syncCaption
6412
	 * @memberOf wp.media.view.Attachment
6413
	 * @instance
6414
	 *
6415
	 * @param {Backbone.Model} model
6416
	 * @param {string} value
6417
	 * @returns {wp.media.view.Attachment} Returns itself to allow chaining
6418
	 */
6419
	/**
6420
	 * @function _syncTitle
6421
	 * @memberOf wp.media.view.Attachment
6422
	 * @instance
6423
	 *
6424
	 * @param {Backbone.Model} model
6425
	 * @param {string} value
6426
	 * @returns {wp.media.view.Attachment} Returns itself to allow chaining
6427
	 */
6428
	/**
6429
	 * @function _syncArtist
6430
	 * @memberOf wp.media.view.Attachment
6431
	 * @instance
6432
	 *
6433
	 * @param {Backbone.Model} model
6434
	 * @param {string} value
6435
	 * @returns {wp.media.view.Attachment} Returns itself to allow chaining
6436
	 */
6437
	/**
6438
	 * @function _syncAlbum
6439
	 * @memberOf wp.media.view.Attachment
6440
	 * @instance
6441
	 *
1.1.4 by Paul Gear
new upstream release 4.2
6442
	 * @param {Backbone.Model} model
6443
	 * @param {string} value
6444
	 * @returns {wp.media.view.Attachment} Returns itself to allow chaining
6445
	 */
6446
	Attachment.prototype[ method ] = function( model, value ) {
6447
		var $setting = this.$('[data-setting="' + setting + '"]');
6448
6449
		if ( ! $setting.length ) {
6450
			return this;
6451
		}
6452
6453
		// If the updated value is in sync with the value in the DOM, there
6454
		// is no need to re-render. If we're currently editing the value,
6455
		// it will automatically be in sync, suppressing the re-render for
6456
		// the view we're editing, while updating any others.
6457
		if ( value === $setting.find('input, textarea, select, [value]').val() ) {
6458
			return this;
6459
		}
6460
6461
		return this.render();
6462
	};
6463
});
6464
6465
module.exports = Attachment;
6466
1.1.30 by Barry Price
new upstream release 4.4
6467
6468
/***/ }),
6469
/* 70 */
6470
/***/ (function(module, exports) {
6471
1.1.4 by Paul Gear
new upstream release 4.2
6472
/**
1.1.30 by Barry Price
new upstream release 4.4
6473
 * wp.media.view.Attachment.Library
6474
 *
6475
 * @memberOf wp.media.view.Attachment
1.1.4 by Paul Gear
new upstream release 4.2
6476
 *
6477
 * @class
6478
 * @augments wp.media.view.Attachment
6479
 * @augments wp.media.View
6480
 * @augments wp.Backbone.View
6481
 * @augments Backbone.View
6482
 */
1.1.30 by Barry Price
new upstream release 4.4
6483
var Library = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Library.prototype */{
6484
	buttons: {
6485
		check: true
1.1.4 by Paul Gear
new upstream release 4.2
6486
	}
6487
});
6488
1.1.30 by Barry Price
new upstream release 4.4
6489
module.exports = Library;
6490
6491
6492
/***/ }),
6493
/* 71 */
6494
/***/ (function(module, exports) {
6495
1.1.4 by Paul Gear
new upstream release 4.2
6496
/**
6497
 * wp.media.view.Attachment.EditLibrary
6498
 *
1.1.30 by Barry Price
new upstream release 4.4
6499
 * @memberOf wp.media.view.Attachment
6500
 *
1.1.4 by Paul Gear
new upstream release 4.2
6501
 * @class
6502
 * @augments wp.media.view.Attachment
6503
 * @augments wp.media.View
6504
 * @augments wp.Backbone.View
6505
 * @augments Backbone.View
6506
 */
1.1.30 by Barry Price
new upstream release 4.4
6507
var EditLibrary = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.EditLibrary.prototype */{
1.1.4 by Paul Gear
new upstream release 4.2
6508
	buttons: {
6509
		close: true
6510
	}
6511
});
6512
6513
module.exports = EditLibrary;
6514
1.1.30 by Barry Price
new upstream release 4.4
6515
6516
/***/ }),
6517
/* 72 */
6518
/***/ (function(module, exports) {
6519
6520
var View = wp.media.View,
6521
	$ = jQuery,
6522
	Attachments;
6523
1.1.4 by Paul Gear
new upstream release 4.2
6524
/**
6525
 * wp.media.view.Attachments
6526
 *
1.1.30 by Barry Price
new upstream release 4.4
6527
 * @memberOf wp.media.view
6528
 *
1.1.4 by Paul Gear
new upstream release 4.2
6529
 * @class
6530
 * @augments wp.media.View
6531
 * @augments wp.Backbone.View
6532
 * @augments Backbone.View
6533
 */
1.1.30 by Barry Price
new upstream release 4.4
6534
Attachments = View.extend(/** @lends wp.media.view.Attachments.prototype */{
1.1.4 by Paul Gear
new upstream release 4.2
6535
	tagName:   'ul',
6536
	className: 'attachments',
6537
6538
	attributes: {
6539
		tabIndex: -1
6540
	},
6541
6542
	initialize: function() {
6543
		this.el.id = _.uniqueId('__attachments-view-');
6544
6545
		_.defaults( this.options, {
6546
			refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,
6547
			refreshThreshold:   3,
6548
			AttachmentView:     wp.media.view.Attachment,
6549
			sortable:           false,
6550
			resize:             true,
6551
			idealColumnWidth:   $( window ).width() < 640 ? 135 : 150
6552
		});
6553
6554
		this._viewsByCid = {};
6555
		this.$window = $( window );
6556
		this.resizeEvent = 'resize.media-modal-columns';
6557
6558
		this.collection.on( 'add', function( attachment ) {
6559
			this.views.add( this.createAttachmentView( attachment ), {
6560
				at: this.collection.indexOf( attachment )
6561
			});
6562
		}, this );
6563
6564
		this.collection.on( 'remove', function( attachment ) {
6565
			var view = this._viewsByCid[ attachment.cid ];
6566
			delete this._viewsByCid[ attachment.cid ];
1 by Jacek Nykis
Initial commit
6567
6568
			if ( view ) {
6569
				view.remove();
6570
			}
1.1.4 by Paul Gear
new upstream release 4.2
6571
		}, this );
6572
6573
		this.collection.on( 'reset', this.render, this );
6574
6575
		this.listenTo( this.controller, 'library:selection:add',    this.attachmentFocus );
6576
6577
		// Throttle the scroll handler and bind this.
6578
		this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
6579
6580
		this.options.scrollElement = this.options.scrollElement || this.el;
6581
		$( this.options.scrollElement ).on( 'scroll', this.scroll );
6582
6583
		this.initSortable();
6584
6585
		_.bindAll( this, 'setColumns' );
6586
6587
		if ( this.options.resize ) {
6588
			this.on( 'ready', this.bindEvents );
6589
			this.controller.on( 'open', this.setColumns );
6590
6591
			// Call this.setColumns() after this view has been rendered in the DOM so
6592
			// attachments get proper width applied.
6593
			_.defer( this.setColumns, this );
6594
		}
6595
	},
6596
6597
	bindEvents: function() {
6598
		this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );
6599
	},
6600
6601
	attachmentFocus: function() {
6602
		this.$( 'li:first' ).focus();
6603
	},
6604
6605
	restoreFocus: function() {
6606
		this.$( 'li.selected:first' ).focus();
6607
	},
6608
6609
	arrowEvent: function( event ) {
6610
		var attachments = this.$el.children( 'li' ),
6611
			perRow = this.columns,
6612
			index = attachments.filter( ':focus' ).index(),
6613
			row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow );
6614
6615
		if ( index === -1 ) {
6616
			return;
6617
		}
6618
6619
		// Left arrow
6620
		if ( 37 === event.keyCode ) {
6621
			if ( 0 === index ) {
6622
				return;
6623
			}
6624
			attachments.eq( index - 1 ).focus();
6625
		}
6626
6627
		// Up arrow
6628
		if ( 38 === event.keyCode ) {
6629
			if ( 1 === row ) {
6630
				return;
6631
			}
6632
			attachments.eq( index - perRow ).focus();
6633
		}
6634
6635
		// Right arrow
6636
		if ( 39 === event.keyCode ) {
6637
			if ( attachments.length === index ) {
6638
				return;
6639
			}
6640
			attachments.eq( index + 1 ).focus();
6641
		}
6642
6643
		// Down arrow
6644
		if ( 40 === event.keyCode ) {
6645
			if ( Math.ceil( attachments.length / perRow ) === row ) {
6646
				return;
6647
			}
6648
			attachments.eq( index + perRow ).focus();
6649
		}
6650
	},
6651
6652
	dispose: function() {
6653
		this.collection.props.off( null, null, this );
6654
		if ( this.options.resize ) {
6655
			this.$window.off( this.resizeEvent );
6656
		}
6657
6658
		/**
6659
		 * call 'dispose' directly on the parent class
6660
		 */
6661
		View.prototype.dispose.apply( this, arguments );
6662
	},
6663
6664
	setColumns: function() {
6665
		var prev = this.columns,
6666
			width = this.$el.width();
6667
6668
		if ( width ) {
6669
			this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1;
6670
6671
			if ( ! prev || prev !== this.columns ) {
6672
				this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns );
6673
			}
6674
		}
6675
	},
6676
6677
	initSortable: function() {
6678
		var collection = this.collection;
6679
1.1.20 by Haw Loeung
New upstream version 4.7.
6680
		if ( ! this.options.sortable || ! $.fn.sortable ) {
1.1.4 by Paul Gear
new upstream release 4.2
6681
			return;
6682
		}
6683
6684
		this.$el.sortable( _.extend({
1 by Jacek Nykis
Initial commit
6685
			// If the `collection` has a `comparator`, disable sorting.
1.1.4 by Paul Gear
new upstream release 4.2
6686
			disabled: !! collection.comparator,
6687
6688
			// Change the position of the attachment as soon as the
6689
			// mouse pointer overlaps a thumbnail.
6690
			tolerance: 'pointer',
6691
6692
			// Record the initial `index` of the dragged model.
6693
			start: function( event, ui ) {
6694
				ui.item.data('sortableIndexStart', ui.item.index());
6695
			},
6696
6697
			// Update the model's index in the collection.
6698
			// Do so silently, as the view is already accurate.
6699
			update: function( event, ui ) {
6700
				var model = collection.at( ui.item.data('sortableIndexStart') ),
6701
					comparator = collection.comparator;
6702
6703
				// Temporarily disable the comparator to prevent `add`
6704
				// from re-sorting.
6705
				delete collection.comparator;
6706
6707
				// Silently shift the model to its new index.
6708
				collection.remove( model, {
6709
					silent: true
6710
				});
6711
				collection.add( model, {
6712
					silent: true,
6713
					at:     ui.item.index()
6714
				});
6715
6716
				// Restore the comparator.
6717
				collection.comparator = comparator;
6718
6719
				// Fire the `reset` event to ensure other collections sync.
6720
				collection.trigger( 'reset', collection );
6721
6722
				// If the collection is sorted by menu order,
6723
				// update the menu order.
6724
				collection.saveMenuOrder();
6725
			}
6726
		}, this.options.sortable ) );
6727
6728
		// If the `orderby` property is changed on the `collection`,
6729
		// check to see if we have a `comparator`. If so, disable sorting.
6730
		collection.props.on( 'change:orderby', function() {
6731
			this.$el.sortable( 'option', 'disabled', !! collection.comparator );
6732
		}, this );
6733
6734
		this.collection.props.on( 'change:orderby', this.refreshSortable, this );
6735
		this.refreshSortable();
6736
	},
6737
6738
	refreshSortable: function() {
1.1.20 by Haw Loeung
New upstream version 4.7.
6739
		if ( ! this.options.sortable || ! $.fn.sortable ) {
1.1.4 by Paul Gear
new upstream release 4.2
6740
			return;
6741
		}
6742
6743
		// If the `collection` has a `comparator`, disable sorting.
6744
		var collection = this.collection,
6745
			orderby = collection.props.get('orderby'),
6746
			enabled = 'menuOrder' === orderby || ! collection.comparator;
6747
6748
		this.$el.sortable( 'option', 'disabled', ! enabled );
6749
	},
6750
6751
	/**
6752
	 * @param {wp.media.model.Attachment} attachment
6753
	 * @returns {wp.media.View}
6754
	 */
6755
	createAttachmentView: function( attachment ) {
6756
		var view = new this.options.AttachmentView({
6757
			controller:           this.controller,
6758
			model:                attachment,
6759
			collection:           this.collection,
6760
			selection:            this.options.selection
6761
		});
6762
6763
		return this._viewsByCid[ attachment.cid ] = view;
6764
	},
6765
6766
	prepare: function() {
6767
		// Create all of the Attachment views, and replace
6768
		// the list in a single DOM operation.
6769
		if ( this.collection.length ) {
6770
			this.views.set( this.collection.map( this.createAttachmentView, this ) );
6771
6772
		// If there are no elements, clear the views and load some.
6773
		} else {
6774
			this.views.unset();
6775
			this.collection.more().done( this.scroll );
6776
		}
6777
	},
6778
6779
	ready: function() {
6780
		// Trigger the scroll event to check if we're within the
6781
		// threshold to query for additional attachments.
6782
		this.scroll();
6783
	},
6784
6785
	scroll: function() {
6786
		var view = this,
6787
			el = this.options.scrollElement,
6788
			scrollTop = el.scrollTop,
6789
			toolbar;
6790
6791
		// The scroll event occurs on the document, but the element
6792
		// that should be checked is the document body.
6793
		if ( el === document ) {
6794
			el = document.body;
6795
			scrollTop = $(document).scrollTop();
6796
		}
6797
6798
		if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) {
6799
			return;
6800
		}
6801
6802
		toolbar = this.views.parent.toolbar;
6803
6804
		// Show the spinner only if we are close to the bottom.
6805
		if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) {
6806
			toolbar.get('spinner').show();
6807
		}
6808
6809
		if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) {
6810
			this.collection.more().done(function() {
6811
				view.scroll();
6812
				toolbar.get('spinner').hide();
6813
			});
6814
		}
6815
	}
6816
});
6817
6818
module.exports = Attachments;
6819
1.1.30 by Barry Price
new upstream release 4.4
6820
6821
/***/ }),
6822
/* 73 */
6823
/***/ (function(module, exports) {
6824
6825
var l10n = wp.media.view.l10n,
6826
	Search;
6827
6828
/**
6829
 * wp.media.view.Search
6830
 *
6831
 * @memberOf wp.media.view
6832
 *
6833
 * @class
6834
 * @augments wp.media.View
6835
 * @augments wp.Backbone.View
6836
 * @augments Backbone.View
6837
 */
6838
Search = wp.media.View.extend(/** @lends wp.media.view.Search.prototype */{
6839
	tagName:   'input',
6840
	className: 'search',
6841
	id:        'media-search-input',
6842
6843
	attributes: {
6844
		type:        'search',
6845
		placeholder: l10n.searchMediaPlaceholder
6846
	},
6847
6848
	events: {
6849
		'input':  'search',
6850
		'keyup':  'search'
6851
	},
6852
6853
	/**
6854
	 * @returns {wp.media.view.Search} Returns itself to allow chaining
6855
	 */
6856
	render: function() {
6857
		this.el.value = this.model.escape('search');
6858
		return this;
6859
	},
6860
6861
	search: _.debounce( function( event ) {
6862
		if ( event.target.value ) {
6863
			this.model.set( 'search', event.target.value );
6864
		} else {
6865
			this.model.unset('search');
6866
		}
6867
	}, 300 )
6868
});
6869
6870
module.exports = Search;
6871
6872
6873
/***/ }),
6874
/* 74 */
6875
/***/ (function(module, exports) {
6876
6877
var $ = jQuery,
6878
	AttachmentFilters;
6879
6880
/**
6881
 * wp.media.view.AttachmentFilters
6882
 *
6883
 * @memberOf wp.media.view
6884
 *
6885
 * @class
6886
 * @augments wp.media.View
6887
 * @augments wp.Backbone.View
6888
 * @augments Backbone.View
6889
 */
6890
AttachmentFilters = wp.media.View.extend(/** @lends wp.media.view.AttachmentFilters.prototype */{
6891
	tagName:   'select',
6892
	className: 'attachment-filters',
6893
	id:        'media-attachment-filters',
6894
6895
	events: {
6896
		change: 'change'
6897
	},
6898
6899
	keys: [],
6900
6901
	initialize: function() {
6902
		this.createFilters();
6903
		_.extend( this.filters, this.options.filters );
6904
6905
		// Build `<option>` elements.
6906
		this.$el.html( _.chain( this.filters ).map( function( filter, value ) {
6907
			return {
6908
				el: $( '<option></option>' ).val( value ).html( filter.text )[0],
6909
				priority: filter.priority || 50
6910
			};
6911
		}, this ).sortBy('priority').pluck('el').value() );
6912
6913
		this.listenTo( this.model, 'change', this.select );
6914
		this.select();
6915
	},
6916
6917
	/**
6918
	 * @abstract
6919
	 */
6920
	createFilters: function() {
6921
		this.filters = {};
6922
	},
6923
6924
	/**
6925
	 * When the selected filter changes, update the Attachment Query properties to match.
6926
	 */
6927
	change: function() {
6928
		var filter = this.filters[ this.el.value ];
6929
		if ( filter ) {
6930
			this.model.set( filter.props );
6931
		}
6932
	},
6933
6934
	select: function() {
6935
		var model = this.model,
6936
			value = 'all',
6937
			props = model.toJSON();
6938
6939
		_.find( this.filters, function( filter, id ) {
6940
			var equal = _.all( filter.props, function( prop, key ) {
6941
				return prop === ( _.isUndefined( props[ key ] ) ? null : props[ key ] );
6942
			});
6943
6944
			if ( equal ) {
6945
				return value = id;
6946
			}
6947
		});
6948
6949
		this.$el.val( value );
6950
	}
6951
});
6952
6953
module.exports = AttachmentFilters;
6954
6955
6956
/***/ }),
6957
/* 75 */
6958
/***/ (function(module, exports) {
6959
6960
var l10n = wp.media.view.l10n,
6961
	DateFilter;
6962
6963
/**
6964
 * A filter dropdown for month/dates.
6965
 *
6966
 * @memberOf wp.media.view.AttachmentFilters
6967
 *
6968
 * @class
6969
 * @augments wp.media.view.AttachmentFilters
6970
 * @augments wp.media.View
6971
 * @augments wp.Backbone.View
6972
 * @augments Backbone.View
6973
 */
6974
DateFilter = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.Date.prototype */{
6975
	id: 'media-attachment-date-filters',
6976
6977
	createFilters: function() {
6978
		var filters = {};
6979
		_.each( wp.media.view.settings.months || {}, function( value, index ) {
6980
			filters[ index ] = {
6981
				text: value.text,
6982
				props: {
6983
					year: value.year,
6984
					monthnum: value.month
6985
				}
6986
			};
6987
		});
6988
		filters.all = {
6989
			text:  l10n.allDates,
6990
			props: {
6991
				monthnum: false,
6992
				year:  false
6993
			},
6994
			priority: 10
6995
		};
6996
		this.filters = filters;
6997
	}
6998
});
6999
7000
module.exports = DateFilter;
7001
7002
7003
/***/ }),
7004
/* 76 */
7005
/***/ (function(module, exports) {
7006
7007
var l10n = wp.media.view.l10n,
7008
	Uploaded;
7009
7010
/**
7011
 * wp.media.view.AttachmentFilters.Uploaded
7012
 *
7013
 * @memberOf wp.media.view.AttachmentFilters
7014
 *
7015
 * @class
7016
 * @augments wp.media.view.AttachmentFilters
7017
 * @augments wp.media.View
7018
 * @augments wp.Backbone.View
7019
 * @augments Backbone.View
7020
 */
7021
Uploaded = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.Uploaded.prototype */{
7022
	createFilters: function() {
7023
		var type = this.model.get('type'),
7024
			types = wp.media.view.settings.mimeTypes,
7025
			text;
7026
7027
		if ( types && type ) {
7028
			text = types[ type ];
7029
		}
7030
7031
		this.filters = {
7032
			all: {
7033
				text:  text || l10n.allMediaItems,
7034
				props: {
7035
					uploadedTo: null,
7036
					orderby: 'date',
7037
					order:   'DESC'
7038
				},
7039
				priority: 10
7040
			},
7041
7042
			uploaded: {
7043
				text:  l10n.uploadedToThisPost,
7044
				props: {
7045
					uploadedTo: wp.media.view.settings.post.id,
7046
					orderby: 'menuOrder',
7047
					order:   'ASC'
7048
				},
7049
				priority: 20
7050
			},
7051
7052
			unattached: {
7053
				text:  l10n.unattached,
7054
				props: {
7055
					uploadedTo: 0,
7056
					orderby: 'menuOrder',
7057
					order:   'ASC'
7058
				},
7059
				priority: 50
7060
			}
7061
		};
7062
	}
7063
});
7064
7065
module.exports = Uploaded;
7066
7067
7068
/***/ }),
7069
/* 77 */
7070
/***/ (function(module, exports) {
7071
7072
var l10n = wp.media.view.l10n,
7073
	All;
7074
7075
/**
7076
 * wp.media.view.AttachmentFilters.All
7077
 *
7078
 * @memberOf wp.media.view.AttachmentFilters
7079
 *
7080
 * @class
7081
 * @augments wp.media.view.AttachmentFilters
7082
 * @augments wp.media.View
7083
 * @augments wp.Backbone.View
7084
 * @augments Backbone.View
7085
 */
7086
All = wp.media.view.AttachmentFilters.extend(/** @lends wp.media.view.AttachmentFilters.All.prototype */{
7087
	createFilters: function() {
7088
		var filters = {};
7089
7090
		_.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) {
7091
			filters[ key ] = {
7092
				text: text,
7093
				props: {
7094
					status:  null,
7095
					type:    key,
7096
					uploadedTo: null,
7097
					orderby: 'date',
7098
					order:   'DESC'
7099
				}
7100
			};
7101
		});
7102
7103
		filters.all = {
7104
			text:  l10n.allMediaItems,
7105
			props: {
7106
				status:  null,
7107
				type:    null,
7108
				uploadedTo: null,
7109
				orderby: 'date',
7110
				order:   'DESC'
7111
			},
7112
			priority: 10
7113
		};
7114
7115
		if ( wp.media.view.settings.post.id ) {
7116
			filters.uploaded = {
7117
				text:  l10n.uploadedToThisPost,
7118
				props: {
7119
					status:  null,
7120
					type:    null,
7121
					uploadedTo: wp.media.view.settings.post.id,
7122
					orderby: 'menuOrder',
7123
					order:   'ASC'
7124
				},
7125
				priority: 20
7126
			};
7127
		}
7128
7129
		filters.unattached = {
7130
			text:  l10n.unattached,
7131
			props: {
7132
				status:     null,
7133
				uploadedTo: 0,
7134
				type:       null,
7135
				orderby:    'menuOrder',
7136
				order:      'ASC'
7137
			},
7138
			priority: 50
7139
		};
7140
7141
		if ( wp.media.view.settings.mediaTrash &&
7142
			this.controller.isModeActive( 'grid' ) ) {
7143
7144
			filters.trash = {
7145
				text:  l10n.trash,
7146
				props: {
7147
					uploadedTo: null,
7148
					status:     'trash',
7149
					type:       null,
7150
					orderby:    'date',
7151
					order:      'DESC'
7152
				},
7153
				priority: 50
7154
			};
7155
		}
7156
7157
		this.filters = filters;
7158
	}
7159
});
7160
7161
module.exports = All;
7162
7163
7164
/***/ }),
7165
/* 78 */
7166
/***/ (function(module, exports) {
7167
7168
var View = wp.media.View,
7169
	mediaTrash = wp.media.view.settings.mediaTrash,
7170
	l10n = wp.media.view.l10n,
7171
	$ = jQuery,
7172
	AttachmentsBrowser;
7173
1.1.4 by Paul Gear
new upstream release 4.2
7174
/**
7175
 * wp.media.view.AttachmentsBrowser
7176
 *
1.1.30 by Barry Price
new upstream release 4.4
7177
 * @memberOf wp.media.view
7178
 *
1.1.4 by Paul Gear
new upstream release 4.2
7179
 * @class
7180
 * @augments wp.media.View
7181
 * @augments wp.Backbone.View
7182
 * @augments Backbone.View
7183
 *
7184
 * @param {object}         [options]               The options hash passed to the view.
7185
 * @param {boolean|string} [options.filters=false] Which filters to show in the browser's toolbar.
7186
 *                                                 Accepts 'uploaded' and 'all'.
7187
 * @param {boolean}        [options.search=true]   Whether to show the search interface in the
7188
 *                                                 browser's toolbar.
7189
 * @param {boolean}        [options.date=true]     Whether to show the date filter in the
7190
 *                                                 browser's toolbar.
7191
 * @param {boolean}        [options.display=false] Whether to show the attachments display settings
7192
 *                                                 view in the sidebar.
7193
 * @param {boolean|string} [options.sidebar=true]  Whether to create a sidebar for the browser.
7194
 *                                                 Accepts true, false, and 'errors'.
7195
 */
1.1.30 by Barry Price
new upstream release 4.4
7196
AttachmentsBrowser = View.extend(/** @lends wp.media.view.AttachmentsBrowser.prototype */{
1.1.4 by Paul Gear
new upstream release 4.2
7197
	tagName:   'div',
7198
	className: 'attachments-browser',
7199
7200
	initialize: function() {
7201
		_.defaults( this.options, {
7202
			filters: false,
7203
			search:  true,
7204
			date:    true,
7205
			display: false,
7206
			sidebar: true,
7207
			AttachmentView: wp.media.view.Attachment.Library
7208
		});
7209
1.1.14 by Nick Moffitt
new upstream release 4.5
7210
		this.controller.on( 'toggle:upload:attachment', this.toggleUploader, this );
1.1.4 by Paul Gear
new upstream release 4.2
7211
		this.controller.on( 'edit:selection', this.editSelection );
1.1.26 by Barry Price
new upstream release 4.8
7212
1.1.23 by axino
new upstream release 4.7.3
7213
		// In the Media Library, the sidebar is used to display errors before the attachments grid.
7214
		if ( this.options.sidebar && 'errors' === this.options.sidebar ) {
7215
			this.createSidebar();
7216
		}
1.1.26 by Barry Price
new upstream release 4.8
7217
7218
		/*
7219
		 * For accessibility reasons, place the Inline Uploader before other sections.
7220
		 * This way, in the Media Library, it's right after the Add New button, see ticket #37188.
7221
		 */
1.1.18 by Barry Price
new upstream release 4.6
7222
		this.createUploader();
1.1.26 by Barry Price
new upstream release 4.8
7223
7224
		/*
7225
		 * Create a multi-purpose toolbar. Used as main toolbar in the Media Library
7226
		 * and also for other things, for example the "Drag and drop to reorder" and
7227
		 * "Suggested dimensions" info in the media modal.
7228
		 */
7229
		this.createToolbar();
7230
7231
		// Create the list of attachments.
1.1.18 by Barry Price
new upstream release 4.6
7232
		this.createAttachments();
1.1.26 by Barry Price
new upstream release 4.8
7233
1.1.23 by axino
new upstream release 4.7.3
7234
		// For accessibility reasons, place the normal sidebar after the attachments, see ticket #36909.
7235
		if ( this.options.sidebar && 'errors' !== this.options.sidebar ) {
1.1.4 by Paul Gear
new upstream release 4.2
7236
			this.createSidebar();
7237
		}
1.1.26 by Barry Price
new upstream release 4.8
7238
1.1.4 by Paul Gear
new upstream release 4.2
7239
		this.updateContent();
7240
7241
		if ( ! this.options.sidebar || 'errors' === this.options.sidebar ) {
7242
			this.$el.addClass( 'hide-sidebar' );
7243
7244
			if ( 'errors' === this.options.sidebar ) {
7245
				this.$el.addClass( 'sidebar-for-errors' );
7246
			}
7247
		}
7248
7249
		this.collection.on( 'add remove reset', this.updateContent, this );
7250
	},
7251
7252
	editSelection: function( modal ) {
7253
		modal.$( '.media-button-backToLibrary' ).focus();
7254
	},
7255
7256
	/**
7257
	 * @returns {wp.media.view.AttachmentsBrowser} Returns itself to allow chaining
7258
	 */
7259
	dispose: function() {
7260
		this.options.selection.off( null, null, this );
7261
		View.prototype.dispose.apply( this, arguments );
7262
		return this;
7263
	},
7264
7265
	createToolbar: function() {
7266
		var LibraryViewSwitcher, Filters, toolbarOptions;
7267
7268
		toolbarOptions = {
7269
			controller: this.controller
7270
		};
7271
7272
		if ( this.controller.isModeActive( 'grid' ) ) {
7273
			toolbarOptions.className = 'media-toolbar wp-filter';
7274
		}
7275
7276
		/**
7277
		* @member {wp.media.view.Toolbar}
7278
		*/
7279
		this.toolbar = new wp.media.view.Toolbar( toolbarOptions );
7280
7281
		this.views.add( this.toolbar );
7282
7283
		this.toolbar.set( 'spinner', new wp.media.view.Spinner({
7284
			priority: -60
7285
		}) );
7286
7287
		if ( -1 !== $.inArray( this.options.filters, [ 'uploaded', 'all' ] ) ) {
7288
			// "Filters" will return a <select>, need to render
7289
			// screen reader text before
7290
			this.toolbar.set( 'filtersLabel', new wp.media.view.Label({
7291
				value: l10n.filterByType,
7292
				attributes: {
7293
					'for':  'media-attachment-filters'
7294
				},
7295
				priority:   -80
7296
			}).render() );
7297
7298
			if ( 'uploaded' === this.options.filters ) {
7299
				this.toolbar.set( 'filters', new wp.media.view.AttachmentFilters.Uploaded({
7300
					controller: this.controller,
7301
					model:      this.collection.props,
7302
					priority:   -80
7303
				}).render() );
7304
			} else {
7305
				Filters = new wp.media.view.AttachmentFilters.All({
7306
					controller: this.controller,
7307
					model:      this.collection.props,
7308
					priority:   -80
7309
				});
7310
7311
				this.toolbar.set( 'filters', Filters.render() );
7312
			}
7313
		}
7314
7315
		// Feels odd to bring the global media library switcher into the Attachment
7316
		// browser view. Is this a use case for doAction( 'add:toolbar-items:attachments-browser', this.toolbar );
7317
		// which the controller can tap into and add this view?
7318
		if ( this.controller.isModeActive( 'grid' ) ) {
7319
			LibraryViewSwitcher = View.extend({
7320
				className: 'view-switch media-grid-view-switch',
7321
				template: wp.template( 'media-library-view-switcher')
7322
			});
7323
7324
			this.toolbar.set( 'libraryViewSwitcher', new LibraryViewSwitcher({
7325
				controller: this.controller,
7326
				priority: -90
7327
			}).render() );
7328
7329
			// DateFilter is a <select>, screen reader text needs to be rendered before
7330
			this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
7331
				value: l10n.filterByDate,
7332
				attributes: {
7333
					'for': 'media-attachment-date-filters'
7334
				},
7335
				priority: -75
7336
			}).render() );
7337
			this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({
7338
				controller: this.controller,
7339
				model:      this.collection.props,
7340
				priority: -75
7341
			}).render() );
7342
7343
			// BulkSelection is a <div> with subviews, including screen reader text
7344
			this.toolbar.set( 'selectModeToggleButton', new wp.media.view.SelectModeToggleButton({
7345
				text: l10n.bulkSelect,
7346
				controller: this.controller,
7347
				priority: -70
7348
			}).render() );
7349
7350
			this.toolbar.set( 'deleteSelectedButton', new wp.media.view.DeleteSelectedButton({
7351
				filters: Filters,
7352
				style: 'primary',
7353
				disabled: true,
7354
				text: mediaTrash ? l10n.trashSelected : l10n.deleteSelected,
7355
				controller: this.controller,
7356
				priority: -60,
7357
				click: function() {
7358
					var changed = [], removed = [],
7359
						selection = this.controller.state().get( 'selection' ),
7360
						library = this.controller.state().get( 'library' );
7361
7362
					if ( ! selection.length ) {
7363
						return;
7364
					}
7365
7366
					if ( ! mediaTrash && ! window.confirm( l10n.warnBulkDelete ) ) {
7367
						return;
7368
					}
7369
7370
					if ( mediaTrash &&
7371
						'trash' !== selection.at( 0 ).get( 'status' ) &&
7372
						! window.confirm( l10n.warnBulkTrash ) ) {
7373
7374
						return;
7375
					}
7376
7377
					selection.each( function( model ) {
7378
						if ( ! model.get( 'nonces' )['delete'] ) {
7379
							removed.push( model );
7380
							return;
7381
						}
7382
7383
						if ( mediaTrash && 'trash' === model.get( 'status' ) ) {
7384
							model.set( 'status', 'inherit' );
7385
							changed.push( model.save() );
7386
							removed.push( model );
7387
						} else if ( mediaTrash ) {
7388
							model.set( 'status', 'trash' );
7389
							changed.push( model.save() );
7390
							removed.push( model );
7391
						} else {
7392
							model.destroy({wait: true});
7393
						}
7394
					} );
7395
7396
					if ( changed.length ) {
7397
						selection.remove( removed );
7398
7399
						$.when.apply( null, changed ).then( _.bind( function() {
7400
							library._requery( true );
7401
							this.controller.trigger( 'selection:action:done' );
7402
						}, this ) );
7403
					} else {
7404
						this.controller.trigger( 'selection:action:done' );
7405
					}
7406
				}
7407
			}).render() );
7408
7409
			if ( mediaTrash ) {
7410
				this.toolbar.set( 'deleteSelectedPermanentlyButton', new wp.media.view.DeleteSelectedPermanentlyButton({
1 by Jacek Nykis
Initial commit
7411
					filters: Filters,
7412
					style: 'primary',
7413
					disabled: true,
1.1.4 by Paul Gear
new upstream release 4.2
7414
					text: l10n.deleteSelected,
1 by Jacek Nykis
Initial commit
7415
					controller: this.controller,
1.1.4 by Paul Gear
new upstream release 4.2
7416
					priority: -55,
1 by Jacek Nykis
Initial commit
7417
					click: function() {
1.1.26 by Barry Price
new upstream release 4.8
7418
						var removed = [],
7419
							destroy = [],
7420
							selection = this.controller.state().get( 'selection' );
1.1.4 by Paul Gear
new upstream release 4.2
7421
7422
						if ( ! selection.length || ! window.confirm( l10n.warnBulkDelete ) ) {
1 by Jacek Nykis
Initial commit
7423
							return;
7424
						}
7425
7426
						selection.each( function( model ) {
7427
							if ( ! model.get( 'nonces' )['delete'] ) {
7428
								removed.push( model );
7429
								return;
7430
							}
7431
1.1.26 by Barry Price
new upstream release 4.8
7432
							destroy.push( model );
1 by Jacek Nykis
Initial commit
7433
						} );
7434
1.1.26 by Barry Price
new upstream release 4.8
7435
						if ( removed.length ) {
7436
							selection.remove( removed );
7437
						}
7438
7439
						if ( destroy.length ) {
7440
							$.when.apply( null, destroy.map( function (item) {
7441
								return item.destroy();
7442
							} ) ).then( _.bind( function() {
7443
								this.controller.trigger( 'selection:action:done' );
7444
							}, this ) );
7445
						}
1 by Jacek Nykis
Initial commit
7446
					}
7447
				}).render() );
1.1.4 by Paul Gear
new upstream release 4.2
7448
			}
7449
7450
		} else if ( this.options.date ) {
7451
			// DateFilter is a <select>, screen reader text needs to be rendered before
7452
			this.toolbar.set( 'dateFilterLabel', new wp.media.view.Label({
7453
				value: l10n.filterByDate,
7454
				attributes: {
7455
					'for': 'media-attachment-date-filters'
7456
				},
7457
				priority: -75
7458
			}).render() );
7459
			this.toolbar.set( 'dateFilter', new wp.media.view.DateFilter({
7460
				controller: this.controller,
7461
				model:      this.collection.props,
7462
				priority: -75
7463
			}).render() );
7464
		}
7465
7466
		if ( this.options.search ) {
7467
			// Search is an input, screen reader text needs to be rendered before
7468
			this.toolbar.set( 'searchLabel', new wp.media.view.Label({
7469
				value: l10n.searchMediaLabel,
7470
				attributes: {
7471
					'for': 'media-search-input'
7472
				},
7473
				priority:   60
7474
			}).render() );
7475
			this.toolbar.set( 'search', new wp.media.view.Search({
7476
				controller: this.controller,
7477
				model:      this.collection.props,
7478
				priority:   60
7479
			}).render() );
7480
		}
7481
7482
		if ( this.options.dragInfo ) {
7483
			this.toolbar.set( 'dragInfo', new View({
7484
				el: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[0],
7485
				priority: -40
7486
			}) );
7487
		}
7488
7489
		if ( this.options.suggestedWidth && this.options.suggestedHeight ) {
7490
			this.toolbar.set( 'suggestedDimensions', new View({
1.1.26 by Barry Price
new upstream release 4.8
7491
				el: $( '<div class="instructions">' + l10n.suggestedDimensions.replace( '%1$s', this.options.suggestedWidth ).replace( '%2$s', this.options.suggestedHeight ) + '</div>' )[0],
1.1.4 by Paul Gear
new upstream release 4.2
7492
				priority: -40
7493
			}) );
7494
		}
7495
	},
7496
7497
	updateContent: function() {
7498
		var view = this,
7499
			noItemsView;
7500
7501
		if ( this.controller.isModeActive( 'grid' ) ) {
7502
			noItemsView = view.attachmentsNoResults;
7503
		} else {
7504
			noItemsView = view.uploader;
7505
		}
7506
7507
		if ( ! this.collection.length ) {
7508
			this.toolbar.get( 'spinner' ).show();
7509
			this.dfd = this.collection.more().done( function() {
7510
				if ( ! view.collection.length ) {
7511
					noItemsView.$el.removeClass( 'hidden' );
7512
				} else {
7513
					noItemsView.$el.addClass( 'hidden' );
1.1.1 by Nick Moffitt
New Upstream Version 4.1
7514
				}
1 by Jacek Nykis
Initial commit
7515
				view.toolbar.get( 'spinner' ).hide();
1.1.4 by Paul Gear
new upstream release 4.2
7516
			} );
7517
		} else {
7518
			noItemsView.$el.addClass( 'hidden' );
7519
			view.toolbar.get( 'spinner' ).hide();
7520
		}
7521
	},
7522
7523
	createUploader: function() {
7524
		this.uploader = new wp.media.view.UploaderInline({
7525
			controller: this.controller,
7526
			status:     false,
7527
			message:    this.controller.isModeActive( 'grid' ) ? '' : l10n.noItemsFound,
7528
			canClose:   this.controller.isModeActive( 'grid' )
7529
		});
7530
1.1.26 by Barry Price
new upstream release 4.8
7531
		this.uploader.$el.addClass( 'hidden' );
1.1.4 by Paul Gear
new upstream release 4.2
7532
		this.views.add( this.uploader );
7533
	},
7534
7535
	toggleUploader: function() {
7536
		if ( this.uploader.$el.hasClass( 'hidden' ) ) {
7537
			this.uploader.show();
7538
		} else {
1 by Jacek Nykis
Initial commit
7539
			this.uploader.hide();
1.1.4 by Paul Gear
new upstream release 4.2
7540
		}
7541
	},
7542
7543
	createAttachments: function() {
7544
		this.attachments = new wp.media.view.Attachments({
7545
			controller:           this.controller,
7546
			collection:           this.collection,
7547
			selection:            this.options.selection,
7548
			model:                this.model,
7549
			sortable:             this.options.sortable,
7550
			scrollElement:        this.options.scrollElement,
7551
			idealColumnWidth:     this.options.idealColumnWidth,
7552
7553
			// The single `Attachment` view to be used in the `Attachments` view.
7554
			AttachmentView: this.options.AttachmentView
7555
		});
7556
7557
		// Add keydown listener to the instance of the Attachments view
1.1.17 by Barry Price
new upstream release 4.5.3
7558
		this.controller.on( 'attachment:keydown:arrow',     _.bind( this.attachments.arrowEvent, this.attachments ) );
7559
		this.controller.on( 'attachment:details:shift-tab', _.bind( this.attachments.restoreFocus, this.attachments ) );
1.1.4 by Paul Gear
new upstream release 4.2
7560
7561
		this.views.add( this.attachments );
7562
7563
7564
		if ( this.controller.isModeActive( 'grid' ) ) {
7565
			this.attachmentsNoResults = new View({
7566
				controller: this.controller,
7567
				tagName: 'p'
7568
			});
7569
7570
			this.attachmentsNoResults.$el.addClass( 'hidden no-media' );
7571
			this.attachmentsNoResults.$el.html( l10n.noMedia );
7572
7573
			this.views.add( this.attachmentsNoResults );
7574
		}
7575
	},
7576
7577
	createSidebar: function() {
7578
		var options = this.options,
7579
			selection = options.selection,
7580
			sidebar = this.sidebar = new wp.media.view.Sidebar({
7581
				controller: this.controller
7582
			});
7583
7584
		this.views.add( sidebar );
7585
7586
		if ( this.controller.uploader ) {
7587
			sidebar.set( 'uploads', new wp.media.view.UploaderStatus({
7588
				controller: this.controller,
7589
				priority:   40
7590
			}) );
7591
		}
7592
7593
		selection.on( 'selection:single', this.createSingle, this );
7594
		selection.on( 'selection:unsingle', this.disposeSingle, this );
7595
7596
		if ( selection.single() ) {
7597
			this.createSingle();
7598
		}
7599
	},
7600
7601
	createSingle: function() {
7602
		var sidebar = this.sidebar,
7603
			single = this.options.selection.single();
7604
7605
		sidebar.set( 'details', new wp.media.view.Attachment.Details({
7606
			controller: this.controller,
7607
			model:      single,
7608
			priority:   80
7609
		}) );
7610
7611
		sidebar.set( 'compat', new wp.media.view.AttachmentCompat({
7612
			controller: this.controller,
7613
			model:      single,
7614
			priority:   120
7615
		}) );
7616
7617
		if ( this.options.display ) {
7618
			sidebar.set( 'display', new wp.media.view.Settings.AttachmentDisplay({
7619
				controller:   this.controller,
7620
				model:        this.model.display( single ),
7621
				attachment:   single,
7622
				priority:     160,
7623
				userSettings: this.model.get('displayUserSettings')
7624
			}) );
7625
		}
7626
7627
		// Show the sidebar on mobile
7628
		if ( this.model.id === 'insert' ) {
7629
			sidebar.$el.addClass( 'visible' );
7630
		}
7631
	},
7632
7633
	disposeSingle: function() {
7634
		var sidebar = this.sidebar;
7635
		sidebar.unset('details');
7636
		sidebar.unset('compat');
7637
		sidebar.unset('display');
7638
		// Hide the sidebar on mobile
7639
		sidebar.$el.removeClass( 'visible' );
7640
	}
7641
});
7642
7643
module.exports = AttachmentsBrowser;
7644
1.1.30 by Barry Price
new upstream release 4.4
7645
7646
/***/ }),
7647
/* 79 */
7648
/***/ (function(module, exports) {
7649
7650
var l10n = wp.media.view.l10n,
7651
	Selection;
7652
7653
/**
7654
 * wp.media.view.Selection
7655
 *
7656
 * @memberOf wp.media.view
7657
 *
7658
 * @class
7659
 * @augments wp.media.View
7660
 * @augments wp.Backbone.View
7661
 * @augments Backbone.View
7662
 */
7663
Selection = wp.media.View.extend(/** @lends wp.media.view.Selection.prototype */{
7664
	tagName:   'div',
7665
	className: 'media-selection',
7666
	template:  wp.template('media-selection'),
7667
7668
	events: {
7669
		'click .edit-selection':  'edit',
7670
		'click .clear-selection': 'clear'
7671
	},
7672
7673
	initialize: function() {
7674
		_.defaults( this.options, {
7675
			editable:  false,
7676
			clearable: true
7677
		});
7678
7679
		/**
7680
		 * @member {wp.media.view.Attachments.Selection}
7681
		 */
7682
		this.attachments = new wp.media.view.Attachments.Selection({
7683
			controller: this.controller,
7684
			collection: this.collection,
7685
			selection:  this.collection,
7686
			model:      new Backbone.Model()
7687
		});
7688
7689
		this.views.set( '.selection-view', this.attachments );
7690
		this.collection.on( 'add remove reset', this.refresh, this );
7691
		this.controller.on( 'content:activate', this.refresh, this );
7692
	},
7693
7694
	ready: function() {
7695
		this.refresh();
7696
	},
7697
7698
	refresh: function() {
7699
		// If the selection hasn't been rendered, bail.
7700
		if ( ! this.$el.children().length ) {
7701
			return;
7702
		}
7703
7704
		var collection = this.collection,
7705
			editing = 'edit-selection' === this.controller.content.mode();
7706
7707
		// If nothing is selected, display nothing.
7708
		this.$el.toggleClass( 'empty', ! collection.length );
7709
		this.$el.toggleClass( 'one', 1 === collection.length );
7710
		this.$el.toggleClass( 'editing', editing );
7711
7712
		this.$('.count').text( l10n.selected.replace('%d', collection.length) );
7713
	},
7714
7715
	edit: function( event ) {
7716
		event.preventDefault();
7717
		if ( this.options.editable ) {
7718
			this.options.editable.call( this, this.collection );
7719
		}
7720
	},
7721
7722
	clear: function( event ) {
7723
		event.preventDefault();
7724
		this.collection.reset();
7725
7726
		// Keep focus inside media modal
7727
		// after clear link is selected
7728
		this.controller.modal.focusManager.focus();
7729
	}
7730
});
7731
7732
module.exports = Selection;
7733
7734
7735
/***/ }),
7736
/* 80 */
7737
/***/ (function(module, exports) {
7738
7739
/**
7740
 * wp.media.view.Attachment.Selection
7741
 *
7742
 * @memberOf wp.media.view.Attachment
7743
 *
7744
 * @class
7745
 * @augments wp.media.view.Attachment
7746
 * @augments wp.media.View
7747
 * @augments wp.Backbone.View
7748
 * @augments Backbone.View
7749
 */
7750
var Selection = wp.media.view.Attachment.extend(/** @lends wp.media.view.Attachment.Selection.prototype */{
7751
	className: 'attachment selection',
7752
7753
	// On click, just select the model, instead of removing the model from
7754
	// the selection.
7755
	toggleSelection: function() {
7756
		this.options.selection.single( this.model );
7757
	}
7758
});
7759
7760
module.exports = Selection;
7761
7762
7763
/***/ }),
7764
/* 81 */
7765
/***/ (function(module, exports) {
7766
7767
var Attachments = wp.media.view.Attachments,
7768
	Selection;
7769
1.1.4 by Paul Gear
new upstream release 4.2
7770
/**
7771
 * wp.media.view.Attachments.Selection
7772
 *
1.1.30 by Barry Price
new upstream release 4.4
7773
 * @memberOf wp.media.view.Attachments
7774
 *
1.1.4 by Paul Gear
new upstream release 4.2
7775
 * @class
7776
 * @augments wp.media.view.Attachments
7777
 * @augments wp.media.View
7778
 * @augments wp.Backbone.View
7779
 * @augments Backbone.View
7780
 */
1.1.30 by Barry Price
new upstream release 4.4
7781
Selection = Attachments.extend(/** @lends wp.media.view.Attachments.Selection.prototype */{
1.1.4 by Paul Gear
new upstream release 4.2
7782
	events: {},
7783
	initialize: function() {
7784
		_.defaults( this.options, {
7785
			sortable:   false,
7786
			resize:     false,
7787
7788
			// The single `Attachment` view to be used in the `Attachments` view.
7789
			AttachmentView: wp.media.view.Attachment.Selection
7790
		});
7791
		// Call 'initialize' directly on the parent class.
7792
		return Attachments.prototype.initialize.apply( this, arguments );
7793
	}
7794
});
7795
7796
module.exports = Selection;
7797
1.1.30 by Barry Price
new upstream release 4.4
7798
7799
/***/ }),
7800
/* 82 */
7801
/***/ (function(module, exports) {
7802
7803
/**
7804
 * wp.media.view.Attachment.EditSelection
7805
 *
7806
 * @memberOf wp.media.view.Attachment
7807
 *
7808
 * @class
7809
 * @augments wp.media.view.Attachment.Selection
7810
 * @augments wp.media.view.Attachment
7811
 * @augments wp.media.View
7812
 * @augments wp.Backbone.View
7813
 * @augments Backbone.View
7814
 */
7815
var EditSelection = wp.media.view.Attachment.Selection.extend(/** @lends wp.media.view.Attachment.EditSelection.prototype */{
7816
	buttons: {
7817
		close: true
7818
	}
7819
});
7820
7821
module.exports = EditSelection;
7822
7823
7824
/***/ }),
7825
/* 83 */
7826
/***/ (function(module, exports) {
7827
7828
var View = wp.media.View,
7829
	$ = Backbone.$,
7830
	Settings;
7831
7832
/**
7833
 * wp.media.view.Settings
7834
 *
7835
 * @memberOf wp.media.view
7836
 *
7837
 * @class
7838
 * @augments wp.media.View
7839
 * @augments wp.Backbone.View
7840
 * @augments Backbone.View
7841
 */
7842
Settings = View.extend(/** @lends wp.media.view.Settings.prototype */{
7843
	events: {
7844
		'click button':    'updateHandler',
7845
		'change input':    'updateHandler',
7846
		'change select':   'updateHandler',
7847
		'change textarea': 'updateHandler'
7848
	},
1.1.4 by Paul Gear
new upstream release 4.2
7849
7850
	initialize: function() {
1.1.30 by Barry Price
new upstream release 4.4
7851
		this.model = this.model || new Backbone.Model();
7852
		this.listenTo( this.model, 'change', this.updateChanges );
7853
	},
7854
7855
	prepare: function() {
7856
		return _.defaults({
7857
			model: this.model.toJSON()
7858
		}, this.options );
7859
	},
7860
	/**
7861
	 * @returns {wp.media.view.Settings} Returns itself to allow chaining
7862
	 */
7863
	render: function() {
7864
		View.prototype.render.apply( this, arguments );
7865
		// Select the correct values.
7866
		_( this.model.attributes ).chain().keys().each( this.update, this );
7867
		return this;
7868
	},
7869
	/**
7870
	 * @param {string} key
7871
	 */
7872
	update: function( key ) {
7873
		var value = this.model.get( key ),
7874
			$setting = this.$('[data-setting="' + key + '"]'),
7875
			$buttons, $value;
7876
7877
		// Bail if we didn't find a matching setting.
7878
		if ( ! $setting.length ) {
7879
			return;
7880
		}
7881
7882
		// Attempt to determine how the setting is rendered and update
7883
		// the selected value.
7884
7885
		// Handle dropdowns.
7886
		if ( $setting.is('select') ) {
7887
			$value = $setting.find('[value="' + value + '"]');
7888
7889
			if ( $value.length ) {
7890
				$setting.find('option').prop( 'selected', false );
7891
				$value.prop( 'selected', true );
1.1.4 by Paul Gear
new upstream release 4.2
7892
			} else {
1.1.30 by Barry Price
new upstream release 4.4
7893
				// If we can't find the desired value, record what *is* selected.
7894
				this.model.set( key, $setting.find(':selected').val() );
7895
			}
7896
7897
		// Handle button groups.
7898
		} else if ( $setting.hasClass('button-group') ) {
7899
			$buttons = $setting.find('button').removeClass('active');
7900
			$buttons.filter( '[value="' + value + '"]' ).addClass('active');
7901
7902
		// Handle text inputs and textareas.
7903
		} else if ( $setting.is('input[type="text"], textarea') ) {
7904
			if ( ! $setting.is(':focus') ) {
7905
				$setting.val( value );
7906
			}
7907
		// Handle checkboxes.
7908
		} else if ( $setting.is('input[type="checkbox"]') ) {
7909
			$setting.prop( 'checked', !! value && 'false' !== value );
7910
		}
7911
	},
7912
	/**
7913
	 * @param {Object} event
7914
	 */
7915
	updateHandler: function( event ) {
7916
		var $setting = $( event.target ).closest('[data-setting]'),
7917
			value = event.target.value,
7918
			userSetting;
7919
7920
		event.preventDefault();
7921
7922
		if ( ! $setting.length ) {
7923
			return;
7924
		}
7925
7926
		// Use the correct value for checkboxes.
7927
		if ( $setting.is('input[type="checkbox"]') ) {
7928
			value = $setting[0].checked;
7929
		}
7930
7931
		// Update the corresponding setting.
7932
		this.model.set( $setting.data('setting'), value );
7933
7934
		// If the setting has a corresponding user setting,
7935
		// update that as well.
7936
		if ( userSetting = $setting.data('userSetting') ) {
7937
			window.setUserSetting( userSetting, value );
7938
		}
7939
	},
7940
7941
	updateChanges: function( model ) {
7942
		if ( model.hasChanged() ) {
7943
			_( model.changed ).chain().keys().each( this.update, this );
7944
		}
7945
	}
7946
});
7947
7948
module.exports = Settings;
7949
7950
7951
/***/ }),
7952
/* 84 */
7953
/***/ (function(module, exports) {
7954
7955
var Settings = wp.media.view.Settings,
7956
	AttachmentDisplay;
7957
7958
/**
7959
 * wp.media.view.Settings.AttachmentDisplay
7960
 *
7961
 * @memberOf wp.media.view.Settings
7962
 *
7963
 * @class
7964
 * @augments wp.media.view.Settings
7965
 * @augments wp.media.View
7966
 * @augments wp.Backbone.View
7967
 * @augments Backbone.View
7968
 */
7969
AttachmentDisplay = Settings.extend(/** @lends wp.media.view.Settings.AttachmentDisplay.prototype */{
7970
	className: 'attachment-display-settings',
7971
	template:  wp.template('attachment-display-settings'),
7972
7973
	initialize: function() {
7974
		var attachment = this.options.attachment;
7975
7976
		_.defaults( this.options, {
7977
			userSettings: false
1.1.4 by Paul Gear
new upstream release 4.2
7978
		});
1.1.30 by Barry Price
new upstream release 4.4
7979
		// Call 'initialize' directly on the parent class.
7980
		Settings.prototype.initialize.apply( this, arguments );
7981
		this.listenTo( this.model, 'change:link', this.updateLinkTo );
7982
7983
		if ( attachment ) {
7984
			attachment.on( 'change:uploading', this.render, this );
7985
		}
7986
	},
7987
7988
	dispose: function() {
7989
		var attachment = this.options.attachment;
7990
		if ( attachment ) {
7991
			attachment.off( null, null, this );
7992
		}
7993
		/**
7994
		 * call 'dispose' directly on the parent class
7995
		 */
7996
		Settings.prototype.dispose.apply( this, arguments );
7997
	},
1.1.4 by Paul Gear
new upstream release 4.2
7998
	/**
1.1.30 by Barry Price
new upstream release 4.4
7999
	 * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining
1.1.4 by Paul Gear
new upstream release 4.2
8000
	 */
8001
	render: function() {
1.1.30 by Barry Price
new upstream release 4.4
8002
		var attachment = this.options.attachment;
8003
		if ( attachment ) {
8004
			_.extend( this.options, {
8005
				sizes: attachment.get('sizes'),
8006
				type:  attachment.get('type')
8007
			});
8008
		}
8009
		/**
8010
		 * call 'render' directly on the parent class
8011
		 */
8012
		Settings.prototype.render.call( this );
8013
		this.updateLinkTo();
1.1.4 by Paul Gear
new upstream release 4.2
8014
		return this;
1.1.30 by Barry Price
new upstream release 4.4
8015
	},
8016
8017
	updateLinkTo: function() {
8018
		var linkTo = this.model.get('link'),
8019
			$input = this.$('.link-to-custom'),
8020
			attachment = this.options.attachment;
8021
8022
		if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) {
8023
			$input.addClass( 'hidden' );
8024
			return;
8025
		}
8026
8027
		if ( attachment ) {
8028
			if ( 'post' === linkTo ) {
8029
				$input.val( attachment.get('link') );
8030
			} else if ( 'file' === linkTo ) {
8031
				$input.val( attachment.get('url') );
8032
			} else if ( ! this.model.get('linkUrl') ) {
8033
				$input.val('http://');
1.1.4 by Paul Gear
new upstream release 4.2
8034
			}
8035
1.1.30 by Barry Price
new upstream release 4.4
8036
			$input.prop( 'readonly', 'custom' !== linkTo );
8037
		}
8038
8039
		$input.removeClass( 'hidden' );
8040
8041
		// If the input is visible, focus and select its contents.
8042
		if ( ! wp.media.isTouchDevice && $input.is(':visible') ) {
8043
			$input.focus()[0].select();
1.1.4 by Paul Gear
new upstream release 4.2
8044
		}
8045
	}
8046
});
8047
1.1.30 by Barry Price
new upstream release 4.4
8048
module.exports = AttachmentDisplay;
8049
8050
8051
/***/ }),
8052
/* 85 */
8053
/***/ (function(module, exports) {
8054
8055
/**
8056
 * wp.media.view.Settings.Gallery
8057
 *
8058
 * @memberOf wp.media.view.Settings
8059
 *
8060
 * @class
8061
 * @augments wp.media.view.Settings
8062
 * @augments wp.media.View
8063
 * @augments wp.Backbone.View
8064
 * @augments Backbone.View
8065
 */
8066
var Gallery = wp.media.view.Settings.extend(/** @lends wp.media.view.Settings.Gallery.prototype */{
8067
	className: 'collection-settings gallery-settings',
8068
	template:  wp.template('gallery-settings')
8069
});
8070
8071
module.exports = Gallery;
8072
8073
8074
/***/ }),
8075
/* 86 */
8076
/***/ (function(module, exports) {
8077
8078
/**
8079
 * wp.media.view.Settings.Playlist
8080
 *
8081
 * @memberOf wp.media.view.Settings
8082
 *
8083
 * @class
8084
 * @augments wp.media.view.Settings
8085
 * @augments wp.media.View
8086
 * @augments wp.Backbone.View
8087
 * @augments Backbone.View
8088
 */
8089
var Playlist = wp.media.view.Settings.extend(/** @lends wp.media.view.Settings.Playlist.prototype */{
8090
	className: 'collection-settings playlist-settings',
8091
	template:  wp.template('playlist-settings')
8092
});
8093
8094
module.exports = Playlist;
8095
8096
8097
/***/ }),
8098
/* 87 */
8099
/***/ (function(module, exports) {
8100
8101
var Attachment = wp.media.view.Attachment,
1.1.4 by Paul Gear
new upstream release 4.2
8102
	l10n = wp.media.view.l10n,
1.1.30 by Barry Price
new upstream release 4.4
8103
	Details;
8104
8105
/**
8106
 * wp.media.view.Attachment.Details
8107
 *
8108
 * @memberOf wp.media.view.Attachment
8109
 *
8110
 * @class
8111
 * @augments wp.media.view.Attachment
8112
 * @augments wp.media.View
8113
 * @augments wp.Backbone.View
8114
 * @augments Backbone.View
8115
 */
8116
Details = Attachment.extend(/** @lends wp.media.view.Attachment.Details.prototype */{
8117
	tagName:   'div',
8118
	className: 'attachment-details',
8119
	template:  wp.template('attachment-details'),
8120
8121
	attributes: function() {
1.1.4 by Paul Gear
new upstream release 4.2
8122
		return {
1.1.30 by Barry Price
new upstream release 4.4
8123
			'tabIndex':     0,
8124
			'data-id':      this.model.get( 'id' )
1.1.4 by Paul Gear
new upstream release 4.2
8125
		};
8126
	},
1.1.30 by Barry Price
new upstream release 4.4
8127
8128
	events: {
8129
		'change [data-setting]':          'updateSetting',
8130
		'change [data-setting] input':    'updateSetting',
8131
		'change [data-setting] select':   'updateSetting',
8132
		'change [data-setting] textarea': 'updateSetting',
8133
		'click .delete-attachment':       'deleteAttachment',
8134
		'click .trash-attachment':        'trashAttachment',
8135
		'click .untrash-attachment':      'untrashAttachment',
8136
		'click .edit-attachment':         'editAttachment',
8137
		'keydown':                        'toggleSelectionHandler'
8138
	},
8139
8140
	initialize: function() {
8141
		this.options = _.defaults( this.options, {
8142
			rerenderOnModelChange: false
8143
		});
8144
8145
		this.on( 'ready', this.initialFocus );
8146
		// Call 'initialize' directly on the parent class.
8147
		Attachment.prototype.initialize.apply( this, arguments );
8148
	},
8149
8150
	initialFocus: function() {
8151
		if ( ! wp.media.isTouchDevice ) {
8152
			/*
8153
			Previously focused the first ':input' (the readonly URL text field).
8154
			Since the first ':input' is now a button (delete/trash): when pressing
8155
			spacebar on an attachment, Firefox fires deleteAttachment/trashAttachment
8156
			as soon as focus is moved. Explicitly target the first text field for now.
8157
			@todo change initial focus logic, also for accessibility.
8158
			*/
8159
			this.$( 'input[type="text"]' ).eq( 0 ).focus();
8160
		}
8161
	},
8162
	/**
8163
	 * @param {Object} event
8164
	 */
8165
	deleteAttachment: function( event ) {
8166
		event.preventDefault();
8167
8168
		if ( window.confirm( l10n.warnDelete ) ) {
8169
			this.model.destroy();
8170
			// Keep focus inside media modal
8171
			// after image is deleted
8172
			this.controller.modal.focusManager.focus();
8173
		}
8174
	},
8175
	/**
8176
	 * @param {Object} event
8177
	 */
8178
	trashAttachment: function( event ) {
8179
		var library = this.controller.library;
8180
		event.preventDefault();
8181
8182
		if ( wp.media.view.settings.mediaTrash &&
8183
			'edit-metadata' === this.controller.content.mode() ) {
8184
8185
			this.model.set( 'status', 'trash' );
8186
			this.model.save().done( function() {
8187
				library._requery( true );
8188
			} );
8189
		}  else {
8190
			this.model.destroy();
8191
		}
8192
	},
8193
	/**
8194
	 * @param {Object} event
8195
	 */
8196
	untrashAttachment: function( event ) {
8197
		var library = this.controller.library;
8198
		event.preventDefault();
8199
8200
		this.model.set( 'status', 'inherit' );
8201
		this.model.save().done( function() {
8202
			library._requery( true );
8203
		} );
8204
	},
8205
	/**
8206
	 * @param {Object} event
8207
	 */
8208
	editAttachment: function( event ) {
8209
		var editState = this.controller.states.get( 'edit-image' );
8210
		if ( window.imageEdit && editState ) {
8211
			event.preventDefault();
8212
8213
			editState.set( 'image', this.model );
8214
			this.controller.setState( 'edit-image' );
8215
		} else {
8216
			this.$el.addClass('needs-refresh');
8217
		}
8218
	},
8219
	/**
8220
	 * When reverse tabbing(shift+tab) out of the right details panel, deliver
8221
	 * the focus to the item in the list that was being edited.
8222
	 *
8223
	 * @param {Object} event
8224
	 */
8225
	toggleSelectionHandler: function( event ) {
8226
		if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) {
8227
			this.controller.trigger( 'attachment:details:shift-tab', event );
8228
			return false;
8229
		}
8230
8231
		if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) {
8232
			this.controller.trigger( 'attachment:keydown:arrow', event );
8233
			return;
8234
		}
1.1.4 by Paul Gear
new upstream release 4.2
8235
	}
8236
});
8237
1.1.30 by Barry Price
new upstream release 4.4
8238
module.exports = Details;
8239
8240
8241
/***/ }),
8242
/* 88 */
8243
/***/ (function(module, exports) {
8244
1.1.4 by Paul Gear
new upstream release 4.2
8245
var View = wp.media.View,
1.1.30 by Barry Price
new upstream release 4.4
8246
	AttachmentCompat;
8247
8248
/**
8249
 * wp.media.view.AttachmentCompat
8250
 *
8251
 * A view to display fields added via the `attachment_fields_to_edit` filter.
8252
 *
8253
 * @memberOf wp.media.view
8254
 *
8255
 * @class
8256
 * @augments wp.media.View
8257
 * @augments wp.Backbone.View
8258
 * @augments Backbone.View
8259
 */
8260
AttachmentCompat = View.extend(/** @lends wp.media.view.AttachmentCompat.prototype */{
8261
	tagName:   'form',
8262
	className: 'compat-item',
8263
8264
	events: {
8265
		'submit':          'preventDefault',
8266
		'change input':    'save',
8267
		'change select':   'save',
8268
		'change textarea': 'save'
8269
	},
8270
8271
	initialize: function() {
8272
		this.listenTo( this.model, 'change:compat', this.render );
8273
	},
8274
	/**
8275
	 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
8276
	 */
8277
	dispose: function() {
8278
		if ( this.$(':focus').length ) {
8279
			this.save();
8280
		}
8281
		/**
8282
		 * call 'dispose' directly on the parent class
8283
		 */
8284
		return View.prototype.dispose.apply( this, arguments );
8285
	},
8286
	/**
8287
	 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining
8288
	 */
8289
	render: function() {
8290
		var compat = this.model.get('compat');
8291
		if ( ! compat || ! compat.item ) {
8292
			return;
8293
		}
8294
8295
		this.views.detach();
8296
		this.$el.html( compat.item );
8297
		this.views.render();
8298
		return this;
8299
	},
8300
	/**
8301
	 * @param {Object} event
8302
	 */
8303
	preventDefault: function( event ) {
8304
		event.preventDefault();
8305
	},
8306
	/**
8307
	 * @param {Object} event
8308
	 */
8309
	save: function( event ) {
8310
		var data = {};
8311
8312
		if ( event ) {
8313
			event.preventDefault();
8314
		}
8315
8316
		_.each( this.$el.serializeArray(), function( pair ) {
8317
			data[ pair.name ] = pair.value;
8318
		});
8319
8320
		this.controller.trigger( 'attachment:compat:waiting', ['waiting'] );
8321
		this.model.saveCompat( data ).always( _.bind( this.postSave, this ) );
8322
	},
8323
8324
	postSave: function() {
8325
		this.controller.trigger( 'attachment:compat:ready', ['ready'] );
8326
	}
8327
});
8328
8329
module.exports = AttachmentCompat;
8330
8331
8332
/***/ }),
8333
/* 89 */
8334
/***/ (function(module, exports) {
8335
8336
/**
8337
 * wp.media.view.Iframe
8338
 *
8339
 * @memberOf wp.media.view
8340
 *
8341
 * @class
8342
 * @augments wp.media.View
8343
 * @augments wp.Backbone.View
8344
 * @augments Backbone.View
8345
 */
8346
var Iframe = wp.media.View.extend(/** @lends wp.media.view.Iframe.prototype */{
8347
	className: 'media-iframe',
8348
	/**
8349
	 * @returns {wp.media.view.Iframe} Returns itself to allow chaining
8350
	 */
8351
	render: function() {
8352
		this.views.detach();
8353
		this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' );
8354
		this.views.render();
8355
		return this;
8356
	}
8357
});
8358
8359
module.exports = Iframe;
8360
8361
8362
/***/ }),
8363
/* 90 */
8364
/***/ (function(module, exports) {
8365
1.1.4 by Paul Gear
new upstream release 4.2
8366
/**
8367
 * wp.media.view.Embed
8368
 *
1.1.30 by Barry Price
new upstream release 4.4
8369
 * @memberOf wp.media.view
8370
 *
1.1.4 by Paul Gear
new upstream release 4.2
8371
 * @class
8372
 * @augments wp.media.View
8373
 * @augments wp.Backbone.View
8374
 * @augments Backbone.View
8375
 */
1.1.30 by Barry Price
new upstream release 4.4
8376
var Embed = wp.media.View.extend(/** @lends wp.media.view.Ember.prototype */{
1.1.4 by Paul Gear
new upstream release 4.2
8377
	className: 'media-embed',
8378
8379
	initialize: function() {
8380
		/**
8381
		 * @member {wp.media.view.EmbedUrl}
8382
		 */
8383
		this.url = new wp.media.view.EmbedUrl({
8384
			controller: this.controller,
8385
			model:      this.model.props
8386
		}).render();
8387
8388
		this.views.set([ this.url ]);
8389
		this.refresh();
8390
		this.listenTo( this.model, 'change:type', this.refresh );
8391
		this.listenTo( this.model, 'change:loading', this.loading );
8392
	},
8393
8394
	/**
8395
	 * @param {Object} view
8396
	 */
8397
	settings: function( view ) {
8398
		if ( this._settings ) {
8399
			this._settings.remove();
8400
		}
8401
		this._settings = view;
8402
		this.views.add( view );
8403
	},
8404
8405
	refresh: function() {
8406
		var type = this.model.get('type'),
8407
			constructor;
8408
8409
		if ( 'image' === type ) {
8410
			constructor = wp.media.view.EmbedImage;
8411
		} else if ( 'link' === type ) {
8412
			constructor = wp.media.view.EmbedLink;
8413
		} else {
8414
			return;
8415
		}
8416
8417
		this.settings( new constructor({
8418
			controller: this.controller,
8419
			model:      this.model.props,
8420
			priority:   40
8421
		}) );
8422
	},
8423
8424
	loading: function() {
8425
		this.$el.toggleClass( 'embed-loading', this.model.get('loading') );
8426
	}
8427
});
8428
8429
module.exports = Embed;
8430
1.1.30 by Barry Price
new upstream release 4.4
8431
8432
/***/ }),
8433
/* 91 */
8434
/***/ (function(module, exports) {
8435
8436
/**
8437
 * wp.media.view.Label
8438
 *
8439
 * @memberOf wp.media.view
8440
 *
8441
 * @class
8442
 * @augments wp.media.View
8443
 * @augments wp.Backbone.View
8444
 * @augments Backbone.View
8445
 */
8446
var Label = wp.media.View.extend(/** @lends wp.media.view.Label.prototype */{
8447
	tagName: 'label',
8448
	className: 'screen-reader-text',
8449
8450
	initialize: function() {
8451
		this.value = this.options.value;
8452
	},
8453
8454
	render: function() {
8455
		this.$el.html( this.value );
8456
8457
		return this;
8458
	}
8459
});
8460
8461
module.exports = Label;
8462
8463
8464
/***/ }),
8465
/* 92 */
8466
/***/ (function(module, exports) {
8467
8468
var View = wp.media.View,
8469
	$ = jQuery,
8470
	EmbedUrl;
8471
8472
/**
8473
 * wp.media.view.EmbedUrl
8474
 *
8475
 * @memberOf wp.media.view
8476
 *
8477
 * @class
8478
 * @augments wp.media.View
8479
 * @augments wp.Backbone.View
8480
 * @augments Backbone.View
8481
 */
8482
EmbedUrl = View.extend(/** @lends wp.media.view.EmbedUrl.prototype */{
8483
	tagName:   'label',
8484
	className: 'embed-url',
8485
8486
	events: {
8487
		'input':  'url',
8488
		'keyup':  'url',
8489
		'change': 'url'
8490
	},
8491
8492
	initialize: function() {
8493
		this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') );
8494
		this.input = this.$input[0];
8495
8496
		this.spinner = $('<span class="spinner" />')[0];
8497
		this.$el.append([ this.input, this.spinner ]);
8498
8499
		this.listenTo( this.model, 'change:url', this.render );
8500
8501
		if ( this.model.get( 'url' ) ) {
8502
			_.delay( _.bind( function () {
8503
				this.model.trigger( 'change:url' );
8504
			}, this ), 500 );
8505
		}
8506
	},
8507
	/**
8508
	 * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining
8509
	 */
8510
	render: function() {
8511
		var $input = this.$input;
8512
8513
		if ( $input.is(':focus') ) {
8514
			return;
8515
		}
8516
8517
		this.input.value = this.model.get('url') || 'http://';
1.1.4 by Paul Gear
new upstream release 4.2
8518
		/**
1.1.30 by Barry Price
new upstream release 4.4
8519
		 * Call `render` directly on parent class with passed arguments
1.1.4 by Paul Gear
new upstream release 4.2
8520
		 */
1.1.30 by Barry Price
new upstream release 4.4
8521
		View.prototype.render.apply( this, arguments );
8522
		return this;
8523
	},
8524
8525
	ready: function() {
8526
		if ( ! wp.media.isTouchDevice ) {
8527
			this.focus();
8528
		}
8529
	},
8530
8531
	url: function( event ) {
8532
		this.model.set( 'url', $.trim( event.target.value ) );
8533
	},
8534
8535
	/**
8536
	 * If the input is visible, focus and select its contents.
8537
	 */
8538
	focus: function() {
8539
		var $input = this.$input;
8540
		if ( $input.is(':visible') ) {
8541
			$input.focus()[0].select();
8542
		}
1.1.4 by Paul Gear
new upstream release 4.2
8543
	}
8544
});
8545
1.1.30 by Barry Price
new upstream release 4.4
8546
module.exports = EmbedUrl;
8547
8548
8549
/***/ }),
8550
/* 93 */
8551
/***/ (function(module, exports) {
8552
8553
var $ = jQuery,
8554
	EmbedLink;
8555
1.1.4 by Paul Gear
new upstream release 4.2
8556
/**
8557
 * wp.media.view.EmbedLink
8558
 *
1.1.30 by Barry Price
new upstream release 4.4
8559
 * @memberOf wp.media.view
8560
 *
1.1.4 by Paul Gear
new upstream release 4.2
8561
 * @class
8562
 * @augments wp.media.view.Settings
8563
 * @augments wp.media.View
8564
 * @augments wp.Backbone.View
8565
 * @augments Backbone.View
8566
 */
1.1.30 by Barry Price
new upstream release 4.4
8567
EmbedLink = wp.media.view.Settings.extend(/** @lends wp.media.view.EmbedLink.prototype */{
1.1.4 by Paul Gear
new upstream release 4.2
8568
	className: 'embed-link-settings',
8569
	template:  wp.template('embed-link-settings'),
8570
8571
	initialize: function() {
8572
		this.listenTo( this.model, 'change:url', this.updateoEmbed );
8573
	},
8574
8575
	updateoEmbed: _.debounce( function() {
8576
		var url = this.model.get( 'url' );
8577
8578
		// clear out previous results
8579
		this.$('.embed-container').hide().find('.embed-preview').empty();
8580
		this.$( '.setting' ).hide();
8581
1.1.9 by Ryan Finnie
new upstream release 4.3
8582
		// only proceed with embed if the field contains more than 11 characters
8583
		// Example: http://a.io is 11 chars
8584
		if ( url && ( url.length < 11 || ! url.match(/^http(s)?:\/\//) ) ) {
1.1.4 by Paul Gear
new upstream release 4.2
8585
			return;
8586
		}
8587
8588
		this.fetch();
1.1.9 by Ryan Finnie
new upstream release 4.3
8589
	}, wp.media.controller.Embed.sensitivity ),
1.1.4 by Paul Gear
new upstream release 4.2
8590
8591
	fetch: function() {
1.1.30 by Barry Price
new upstream release 4.4
8592
		var url = this.model.get( 'url' ), re, youTubeEmbedMatch;
1.1.9 by Ryan Finnie
new upstream release 4.3
8593
1.1.4 by Paul Gear
new upstream release 4.2
8594
		// check if they haven't typed in 500 ms
1.1.30 by Barry Price
new upstream release 4.4
8595
		if ( $('#embed-url-field').val() !== url ) {
1.1.4 by Paul Gear
new upstream release 4.2
8596
			return;
8597
		}
8598
1.1.9 by Ryan Finnie
new upstream release 4.3
8599
		if ( this.dfd && 'pending' === this.dfd.state() ) {
8600
			this.dfd.abort();
8601
		}
8602
1.1.30 by Barry Price
new upstream release 4.4
8603
		// Support YouTube embed urls, since they work once in the editor.
8604
		re = /https?:\/\/www\.youtube\.com\/embed\/([^/]+)/;
8605
		youTubeEmbedMatch = re.exec( url );
8606
		if ( youTubeEmbedMatch ) {
8607
			url = 'https://www.youtube.com/watch?v=' + youTubeEmbedMatch[ 1 ];
8608
		}
8609
8610
		this.dfd = wp.apiRequest({
1.1.26 by Barry Price
new upstream release 4.8
8611
			url: wp.media.view.settings.oEmbedProxyUrl,
8612
			data: {
1.1.30 by Barry Price
new upstream release 4.4
8613
				url: url,
1.1.26 by Barry Price
new upstream release 4.8
8614
				maxwidth: this.model.get( 'width' ),
1.1.30 by Barry Price
new upstream release 4.4
8615
				maxheight: this.model.get( 'height' )
1.1.26 by Barry Price
new upstream release 4.8
8616
			},
8617
			type: 'GET',
8618
			dataType: 'json',
8619
			context: this
1.1.9 by Ryan Finnie
new upstream release 4.3
8620
		})
1.1.26 by Barry Price
new upstream release 4.8
8621
			.done( function( response ) {
8622
				this.renderoEmbed( {
8623
					data: {
8624
						body: response.html || ''
8625
					}
8626
				} );
8627
			} )
1.1.9 by Ryan Finnie
new upstream release 4.3
8628
			.fail( this.renderFail );
1.1.4 by Paul Gear
new upstream release 4.2
8629
	},
8630
1.1.9 by Ryan Finnie
new upstream release 4.3
8631
	renderFail: function ( response, status ) {
8632
		if ( 'abort' === status ) {
8633
			return;
8634
		}
1.1.4 by Paul Gear
new upstream release 4.2
8635
		this.$( '.link-text' ).show();
8636
	},
8637
8638
	renderoEmbed: function( response ) {
1.1.9 by Ryan Finnie
new upstream release 4.3
8639
		var html = ( response && response.data && response.data.body ) || '';
1.1.4 by Paul Gear
new upstream release 4.2
8640
8641
		if ( html ) {
1 by Jacek Nykis
Initial commit
8642
			this.$('.embed-container').show().find('.embed-preview').html( html );
1.1.4 by Paul Gear
new upstream release 4.2
8643
		} else {
8644
			this.renderFail();
8645
		}
8646
	}
8647
});
8648
8649
module.exports = EmbedLink;
8650
1.1.30 by Barry Price
new upstream release 4.4
8651
8652
/***/ }),
8653
/* 94 */
8654
/***/ (function(module, exports) {
8655
8656
var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
8657
	EmbedImage;
8658
1.1.4 by Paul Gear
new upstream release 4.2
8659
/**
1.1.30 by Barry Price
new upstream release 4.4
8660
 * wp.media.view.EmbedImage
8661
 *
8662
 * @memberOf wp.media.view
1.1.4 by Paul Gear
new upstream release 4.2
8663
 *
8664
 * @class
1.1.30 by Barry Price
new upstream release 4.4
8665
 * @augments wp.media.view.Settings.AttachmentDisplay
8666
 * @augments wp.media.view.Settings
1.1.4 by Paul Gear
new upstream release 4.2
8667
 * @augments wp.media.View
8668
 * @augments wp.Backbone.View
8669
 * @augments Backbone.View
8670
 */
1.1.30 by Barry Price
new upstream release 4.4
8671
EmbedImage = AttachmentDisplay.extend(/** @lends wp.media.view.EmbedImage.prototype */{
8672
	className: 'embed-media-settings',
8673
	template:  wp.template('embed-image-settings'),
8674
8675
	initialize: function() {
8676
		/**
8677
		 * Call `initialize` directly on parent class with passed arguments
8678
		 */
8679
		AttachmentDisplay.prototype.initialize.apply( this, arguments );
8680
		this.listenTo( this.model, 'change:url', this.updateImage );
8681
	},
8682
8683
	updateImage: function() {
8684
		this.$('img').attr( 'src', this.model.get('url') );
8685
	}
8686
});
8687
8688
module.exports = EmbedImage;
8689
8690
8691
/***/ }),
8692
/* 95 */
8693
/***/ (function(module, exports) {
8694
8695
var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay,
1.1.4 by Paul Gear
new upstream release 4.2
8696
	$ = jQuery,
8697
	ImageDetails;
8698
8699
/**
8700
 * wp.media.view.ImageDetails
8701
 *
1.1.30 by Barry Price
new upstream release 4.4
8702
 * @memberOf wp.media.view
8703
 *
1.1.4 by Paul Gear
new upstream release 4.2
8704
 * @class
8705
 * @augments wp.media.view.Settings.AttachmentDisplay
8706
 * @augments wp.media.view.Settings
8707
 * @augments wp.media.View
8708
 * @augments wp.Backbone.View
8709
 * @augments Backbone.View
8710
 */
1.1.30 by Barry Price
new upstream release 4.4
8711
ImageDetails = AttachmentDisplay.extend(/** @lends wp.media.view.ImageDetails.prototype */{
1.1.4 by Paul Gear
new upstream release 4.2
8712
	className: 'image-details',
8713
	template:  wp.template('image-details'),
8714
	events: _.defaults( AttachmentDisplay.prototype.events, {
8715
		'click .edit-attachment': 'editAttachment',
8716
		'click .replace-attachment': 'replaceAttachment',
8717
		'click .advanced-toggle': 'onToggleAdvanced',
8718
		'change [data-setting="customWidth"]': 'onCustomSize',
8719
		'change [data-setting="customHeight"]': 'onCustomSize',
8720
		'keyup [data-setting="customWidth"]': 'onCustomSize',
8721
		'keyup [data-setting="customHeight"]': 'onCustomSize'
8722
	} ),
8723
	initialize: function() {
8724
		// used in AttachmentDisplay.prototype.updateLinkTo
8725
		this.options.attachment = this.model.attachment;
8726
		this.listenTo( this.model, 'change:url', this.updateUrl );
8727
		this.listenTo( this.model, 'change:link', this.toggleLinkSettings );
8728
		this.listenTo( this.model, 'change:size', this.toggleCustomSize );
8729
8730
		AttachmentDisplay.prototype.initialize.apply( this, arguments );
8731
	},
8732
8733
	prepare: function() {
8734
		var attachment = false;
8735
8736
		if ( this.model.attachment ) {
8737
			attachment = this.model.attachment.toJSON();
8738
		}
8739
		return _.defaults({
8740
			model: this.model.toJSON(),
8741
			attachment: attachment
8742
		}, this.options );
8743
	},
8744
8745
	render: function() {
8746
		var args = arguments;
8747
8748
		if ( this.model.attachment && 'pending' === this.model.dfd.state() ) {
8749
			this.model.dfd
8750
				.done( _.bind( function() {
8751
					AttachmentDisplay.prototype.render.apply( this, args );
8752
					this.postRender();
8753
				}, this ) )
8754
				.fail( _.bind( function() {
8755
					this.model.attachment = false;
8756
					AttachmentDisplay.prototype.render.apply( this, args );
8757
					this.postRender();
8758
				}, this ) );
8759
		} else {
8760
			AttachmentDisplay.prototype.render.apply( this, arguments );
8761
			this.postRender();
8762
		}
8763
8764
		return this;
8765
	},
8766
8767
	postRender: function() {
8768
		setTimeout( _.bind( this.resetFocus, this ), 10 );
8769
		this.toggleLinkSettings();
8770
		if ( window.getUserSetting( 'advImgDetails' ) === 'show' ) {
8771
			this.toggleAdvanced( true );
8772
		}
8773
		this.trigger( 'post-render' );
8774
	},
8775
8776
	resetFocus: function() {
8777
		this.$( '.link-to-custom' ).blur();
8778
		this.$( '.embed-media-settings' ).scrollTop( 0 );
8779
	},
8780
8781
	updateUrl: function() {
8782
		this.$( '.image img' ).attr( 'src', this.model.get( 'url' ) );
8783
		this.$( '.url' ).val( this.model.get( 'url' ) );
8784
	},
8785
8786
	toggleLinkSettings: function() {
8787
		if ( this.model.get( 'link' ) === 'none' ) {
8788
			this.$( '.link-settings' ).addClass('hidden');
8789
		} else {
8790
			this.$( '.link-settings' ).removeClass('hidden');
8791
		}
8792
	},
8793
8794
	toggleCustomSize: function() {
8795
		if ( this.model.get( 'size' ) !== 'custom' ) {
8796
			this.$( '.custom-size' ).addClass('hidden');
8797
		} else {
8798
			this.$( '.custom-size' ).removeClass('hidden');
8799
		}
8800
	},
8801
8802
	onCustomSize: function( event ) {
8803
		var dimension = $( event.target ).data('setting'),
8804
			num = $( event.target ).val(),
8805
			value;
8806
8807
		// Ignore bogus input
8808
		if ( ! /^\d+/.test( num ) || parseInt( num, 10 ) < 1 ) {
8809
			event.preventDefault();
8810
			return;
8811
		}
8812
8813
		if ( dimension === 'customWidth' ) {
8814
			value = Math.round( 1 / this.model.get( 'aspectRatio' ) * num );
8815
			this.model.set( 'customHeight', value, { silent: true } );
8816
			this.$( '[data-setting="customHeight"]' ).val( value );
8817
		} else {
8818
			value = Math.round( this.model.get( 'aspectRatio' ) * num );
8819
			this.model.set( 'customWidth', value, { silent: true  } );
8820
			this.$( '[data-setting="customWidth"]' ).val( value );
8821
		}
8822
	},
8823
8824
	onToggleAdvanced: function( event ) {
8825
		event.preventDefault();
8826
		this.toggleAdvanced();
8827
	},
8828
8829
	toggleAdvanced: function( show ) {
8830
		var $advanced = this.$el.find( '.advanced-section' ),
8831
			mode;
8832
8833
		if ( $advanced.hasClass('advanced-visible') || show === false ) {
8834
			$advanced.removeClass('advanced-visible');
8835
			$advanced.find('.advanced-settings').addClass('hidden');
8836
			mode = 'hide';
8837
		} else {
8838
			$advanced.addClass('advanced-visible');
8839
			$advanced.find('.advanced-settings').removeClass('hidden');
8840
			mode = 'show';
8841
		}
8842
8843
		window.setUserSetting( 'advImgDetails', mode );
8844
	},
8845
8846
	editAttachment: function( event ) {
8847
		var editState = this.controller.states.get( 'edit-image' );
8848
8849
		if ( window.imageEdit && editState ) {
8850
			event.preventDefault();
8851
			editState.set( 'image', this.model.attachment );
8852
			this.controller.setState( 'edit-image' );
8853
		}
8854
	},
8855
8856
	replaceAttachment: function( event ) {
8857
		event.preventDefault();
8858
		this.controller.setState( 'replace-image' );
8859
	}
8860
});
8861
8862
module.exports = ImageDetails;
8863
1.1.30 by Barry Price
new upstream release 4.4
8864
8865
/***/ }),
8866
/* 96 */
8867
/***/ (function(module, exports) {
8868
8869
var View = wp.media.View,
8870
	UploaderStatus = wp.media.view.UploaderStatus,
8871
	l10n = wp.media.view.l10n,
1.1.4 by Paul Gear
new upstream release 4.2
8872
	$ = jQuery,
1.1.30 by Barry Price
new upstream release 4.4
8873
	Cropper;
8874
8875
/**
8876
 * wp.media.view.Cropper
8877
 *
8878
 * Uses the imgAreaSelect plugin to allow a user to crop an image.
8879
 *
8880
 * Takes imgAreaSelect options from
8881
 * wp.customize.HeaderControl.calculateImageSelectOptions via
8882
 * wp.customize.HeaderControl.openMM.
8883
 *
8884
 * @memberOf wp.media.view
8885
 *
8886
 * @class
8887
 * @augments wp.media.View
8888
 * @augments wp.Backbone.View
8889
 * @augments Backbone.View
8890
 */
8891
Cropper = View.extend(/** @lends wp.media.view.Cropper.prototype */{
8892
	className: 'crop-content',
8893
	template: wp.template('crop-content'),
1.1.4 by Paul Gear
new upstream release 4.2
8894
	initialize: function() {
1.1.30 by Barry Price
new upstream release 4.4
8895
		_.bindAll(this, 'onImageLoad');
8896
	},
1.1.4 by Paul Gear
new upstream release 4.2
8897
	ready: function() {
1.1.30 by Barry Price
new upstream release 4.4
8898
		this.controller.frame.on('content:error:crop', this.onError, this);
8899
		this.$image = this.$el.find('.crop-image');
8900
		this.$image.on('load', this.onImageLoad);
8901
		$(window).on('resize.cropper', _.debounce(this.onImageLoad, 250));
8902
	},
8903
	remove: function() {
8904
		$(window).off('resize.cropper');
8905
		this.$el.remove();
8906
		this.$el.off();
8907
		View.prototype.remove.apply(this, arguments);
8908
	},
1.1.4 by Paul Gear
new upstream release 4.2
8909
	prepare: function() {
8910
		return {
1.1.30 by Barry Price
new upstream release 4.4
8911
			title: l10n.cropYourImage,
8912
			url: this.options.attachment.get('url')
1.1.4 by Paul Gear
new upstream release 4.2
8913
		};
8914
	},
1.1.30 by Barry Price
new upstream release 4.4
8915
	onImageLoad: function() {
8916
		var imgOptions = this.controller.get('imgSelectOptions'),
8917
			imgSelect;
8918
8919
		if (typeof imgOptions === 'function') {
8920
			imgOptions = imgOptions(this.options.attachment, this.controller);
8921
		}
8922
8923
		imgOptions = _.extend(imgOptions, {
8924
			parent: this.$el,
8925
			onInit: function() {
1.2.3 by Barry Price
new upstream release 4.9.4
8926
8927
				// Store the set ratio.
8928
				var setRatio = imgSelect.getOptions().aspectRatio;
8929
8930
				// On mousedown, if no ratio is set and the Shift key is down, use a 1:1 ratio.
8931
				this.parent.children().on( 'mousedown touchstart', function( e ) {
8932
8933
					// If no ratio is set and the shift key is down, use a 1:1 ratio.
8934
					if ( ! setRatio && e.shiftKey ) {
1.1.30 by Barry Price
new upstream release 4.4
8935
						imgSelect.setOptions( {
8936
							aspectRatio: '1:1'
8937
						} );
8938
					}
8939
				} );
1.2.3 by Barry Price
new upstream release 4.9.4
8940
8941
				this.parent.children().on( 'mouseup touchend', function() {
8942
8943
					// Restore the set ratio.
8944
					imgSelect.setOptions( {
8945
						aspectRatio: setRatio ? setRatio : false
8946
					} );
8947
				} );
1.1.30 by Barry Price
new upstream release 4.4
8948
			}
8949
		} );
8950
		this.trigger('image-loaded');
8951
		imgSelect = this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions);
8952
	},
8953
	onError: function() {
8954
		var filename = this.options.attachment.get('filename');
8955
8956
		this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({
8957
			filename: UploaderStatus.prototype.filename(filename),
8958
			message: window._wpMediaViewsL10n.cropError
8959
		}), { at: 0 });
8960
	}
8961
});
8962
8963
module.exports = Cropper;
8964
8965
8966
/***/ }),
8967
/* 97 */
8968
/***/ (function(module, exports) {
8969
8970
var View = wp.media.view,
8971
	SiteIconCropper;
8972
1.1.9 by Ryan Finnie
new upstream release 4.3
8973
/**
8974
 * wp.media.view.SiteIconCropper
8975
 *
8976
 * Uses the imgAreaSelect plugin to allow a user to crop a Site Icon.
8977
 *
8978
 * Takes imgAreaSelect options from
8979
 * wp.customize.SiteIconControl.calculateImageSelectOptions.
8980
 *
1.1.30 by Barry Price
new upstream release 4.4
8981
 * @memberOf wp.media.view
8982
 *
1.1.9 by Ryan Finnie
new upstream release 4.3
8983
 * @class
8984
 * @augments wp.media.view.Cropper
8985
 * @augments wp.media.View
8986
 * @augments wp.Backbone.View
8987
 * @augments Backbone.View
8988
 */
1.1.30 by Barry Price
new upstream release 4.4
8989
SiteIconCropper = View.Cropper.extend(/** @lends wp.media.view.SiteIconCropper.prototype */{
1.1.9 by Ryan Finnie
new upstream release 4.3
8990
	className: 'crop-content site-icon',
8991
8992
	ready: function () {
8993
		View.Cropper.prototype.ready.apply( this, arguments );
8994
8995
		this.$( '.crop-image' ).on( 'load', _.bind( this.addSidebar, this ) );
8996
	},
8997
8998
	addSidebar: function() {
8999
		this.sidebar = new wp.media.view.Sidebar({
9000
			controller: this.controller
9001
		});
9002
9003
		this.sidebar.set( 'preview', new wp.media.view.SiteIconPreview({
9004
			controller: this.controller,
9005
			attachment: this.options.attachment
9006
		}) );
9007
9008
		this.controller.cropperView.views.add( this.sidebar );
9009
	}
9010
});
9011
9012
module.exports = SiteIconCropper;
9013
1.1.30 by Barry Price
new upstream release 4.4
9014
9015
/***/ }),
9016
/* 98 */
9017
/***/ (function(module, exports) {
9018
9019
var View = wp.media.View,
9020
	$ = jQuery,
9021
	SiteIconPreview;
9022
1.1.9 by Ryan Finnie
new upstream release 4.3
9023
/**
9024
 * wp.media.view.SiteIconPreview
9025
 *
9026
 * Shows a preview of the Site Icon as a favicon and app icon while cropping.
9027
 *
1.1.30 by Barry Price
new upstream release 4.4
9028
 * @memberOf wp.media.view
9029
 *
1.1.9 by Ryan Finnie
new upstream release 4.3
9030
 * @class
9031
 * @augments wp.media.View
9032
 * @augments wp.Backbone.View
9033
 * @augments Backbone.View
9034
 */
1.1.30 by Barry Price
new upstream release 4.4
9035
SiteIconPreview = View.extend(/** @lends wp.media.view.SiteIconPreview.prototype */{
1.1.9 by Ryan Finnie
new upstream release 4.3
9036
	className: 'site-icon-preview',
9037
	template: wp.template( 'site-icon-preview' ),
9038
9039
	ready: function() {
9040
		this.controller.imgSelect.setOptions({
9041
			onInit: this.updatePreview,
9042
			onSelectChange: this.updatePreview
9043
		});
9044
	},
9045
9046
	prepare: function() {
9047
		return {
9048
			url: this.options.attachment.get( 'url' )
9049
		};
9050
	},
9051
9052
	updatePreview: function( img, coords ) {
9053
		var rx = 64 / coords.width,
9054
			ry = 64 / coords.height,
9055
			preview_rx = 16 / coords.width,
9056
			preview_ry = 16 / coords.height;
9057
9058
		$( '#preview-app-icon' ).css({
9059
			width: Math.round(rx * this.imageWidth ) + 'px',
9060
			height: Math.round(ry * this.imageHeight ) + 'px',
9061
			marginLeft: '-' + Math.round(rx * coords.x1) + 'px',
9062
			marginTop: '-' + Math.round(ry * coords.y1) + 'px'
9063
		});
9064
9065
		$( '#preview-favicon' ).css({
9066
			width: Math.round( preview_rx * this.imageWidth ) + 'px',
9067
			height: Math.round( preview_ry * this.imageHeight ) + 'px',
9068
			marginLeft: '-' + Math.round( preview_rx * coords.x1 ) + 'px',
9069
			marginTop: '-' + Math.floor( preview_ry* coords.y1 ) + 'px'
9070
		});
9071
	}
9072
});
9073
9074
module.exports = SiteIconPreview;
9075
1.1.30 by Barry Price
new upstream release 4.4
9076
9077
/***/ }),
9078
/* 99 */
9079
/***/ (function(module, exports) {
9080
9081
var View = wp.media.View,
9082
	EditImage;
9083
9084
/**
9085
 * wp.media.view.EditImage
9086
 *
9087
 * @memberOf wp.media.view
9088
 *
9089
 * @class
9090
 * @augments wp.media.View
9091
 * @augments wp.Backbone.View
9092
 * @augments Backbone.View
9093
 */
9094
EditImage = View.extend(/** @lends wp.media.view.EditImage.prototype */{
9095
	className: 'image-editor',
9096
	template: wp.template('image-editor'),
9097
9098
	initialize: function( options ) {
9099
		this.editor = window.imageEdit;
9100
		this.controller = options.controller;
9101
		View.prototype.initialize.apply( this, arguments );
9102
	},
9103
9104
	prepare: function() {
9105
		return this.model.toJSON();
9106
	},
9107
9108
	loadEditor: function() {
9109
		var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this );
9110
		dfd.done( _.bind( this.focus, this ) );
9111
	},
9112
9113
	focus: function() {
9114
		this.$( '.imgedit-submit .button' ).eq( 0 ).focus();
9115
	},
9116
9117
	back: function() {
9118
		var lastState = this.controller.lastState();
9119
		this.controller.setState( lastState );
9120
	},
9121
9122
	refresh: function() {
9123
		this.model.fetch();
9124
	},
9125
9126
	save: function() {
9127
		var lastState = this.controller.lastState();
9128
9129
		this.model.fetch().done( _.bind( function() {
9130
			this.controller.setState( lastState );
9131
		}, this ) );
9132
	}
9133
9134
});
9135
9136
module.exports = EditImage;
9137
9138
9139
/***/ }),
9140
/* 100 */
9141
/***/ (function(module, exports) {
9142
1.1.4 by Paul Gear
new upstream release 4.2
9143
/**
9144
 * wp.media.view.Spinner
9145
 *
1.1.30 by Barry Price
new upstream release 4.4
9146
 * @memberOf wp.media.view
9147
 *
1.1.4 by Paul Gear
new upstream release 4.2
9148
 * @class
9149
 * @augments wp.media.View
9150
 * @augments wp.Backbone.View
9151
 * @augments Backbone.View
9152
 */
1.1.30 by Barry Price
new upstream release 4.4
9153
var Spinner = wp.media.View.extend(/** @lends wp.media.view.Spinner.prototype */{
1.1.4 by Paul Gear
new upstream release 4.2
9154
	tagName:   'span',
9155
	className: 'spinner',
9156
	spinnerTimeout: false,
9157
	delay: 400,
9158
9159
	show: function() {
9160
		if ( ! this.spinnerTimeout ) {
9161
			this.spinnerTimeout = _.delay(function( $el ) {
9162
				$el.addClass( 'is-active' );
9163
			}, this.delay, this.$el );
9164
		}
9165
9166
		return this;
9167
	},
9168
9169
	hide: function() {
9170
		this.$el.removeClass( 'is-active' );
9171
		this.spinnerTimeout = clearTimeout( this.spinnerTimeout );
9172
9173
		return this;
9174
	}
9175
});
9176
9177
module.exports = Spinner;
9178
1.1.30 by Barry Price
new upstream release 4.4
9179
9180
/***/ })
9181
/******/ ]));