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

Erat himenaeos neque id sagittis massa. Hac suscipit pulvinar dignissim platea magnis eu. Don tellus a pharetra inceptos efficitur dui pulvinar. Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent pulvinar odio volutpat parturient. Quisque risus finibus suspendisse mus purus magnis facilisi condimentum consectetur dui. Curae elit suspendisse cursus vehicula.

Turpis taciti class non vel pretium quis pulvinar tempor lobortis nunc. Libero phasellus parturient sapien volutpat malesuada ornare. Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae. Porta est tempor ex eget feugiat vulputate ipsum. Justo nec iaculis habitant diam arcu fermentum.

We offer comprehen sive emplo ment services such as assistance wit employer compliance.Our company is your strategic HR partner as instead of HR. john smithson

Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae.

Exploring Learning Landscapes in Academic

Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent.

]]>
https://cardgames4educators.com/masters-in-english-how-english-speaker/feed/ 1