Changeset 50019
- Timestamp:
- 01/25/2021 08:15:27 PM (5 years ago)
- File:
-
- 1 edited
-
branches/4.3/src/wp-includes/js/media-views.js (modified) (22 diffs)
Legend:
- Unmodified
- Added
- Removed
-
branches/4.3/src/wp-includes/js/media-views.js
r46499 r50019 1 /******/ (function(modules) { // webpackBootstrap 2 /******/ // The module cache 3 /******/ var installedModules = {}; 4 /******/ 5 /******/ // The require function 6 /******/ function __webpack_require__(moduleId) { 7 /******/ 8 /******/ // Check if module is in cache 9 /******/ if(installedModules[moduleId]) { 10 /******/ return installedModules[moduleId].exports; 11 /******/ } 12 /******/ // Create a new module (and put it into the cache) 13 /******/ var module = installedModules[moduleId] = { 14 /******/ i: moduleId, 15 /******/ l: false, 16 /******/ exports: {} 17 /******/ }; 18 /******/ 19 /******/ // Execute the module function 20 /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 /******/ 22 /******/ // Flag the module as loaded 23 /******/ module.l = true; 24 /******/ 25 /******/ // Return the exports of the module 26 /******/ return module.exports; 27 /******/ } 28 /******/ 29 /******/ 30 /******/ // expose the modules object (__webpack_modules__) 31 /******/ __webpack_require__.m = modules; 32 /******/ 33 /******/ // expose the module cache 34 /******/ __webpack_require__.c = installedModules; 35 /******/ 36 /******/ // define getter function for harmony exports 37 /******/ __webpack_require__.d = function(exports, name, getter) { 38 /******/ if(!__webpack_require__.o(exports, name)) { 39 /******/ Object.defineProperty(exports, name, { 40 /******/ configurable: false, 41 /******/ enumerable: true, 42 /******/ get: getter 43 /******/ }); 44 /******/ } 45 /******/ }; 46 /******/ 47 /******/ // getDefaultExport function for compatibility with non-harmony modules 48 /******/ __webpack_require__.n = function(module) { 49 /******/ var getter = module && module.__esModule ? 50 /******/ function getDefault() { return module['default']; } : 51 /******/ function getModuleExports() { return module; }; 52 /******/ __webpack_require__.d(getter, 'a', getter); 53 /******/ return getter; 54 /******/ }; 55 /******/ 56 /******/ // Object.prototype.hasOwnProperty.call 57 /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 58 /******/ 59 /******/ // __webpack_public_path__ 60 /******/ __webpack_require__.p = ""; 61 /******/ 62 /******/ // Load entry module and return exports 63 /******/ return __webpack_require__(__webpack_require__.s = 26); 64 /******/ }) 65 /************************************************************************/ 66 /******/ (Array(26).concat([ 67 /* 26 */ 68 /***/ (function(module, exports, __webpack_require__) { 69 70 var media = wp.media, 1 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ 2 /** 3 * wp.media.controller.CollectionAdd 4 * 5 * A state for adding attachments to a collection (e.g. video playlist). 6 * 7 * @class 8 * @augments wp.media.controller.Library 9 * @augments wp.media.controller.State 10 * @augments Backbone.Model 11 * 12 * @param {object} [attributes] The attributes hash passed to the state. 13 * @param {string} [attributes.id=library] Unique identifier. 14 * @param {string} attributes.title Title for the state. Displays in the frame's title region. 15 * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. 16 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 17 * If one is not supplied, a collection of attachments of the specified type will be created. 18 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 19 * Accepts 'all', 'uploaded', or 'unattached'. 20 * @param {string} [attributes.menu=gallery] Initial mode for the menu region. 21 * @param {string} [attributes.content=upload] Initial mode for the content region. 22 * Overridden by persistent user setting if 'contentUserSetting' is true. 23 * @param {string} [attributes.router=browse] Initial mode for the router region. 24 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 25 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 26 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 27 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 28 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 29 * @param {int} [attributes.priority=100] The priority for the state link in the media menu. 30 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 31 * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. 32 * @param {string} attributes.type The collection's media type. (e.g. 'video'). 33 * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). 34 */ 35 var Selection = wp.media.model.Selection, 36 Library = wp.media.controller.Library, 37 CollectionAdd; 38 39 CollectionAdd = Library.extend({ 40 defaults: _.defaults( { 41 // Selection defaults. @see media.model.Selection 42 multiple: 'add', 43 // Attachments browser defaults. @see media.view.AttachmentsBrowser 44 filterable: 'uploaded', 45 46 priority: 100, 47 syncSelection: false 48 }, Library.prototype.defaults ), 49 50 /** 51 * @since 3.9.0 52 */ 53 initialize: function() { 54 var collectionType = this.get('collectionType'); 55 56 if ( 'video' === this.get( 'type' ) ) { 57 collectionType = 'video-' + collectionType; 58 } 59 60 this.set( 'id', collectionType + '-library' ); 61 this.set( 'toolbar', collectionType + '-add' ); 62 this.set( 'menu', collectionType ); 63 64 // If we haven't been provided a `library`, create a `Selection`. 65 if ( ! this.get('library') ) { 66 this.set( 'library', wp.media.query({ type: this.get('type') }) ); 67 } 68 Library.prototype.initialize.apply( this, arguments ); 69 }, 70 71 /** 72 * @since 3.9.0 73 */ 74 activate: function() { 75 var library = this.get('library'), 76 editLibrary = this.get('editLibrary'), 77 edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library'); 78 79 if ( editLibrary && editLibrary !== edit ) { 80 library.unobserve( editLibrary ); 81 } 82 83 // Accepts attachments that exist in the original library and 84 // that do not exist in gallery's library. 85 library.validator = function( attachment ) { 86 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments ); 87 }; 88 89 // Reset the library to ensure that all attachments are re-added 90 // to the collection. Do so silently, as calling `observe` will 91 // trigger the `reset` event. 92 library.reset( library.mirroring.models, { silent: true }); 93 library.observe( edit ); 94 this.set('editLibrary', edit); 95 96 Library.prototype.activate.apply( this, arguments ); 97 } 98 }); 99 100 module.exports = CollectionAdd; 101 102 },{}],2:[function(require,module,exports){ 103 /** 104 * wp.media.controller.CollectionEdit 105 * 106 * A state for editing a collection, which is used by audio and video playlists, 107 * and can be used for other collections. 108 * 109 * @class 110 * @augments wp.media.controller.Library 111 * @augments wp.media.controller.State 112 * @augments Backbone.Model 113 * 114 * @param {object} [attributes] The attributes hash passed to the state. 115 * @param {string} attributes.title Title for the state. Displays in the media menu and the frame's title region. 116 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to edit. 117 * If one is not supplied, an empty media.model.Selection collection is created. 118 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 119 * @param {string} [attributes.content=browse] Initial mode for the content region. 120 * @param {string} attributes.menu Initial mode for the menu region. @todo this needs a better explanation. 121 * @param {boolean} [attributes.searchable=false] Whether the library is searchable. 122 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 123 * @param {boolean} [attributes.date=true] Whether to show the date filter in the browser's toolbar. 124 * @param {boolean} [attributes.describe=true] Whether to offer UI to describe the attachments - e.g. captioning images in a gallery. 125 * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable. 126 * @param {boolean} [attributes.dragInfoText] Instructional text about the attachments being sortable. 127 * @param {int} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments. 128 * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance. 129 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 130 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 131 * Defaults to false for this state, because the library passed in *is* the selection. 132 * @param {view} [attributes.SettingsView] The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox). 133 * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`. 134 * If none supplied, defaults to wp.media.view.Attachment.EditLibrary. 135 * @param {string} attributes.type The collection's media type. (e.g. 'video'). 136 * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). 137 */ 138 var Library = wp.media.controller.Library, 139 l10n = wp.media.view.l10n, 71 140 $ = jQuery, 72 l10n; 73 74 media.isTouchDevice = ( 'ontouchend' in document ); 75 76 // Link any localized strings. 77 l10n = media.view.l10n = window._wpMediaViewsL10n || {}; 78 79 // Link any settings. 80 media.view.settings = l10n.settings || {}; 81 delete l10n.settings; 82 83 // Copy the `post` setting over to the model settings. 84 media.model.settings.post = media.view.settings.post; 85 86 // Check if the browser supports CSS 3.0 transitions 87 $.support.transition = (function(){ 88 var style = document.documentElement.style, 89 transitions = { 90 WebkitTransition: 'webkitTransitionEnd', 91 MozTransition: 'transitionend', 92 OTransition: 'oTransitionEnd otransitionend', 93 transition: 'transitionend' 94 }, transition; 95 96 transition = _.find( _.keys( transitions ), function( transition ) { 97 return ! _.isUndefined( style[ transition ] ); 98 }); 99 100 return transition && { 101 end: transitions[ transition ] 102 }; 103 }()); 104 141 CollectionEdit; 142 143 CollectionEdit = Library.extend({ 144 defaults: { 145 multiple: false, 146 sortable: true, 147 date: false, 148 searchable: false, 149 content: 'browse', 150 describe: true, 151 dragInfo: true, 152 idealColumnWidth: 170, 153 editing: false, 154 priority: 60, 155 SettingsView: false, 156 syncSelection: false 157 }, 158 159 /** 160 * @since 3.9.0 161 */ 162 initialize: function() { 163 var collectionType = this.get('collectionType'); 164 165 if ( 'video' === this.get( 'type' ) ) { 166 collectionType = 'video-' + collectionType; 167 } 168 169 this.set( 'id', collectionType + '-edit' ); 170 this.set( 'toolbar', collectionType + '-edit' ); 171 172 // If we haven't been provided a `library`, create a `Selection`. 173 if ( ! this.get('library') ) { 174 this.set( 'library', new wp.media.model.Selection() ); 175 } 176 // The single `Attachment` view to be used in the `Attachments` view. 177 if ( ! this.get('AttachmentView') ) { 178 this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); 179 } 180 Library.prototype.initialize.apply( this, arguments ); 181 }, 182 183 /** 184 * @since 3.9.0 185 */ 186 activate: function() { 187 var library = this.get('library'); 188 189 // Limit the library to images only. 190 library.props.set( 'type', this.get( 'type' ) ); 191 192 // Watch for uploaded attachments. 193 this.get('library').observe( wp.Uploader.queue ); 194 195 this.frame.on( 'content:render:browse', this.renderSettings, this ); 196 197 Library.prototype.activate.apply( this, arguments ); 198 }, 199 200 /** 201 * @since 3.9.0 202 */ 203 deactivate: function() { 204 // Stop watching for uploaded attachments. 205 this.get('library').unobserve( wp.Uploader.queue ); 206 207 this.frame.off( 'content:render:browse', this.renderSettings, this ); 208 209 Library.prototype.deactivate.apply( this, arguments ); 210 }, 211 212 /** 213 * Render the collection embed settings view in the browser sidebar. 214 * 215 * @todo This is against the pattern elsewhere in media. Typically the frame 216 * is responsible for adding region mode callbacks. Explain. 217 * 218 * @since 3.9.0 219 * 220 * @param {wp.media.view.attachmentsBrowser} The attachments browser view. 221 */ 222 renderSettings: function( attachmentsBrowserView ) { 223 var library = this.get('library'), 224 collectionType = this.get('collectionType'), 225 dragInfoText = this.get('dragInfoText'), 226 SettingsView = this.get('SettingsView'), 227 obj = {}; 228 229 if ( ! library || ! attachmentsBrowserView ) { 230 return; 231 } 232 233 library[ collectionType ] = library[ collectionType ] || new Backbone.Model(); 234 235 obj[ collectionType ] = new SettingsView({ 236 controller: this, 237 model: library[ collectionType ], 238 priority: 40 239 }); 240 241 attachmentsBrowserView.sidebar.set( obj ); 242 243 if ( dragInfoText ) { 244 attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({ 245 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0], 246 priority: -40 247 }) ); 248 } 249 250 // Add the 'Reverse order' button to the toolbar. 251 attachmentsBrowserView.toolbar.set( 'reverse', { 252 text: l10n.reverseOrder, 253 priority: 80, 254 255 click: function() { 256 library.reset( library.toArray().reverse() ); 257 } 258 }); 259 } 260 }); 261 262 module.exports = CollectionEdit; 263 264 },{}],3:[function(require,module,exports){ 105 265 /** 106 * A shared event bus used to provide events into 107 * the media workflows that 3rd-party devs can use to hook 108 * in. 266 * wp.media.controller.Cropper 267 * 268 * A state for cropping an image. 269 * 270 * @class 271 * @augments wp.media.controller.State 272 * @augments Backbone.Model 109 273 */ 110 media.events = _.extend( {}, Backbone.Events ); 111 274 var l10n = wp.media.view.l10n, 275 Cropper; 276 277 Cropper = wp.media.controller.State.extend({ 278 defaults: { 279 id: 'cropper', 280 title: l10n.cropImage, 281 // Region mode defaults. 282 toolbar: 'crop', 283 content: 'crop', 284 router: false, 285 286 canSkipCrop: false 287 }, 288 289 activate: function() { 290 this.frame.on( 'content:create:crop', this.createCropContent, this ); 291 this.frame.on( 'close', this.removeCropper, this ); 292 this.set('selection', new Backbone.Collection(this.frame._selection.single)); 293 }, 294 295 deactivate: function() { 296 this.frame.toolbar.mode('browse'); 297 }, 298 299 createCropContent: function() { 300 this.cropperView = new wp.media.view.Cropper({ 301 controller: this, 302 attachment: this.get('selection').first() 303 }); 304 this.cropperView.on('image-loaded', this.createCropToolbar, this); 305 this.frame.content.set(this.cropperView); 306 307 }, 308 removeCropper: function() { 309 this.imgSelect.cancelSelection(); 310 this.imgSelect.setOptions({remove: true}); 311 this.imgSelect.update(); 312 this.cropperView.remove(); 313 }, 314 createCropToolbar: function() { 315 var canSkipCrop, toolbarOptions; 316 317 canSkipCrop = this.get('canSkipCrop') || false; 318 319 toolbarOptions = { 320 controller: this.frame, 321 items: { 322 insert: { 323 style: 'primary', 324 text: l10n.cropImage, 325 priority: 80, 326 requires: { library: false, selection: false }, 327 328 click: function() { 329 var controller = this.controller, 330 selection; 331 332 selection = controller.state().get('selection').first(); 333 selection.set({cropDetails: controller.state().imgSelect.getSelection()}); 334 335 this.$el.text(l10n.cropping); 336 this.$el.attr('disabled', true); 337 338 controller.state().doCrop( selection ).done( function( croppedImage ) { 339 controller.trigger('cropped', croppedImage ); 340 controller.close(); 341 }).fail( function() { 342 controller.trigger('content:error:crop'); 343 }); 344 } 345 } 346 } 347 }; 348 349 if ( canSkipCrop ) { 350 _.extend( toolbarOptions.items, { 351 skip: { 352 style: 'secondary', 353 text: l10n.skipCropping, 354 priority: 70, 355 requires: { library: false, selection: false }, 356 click: function() { 357 var selection = this.controller.state().get('selection').first(); 358 this.controller.state().cropperView.remove(); 359 this.controller.trigger('skippedcrop', selection); 360 this.controller.close(); 361 } 362 } 363 }); 364 } 365 366 this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) ); 367 }, 368 369 doCrop: function( attachment ) { 370 return wp.ajax.post( 'custom-header-crop', { 371 nonce: attachment.get('nonces').edit, 372 id: attachment.get('id'), 373 cropDetails: attachment.get('cropDetails') 374 } ); 375 } 376 }); 377 378 module.exports = Cropper; 379 380 },{}],4:[function(require,module,exports){ 112 381 /** 113 * Makes it easier to bind events using transitions. 114 * 115 * @param {string} selector 116 * @param {Number} sensitivity 117 * @returns {Promise} 382 * wp.media.controller.CustomizeImageCropper 383 * 384 * A state for cropping an image. 385 * 386 * @class 387 * @augments wp.media.controller.Cropper 388 * @augments wp.media.controller.State 389 * @augments Backbone.Model 118 390 */ 119 media.transition = function( selector, sensitivity ) { 120 var deferred = $.Deferred(); 121 122 sensitivity = sensitivity || 2000; 123 124 if ( $.support.transition ) { 125 if ( ! (selector instanceof $) ) { 126 selector = $( selector ); 127 } 128 129 // Resolve the deferred when the first element finishes animating. 130 selector.first().one( $.support.transition.end, deferred.resolve ); 131 132 // Just in case the event doesn't trigger, fire a callback. 133 _.delay( deferred.resolve, sensitivity ); 134 135 // Otherwise, execute on the spot. 136 } else { 137 deferred.resolve(); 391 var Controller = wp.media.controller, 392 CustomizeImageCropper; 393 394 CustomizeImageCropper = Controller.Cropper.extend({ 395 doCrop: function( attachment ) { 396 var cropDetails = attachment.get( 'cropDetails' ), 397 control = this.get( 'control' ); 398 399 cropDetails.dst_width = control.params.width; 400 cropDetails.dst_height = control.params.height; 401 402 return wp.ajax.post( 'crop-image', { 403 wp_customize: 'on', 404 nonce: attachment.get( 'nonces' ).edit, 405 id: attachment.get( 'id' ), 406 context: control.id, 407 cropDetails: cropDetails 408 } ); 138 409 } 139 140 return deferred.promise(); 141 }; 142 143 media.controller.Region = __webpack_require__( 27 ); 144 media.controller.StateMachine = __webpack_require__( 28 ); 145 media.controller.State = __webpack_require__( 29 ); 146 147 media.selectionSync = __webpack_require__( 30 ); 148 media.controller.Library = __webpack_require__( 31 ); 149 media.controller.ImageDetails = __webpack_require__( 32 ); 150 media.controller.GalleryEdit = __webpack_require__( 33 ); 151 media.controller.GalleryAdd = __webpack_require__( 34 ); 152 media.controller.CollectionEdit = __webpack_require__( 35 ); 153 media.controller.CollectionAdd = __webpack_require__( 36 ); 154 media.controller.FeaturedImage = __webpack_require__( 37 ); 155 media.controller.ReplaceImage = __webpack_require__( 38 ); 156 media.controller.EditImage = __webpack_require__( 39 ); 157 media.controller.MediaLibrary = __webpack_require__( 40 ); 158 media.controller.Embed = __webpack_require__( 41 ); 159 media.controller.Cropper = __webpack_require__( 42 ); 160 media.controller.CustomizeImageCropper = __webpack_require__( 43 ); 161 media.controller.SiteIconCropper = __webpack_require__( 44 ); 162 163 media.View = __webpack_require__( 45 ); 164 media.view.Frame = __webpack_require__( 46 ); 165 media.view.MediaFrame = __webpack_require__( 47 ); 166 media.view.MediaFrame.Select = __webpack_require__( 48 ); 167 media.view.MediaFrame.Post = __webpack_require__( 49 ); 168 media.view.MediaFrame.ImageDetails = __webpack_require__( 50 ); 169 media.view.Modal = __webpack_require__( 51 ); 170 media.view.FocusManager = __webpack_require__( 52 ); 171 media.view.UploaderWindow = __webpack_require__( 53 ); 172 media.view.EditorUploader = __webpack_require__( 54 ); 173 media.view.UploaderInline = __webpack_require__( 55 ); 174 media.view.UploaderStatus = __webpack_require__( 56 ); 175 media.view.UploaderStatusError = __webpack_require__( 57 ); 176 media.view.Toolbar = __webpack_require__( 58 ); 177 media.view.Toolbar.Select = __webpack_require__( 59 ); 178 media.view.Toolbar.Embed = __webpack_require__( 60 ); 179 media.view.Button = __webpack_require__( 61 ); 180 media.view.ButtonGroup = __webpack_require__( 62 ); 181 media.view.PriorityList = __webpack_require__( 63 ); 182 media.view.MenuItem = __webpack_require__( 64 ); 183 media.view.Menu = __webpack_require__( 65 ); 184 media.view.RouterItem = __webpack_require__( 66 ); 185 media.view.Router = __webpack_require__( 67 ); 186 media.view.Sidebar = __webpack_require__( 68 ); 187 media.view.Attachment = __webpack_require__( 69 ); 188 media.view.Attachment.Library = __webpack_require__( 70 ); 189 media.view.Attachment.EditLibrary = __webpack_require__( 71 ); 190 media.view.Attachments = __webpack_require__( 72 ); 191 media.view.Search = __webpack_require__( 73 ); 192 media.view.AttachmentFilters = __webpack_require__( 74 ); 193 media.view.DateFilter = __webpack_require__( 75 ); 194 media.view.AttachmentFilters.Uploaded = __webpack_require__( 76 ); 195 media.view.AttachmentFilters.All = __webpack_require__( 77 ); 196 media.view.AttachmentsBrowser = __webpack_require__( 78 ); 197 media.view.Selection = __webpack_require__( 79 ); 198 media.view.Attachment.Selection = __webpack_require__( 80 ); 199 media.view.Attachments.Selection = __webpack_require__( 81 ); 200 media.view.Attachment.EditSelection = __webpack_require__( 82 ); 201 media.view.Settings = __webpack_require__( 83 ); 202 media.view.Settings.AttachmentDisplay = __webpack_require__( 84 ); 203 media.view.Settings.Gallery = __webpack_require__( 85 ); 204 media.view.Settings.Playlist = __webpack_require__( 86 ); 205 media.view.Attachment.Details = __webpack_require__( 87 ); 206 media.view.AttachmentCompat = __webpack_require__( 88 ); 207 media.view.Iframe = __webpack_require__( 89 ); 208 media.view.Embed = __webpack_require__( 90 ); 209 media.view.Label = __webpack_require__( 91 ); 210 media.view.EmbedUrl = __webpack_require__( 92 ); 211 media.view.EmbedLink = __webpack_require__( 93 ); 212 media.view.EmbedImage = __webpack_require__( 94 ); 213 media.view.ImageDetails = __webpack_require__( 95 ); 214 media.view.Cropper = __webpack_require__( 96 ); 215 media.view.SiteIconCropper = __webpack_require__( 97 ); 216 media.view.SiteIconPreview = __webpack_require__( 98 ); 217 media.view.EditImage = __webpack_require__( 99 ); 218 media.view.Spinner = __webpack_require__( 100 ); 219 220 221 /***/ }), 222 /* 27 */ 223 /***/ (function(module, exports) { 224 410 }); 411 412 module.exports = CustomizeImageCropper; 413 414 },{}],5:[function(require,module,exports){ 225 415 /** 226 * wp.media.controller.Region 227 * 228 * A region is a persistent application layout area. 229 * 230 * A region assumes one mode at any time, and can be switched to another. 231 * 232 * When mode changes, events are triggered on the region's parent view. 233 * The parent view will listen to specific events and fill the region with an 234 * appropriate view depending on mode. For example, a frame listens for the 235 * 'browse' mode t be activated on the 'content' view and then fills the region 236 * with an AttachmentsBrowser view. 416 * wp.media.controller.EditImage 417 * 418 * A state for editing (cropping, etc.) an image. 237 419 * 238 420 * @class 239 * 240 * @param {object} options Options hash for the region. 241 * @param {string} options.id Unique identifier for the region. 242 * @param {Backbone.View} options.view A parent view the region exists within. 243 * @param {string} options.selector jQuery selector for the region within the parent view. 421 * @augments wp.media.controller.State 422 * @augments Backbone.Model 423 * 424 * @param {object} attributes The attributes hash passed to the state. 425 * @param {wp.media.model.Attachment} attributes.model The attachment. 426 * @param {string} [attributes.id=edit-image] Unique identifier. 427 * @param {string} [attributes.title=Edit Image] Title for the state. Displays in the media menu and the frame's title region. 428 * @param {string} [attributes.content=edit-image] Initial mode for the content region. 429 * @param {string} [attributes.toolbar=edit-image] Initial mode for the toolbar region. 430 * @param {string} [attributes.menu=false] Initial mode for the menu region. 431 * @param {string} [attributes.url] Unused. @todo Consider removal. 244 432 */ 245 var Region = function( options ) { 246 _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) ); 247 }; 248 249 // Use Backbone's self-propagating `extend` inheritance method. 250 Region.extend = Backbone.Model.extend; 251 252 _.extend( Region.prototype, { 253 /** 254 * Activate a mode. 433 var l10n = wp.media.view.l10n, 434 EditImage; 435 436 EditImage = wp.media.controller.State.extend({ 437 defaults: { 438 id: 'edit-image', 439 title: l10n.editImage, 440 menu: false, 441 toolbar: 'edit-image', 442 content: 'edit-image', 443 url: '' 444 }, 445 446 /** 447 * @since 3.9.0 448 */ 449 activate: function() { 450 this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar ); 451 }, 452 453 /** 454 * @since 3.9.0 455 */ 456 deactivate: function() { 457 this.stopListening( this.frame ); 458 }, 459 460 /** 461 * @since 3.9.0 462 */ 463 toolbar: function() { 464 var frame = this.frame, 465 lastState = frame.lastState(), 466 previous = lastState && lastState.id; 467 468 frame.toolbar.set( new wp.media.view.Toolbar({ 469 controller: frame, 470 items: { 471 back: { 472 style: 'primary', 473 text: l10n.back, 474 priority: 20, 475 click: function() { 476 if ( previous ) { 477 frame.setState( previous ); 478 } else { 479 frame.close(); 480 } 481 } 482 } 483 } 484 }) ); 485 } 486 }); 487 488 module.exports = EditImage; 489 490 },{}],6:[function(require,module,exports){ 491 /** 492 * wp.media.controller.Embed 493 * 494 * A state for embedding media from a URL. 495 * 496 * @class 497 * @augments wp.media.controller.State 498 * @augments Backbone.Model 499 * 500 * @param {object} attributes The attributes hash passed to the state. 501 * @param {string} [attributes.id=embed] Unique identifier. 502 * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region. 503 * @param {string} [attributes.content=embed] Initial mode for the content region. 504 * @param {string} [attributes.menu=default] Initial mode for the menu region. 505 * @param {string} [attributes.toolbar=main-embed] Initial mode for the toolbar region. 506 * @param {string} [attributes.menu=false] Initial mode for the menu region. 507 * @param {int} [attributes.priority=120] The priority for the state link in the media menu. 508 * @param {string} [attributes.type=link] The type of embed. Currently only link is supported. 509 * @param {string} [attributes.url] The embed URL. 510 * @param {object} [attributes.metadata={}] Properties of the embed, which will override attributes.url if set. 511 */ 512 var l10n = wp.media.view.l10n, 513 $ = Backbone.$, 514 Embed; 515 516 Embed = wp.media.controller.State.extend({ 517 defaults: { 518 id: 'embed', 519 title: l10n.insertFromUrlTitle, 520 content: 'embed', 521 menu: 'default', 522 toolbar: 'main-embed', 523 priority: 120, 524 type: 'link', 525 url: '', 526 metadata: {} 527 }, 528 529 // The amount of time used when debouncing the scan. 530 sensitivity: 400, 531 532 initialize: function(options) { 533 this.metadata = options.metadata; 534 this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity ); 535 this.props = new Backbone.Model( this.metadata || { url: '' }); 536 this.props.on( 'change:url', this.debouncedScan, this ); 537 this.props.on( 'change:url', this.refresh, this ); 538 this.on( 'scan', this.scanImage, this ); 539 }, 540 541 /** 542 * Trigger a scan of the embedded URL's content for metadata required to embed. 255 543 * 544 * @fires wp.media.controller.Embed#scan 545 */ 546 scan: function() { 547 var scanners, 548 embed = this, 549 attributes = { 550 type: 'link', 551 scanners: [] 552 }; 553 554 // Scan is triggered with the list of `attributes` to set on the 555 // state, useful for the 'type' attribute and 'scanners' attribute, 556 // an array of promise objects for asynchronous scan operations. 557 if ( this.props.get('url') ) { 558 this.trigger( 'scan', attributes ); 559 } 560 561 if ( attributes.scanners.length ) { 562 scanners = attributes.scanners = $.when.apply( $, attributes.scanners ); 563 scanners.always( function() { 564 if ( embed.get('scanners') === scanners ) { 565 embed.set( 'loading', false ); 566 } 567 }); 568 } else { 569 attributes.scanners = null; 570 } 571 572 attributes.loading = !! attributes.scanners; 573 this.set( attributes ); 574 }, 575 /** 576 * Try scanning the embed as an image to discover its dimensions. 577 * 578 * @param {Object} attributes 579 */ 580 scanImage: function( attributes ) { 581 var frame = this.frame, 582 state = this, 583 url = this.props.get('url'), 584 image = new Image(), 585 deferred = $.Deferred(); 586 587 attributes.scanners.push( deferred.promise() ); 588 589 // Try to load the image and find its width/height. 590 image.onload = function() { 591 deferred.resolve(); 592 593 if ( state !== frame.state() || url !== state.props.get('url') ) { 594 return; 595 } 596 597 state.set({ 598 type: 'image' 599 }); 600 601 state.props.set({ 602 width: image.width, 603 height: image.height 604 }); 605 }; 606 607 image.onerror = deferred.reject; 608 image.src = url; 609 }, 610 611 refresh: function() { 612 this.frame.toolbar.get().refresh(); 613 }, 614 615 reset: function() { 616 this.props.clear().set({ url: '' }); 617 618 if ( this.active ) { 619 this.refresh(); 620 } 621 } 622 }); 623 624 module.exports = Embed; 625 626 },{}],7:[function(require,module,exports){ 627 /** 628 * wp.media.controller.FeaturedImage 629 * 630 * A state for selecting a featured image for a post. 631 * 632 * @class 633 * @augments wp.media.controller.Library 634 * @augments wp.media.controller.State 635 * @augments Backbone.Model 636 * 637 * @param {object} [attributes] The attributes hash passed to the state. 638 * @param {string} [attributes.id=featured-image] Unique identifier. 639 * @param {string} [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region. 640 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 641 * If one is not supplied, a collection of all images will be created. 642 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 643 * @param {string} [attributes.content=upload] Initial mode for the content region. 644 * Overridden by persistent user setting if 'contentUserSetting' is true. 645 * @param {string} [attributes.menu=default] Initial mode for the menu region. 646 * @param {string} [attributes.router=browse] Initial mode for the router region. 647 * @param {string} [attributes.toolbar=featured-image] Initial mode for the toolbar region. 648 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 649 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 650 * @param {boolean|string} [attributes.filterable=false] Whether the library is filterable, and if so what filters should be shown. 651 * Accepts 'all', 'uploaded', or 'unattached'. 652 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 653 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 654 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 655 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 656 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 657 */ 658 var Attachment = wp.media.model.Attachment, 659 Library = wp.media.controller.Library, 660 l10n = wp.media.view.l10n, 661 FeaturedImage; 662 663 FeaturedImage = Library.extend({ 664 defaults: _.defaults({ 665 id: 'featured-image', 666 title: l10n.setFeaturedImageTitle, 667 multiple: false, 668 filterable: 'uploaded', 669 toolbar: 'featured-image', 670 priority: 60, 671 syncSelection: true 672 }, Library.prototype.defaults ), 673 674 /** 675 * @since 3.5.0 676 */ 677 initialize: function() { 678 var library, comparator; 679 680 // If we haven't been provided a `library`, create a `Selection`. 681 if ( ! this.get('library') ) { 682 this.set( 'library', wp.media.query({ type: 'image' }) ); 683 } 684 685 Library.prototype.initialize.apply( this, arguments ); 686 687 library = this.get('library'); 688 comparator = library.comparator; 689 690 // Overload the library's comparator to push items that are not in 691 // the mirrored query to the front of the aggregate collection. 692 library.comparator = function( a, b ) { 693 var aInQuery = !! this.mirroring.get( a.cid ), 694 bInQuery = !! this.mirroring.get( b.cid ); 695 696 if ( ! aInQuery && bInQuery ) { 697 return -1; 698 } else if ( aInQuery && ! bInQuery ) { 699 return 1; 700 } else { 701 return comparator.apply( this, arguments ); 702 } 703 }; 704 705 // Add all items in the selection to the library, so any featured 706 // images that are not initially loaded still appear. 707 library.observe( this.get('selection') ); 708 }, 709 710 /** 711 * @since 3.5.0 712 */ 713 activate: function() { 714 this.updateSelection(); 715 this.frame.on( 'open', this.updateSelection, this ); 716 717 Library.prototype.activate.apply( this, arguments ); 718 }, 719 720 /** 721 * @since 3.5.0 722 */ 723 deactivate: function() { 724 this.frame.off( 'open', this.updateSelection, this ); 725 726 Library.prototype.deactivate.apply( this, arguments ); 727 }, 728 729 /** 730 * @since 3.5.0 731 */ 732 updateSelection: function() { 733 var selection = this.get('selection'), 734 id = wp.media.view.settings.post.featuredImageId, 735 attachment; 736 737 if ( '' !== id && -1 !== id ) { 738 attachment = Attachment.get( id ); 739 attachment.fetch(); 740 } 741 742 selection.reset( attachment ? [ attachment ] : [] ); 743 } 744 }); 745 746 module.exports = FeaturedImage; 747 748 },{}],8:[function(require,module,exports){ 749 /** 750 * wp.media.controller.GalleryAdd 751 * 752 * A state for selecting more images to add to a gallery. 753 * 754 * @class 755 * @augments wp.media.controller.Library 756 * @augments wp.media.controller.State 757 * @augments Backbone.Model 758 * 759 * @param {object} [attributes] The attributes hash passed to the state. 760 * @param {string} [attributes.id=gallery-library] Unique identifier. 761 * @param {string} [attributes.title=Add to Gallery] Title for the state. Displays in the frame's title region. 762 * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. 763 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 764 * If one is not supplied, a collection of all images will be created. 765 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 766 * Accepts 'all', 'uploaded', or 'unattached'. 767 * @param {string} [attributes.menu=gallery] Initial mode for the menu region. 768 * @param {string} [attributes.content=upload] Initial mode for the content region. 769 * Overridden by persistent user setting if 'contentUserSetting' is true. 770 * @param {string} [attributes.router=browse] Initial mode for the router region. 771 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 772 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 773 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 774 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 775 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 776 * @param {int} [attributes.priority=100] The priority for the state link in the media menu. 777 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 778 * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. 779 */ 780 var Selection = wp.media.model.Selection, 781 Library = wp.media.controller.Library, 782 l10n = wp.media.view.l10n, 783 GalleryAdd; 784 785 GalleryAdd = Library.extend({ 786 defaults: _.defaults({ 787 id: 'gallery-library', 788 title: l10n.addToGalleryTitle, 789 multiple: 'add', 790 filterable: 'uploaded', 791 menu: 'gallery', 792 toolbar: 'gallery-add', 793 priority: 100, 794 syncSelection: false 795 }, Library.prototype.defaults ), 796 797 /** 798 * @since 3.5.0 799 */ 800 initialize: function() { 801 // If a library wasn't supplied, create a library of images. 802 if ( ! this.get('library') ) { 803 this.set( 'library', wp.media.query({ type: 'image' }) ); 804 } 805 806 Library.prototype.initialize.apply( this, arguments ); 807 }, 808 809 /** 810 * @since 3.5.0 811 */ 812 activate: function() { 813 var library = this.get('library'), 814 edit = this.frame.state('gallery-edit').get('library'); 815 816 if ( this.editLibrary && this.editLibrary !== edit ) { 817 library.unobserve( this.editLibrary ); 818 } 819 820 // Accepts attachments that exist in the original library and 821 // that do not exist in gallery's library. 822 library.validator = function( attachment ) { 823 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments ); 824 }; 825 826 // Reset the library to ensure that all attachments are re-added 827 // to the collection. Do so silently, as calling `observe` will 828 // trigger the `reset` event. 829 library.reset( library.mirroring.models, { silent: true }); 830 library.observe( edit ); 831 this.editLibrary = edit; 832 833 Library.prototype.activate.apply( this, arguments ); 834 } 835 }); 836 837 module.exports = GalleryAdd; 838 839 },{}],9:[function(require,module,exports){ 840 /** 841 * wp.media.controller.GalleryEdit 842 * 843 * A state for editing a gallery's images and settings. 844 * 845 * @class 846 * @augments wp.media.controller.Library 847 * @augments wp.media.controller.State 848 * @augments Backbone.Model 849 * 850 * @param {object} [attributes] The attributes hash passed to the state. 851 * @param {string} [attributes.id=gallery-edit] Unique identifier. 852 * @param {string} [attributes.title=Edit Gallery] Title for the state. Displays in the frame's title region. 853 * @param {wp.media.model.Attachments} [attributes.library] The collection of attachments in the gallery. 854 * If one is not supplied, an empty media.model.Selection collection is created. 855 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 856 * @param {boolean} [attributes.searchable=false] Whether the library is searchable. 857 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 858 * @param {boolean} [attributes.date=true] Whether to show the date filter in the browser's toolbar. 859 * @param {string|false} [attributes.content=browse] Initial mode for the content region. 860 * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region. 861 * @param {boolean} [attributes.describe=true] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 862 * @param {boolean} [attributes.displaySettings=true] Whether to show the attachment display settings interface. 863 * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable. 864 * @param {int} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments. 865 * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance. 866 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 867 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 868 * Defaults to false for this state, because the library passed in *is* the selection. 869 * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`. 870 * If none supplied, defaults to wp.media.view.Attachment.EditLibrary. 871 */ 872 var Library = wp.media.controller.Library, 873 l10n = wp.media.view.l10n, 874 GalleryEdit; 875 876 GalleryEdit = Library.extend({ 877 defaults: { 878 id: 'gallery-edit', 879 title: l10n.editGalleryTitle, 880 multiple: false, 881 searchable: false, 882 sortable: true, 883 date: false, 884 display: false, 885 content: 'browse', 886 toolbar: 'gallery-edit', 887 describe: true, 888 displaySettings: true, 889 dragInfo: true, 890 idealColumnWidth: 170, 891 editing: false, 892 priority: 60, 893 syncSelection: false 894 }, 895 896 /** 897 * @since 3.5.0 898 */ 899 initialize: function() { 900 // If we haven't been provided a `library`, create a `Selection`. 901 if ( ! this.get('library') ) { 902 this.set( 'library', new wp.media.model.Selection() ); 903 } 904 905 // The single `Attachment` view to be used in the `Attachments` view. 906 if ( ! this.get('AttachmentView') ) { 907 this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); 908 } 909 910 Library.prototype.initialize.apply( this, arguments ); 911 }, 912 913 /** 914 * @since 3.5.0 915 */ 916 activate: function() { 917 var library = this.get('library'); 918 919 // Limit the library to images only. 920 library.props.set( 'type', 'image' ); 921 922 // Watch for uploaded attachments. 923 this.get('library').observe( wp.Uploader.queue ); 924 925 this.frame.on( 'content:render:browse', this.gallerySettings, this ); 926 927 Library.prototype.activate.apply( this, arguments ); 928 }, 929 930 /** 931 * @since 3.5.0 932 */ 933 deactivate: function() { 934 // Stop watching for uploaded attachments. 935 this.get('library').unobserve( wp.Uploader.queue ); 936 937 this.frame.off( 'content:render:browse', this.gallerySettings, this ); 938 939 Library.prototype.deactivate.apply( this, arguments ); 940 }, 941 942 /** 256 943 * @since 3.5.0 257 944 * 258 * @param {string} mode 259 * 260 * @fires this.view#{this.id}:activate:{this._mode} 261 * @fires this.view#{this.id}:activate 262 * @fires this.view#{this.id}:deactivate:{this._mode} 263 * @fires this.view#{this.id}:deactivate 264 * 265 * @returns {wp.media.controller.Region} Returns itself to allow chaining. 266 */ 267 mode: function( mode ) { 268 if ( ! mode ) { 269 return this._mode; 270 } 271 // Bail if we're trying to change to the current mode. 272 if ( mode === this._mode ) { 273 return this; 274 } 275 276 /** 277 * Region mode deactivation event. 278 * 279 * @event this.view#{this.id}:deactivate:{this._mode} 280 * @event this.view#{this.id}:deactivate 281 */ 282 this.trigger('deactivate'); 283 284 this._mode = mode; 285 this.render( mode ); 286 287 /** 288 * Region mode activation event. 289 * 290 * @event this.view#{this.id}:activate:{this._mode} 291 * @event this.view#{this.id}:activate 292 */ 293 this.trigger('activate'); 294 return this; 295 }, 296 /** 297 * Render a mode. 298 * 299 * @since 3.5.0 300 * 301 * @param {string} mode 302 * 303 * @fires this.view#{this.id}:create:{this._mode} 304 * @fires this.view#{this.id}:create 305 * @fires this.view#{this.id}:render:{this._mode} 306 * @fires this.view#{this.id}:render 307 * 308 * @returns {wp.media.controller.Region} Returns itself to allow chaining 309 */ 310 render: function( mode ) { 311 // If the mode isn't active, activate it. 312 if ( mode && mode !== this._mode ) { 313 return this.mode( mode ); 314 } 315 316 var set = { view: null }, 317 view; 318 319 /** 320 * Create region view event. 321 * 322 * Region view creation takes place in an event callback on the frame. 323 * 324 * @event this.view#{this.id}:create:{this._mode} 325 * @event this.view#{this.id}:create 326 */ 327 this.trigger( 'create', set ); 328 view = set.view; 329 330 /** 331 * Render region view event. 332 * 333 * Region view creation takes place in an event callback on the frame. 334 * 335 * @event this.view#{this.id}:create:{this._mode} 336 * @event this.view#{this.id}:create 337 */ 338 this.trigger( 'render', view ); 339 if ( view ) { 340 this.set( view ); 341 } 342 return this; 343 }, 344 345 /** 346 * Get the region's view. 347 * 348 * @since 3.5.0 349 * 350 * @returns {wp.media.View} 351 */ 352 get: function() { 353 return this.view.views.first( this.selector ); 354 }, 355 356 /** 357 * Set the region's view as a subview of the frame. 358 * 359 * @since 3.5.0 360 * 361 * @param {Array|Object} views 362 * @param {Object} [options={}] 363 * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining 364 */ 365 set: function( views, options ) { 366 if ( options ) { 367 options.add = false; 368 } 369 return this.view.views.set( this.selector, views, options ); 370 }, 371 372 /** 373 * Trigger regional view events on the frame. 374 * 375 * @since 3.5.0 376 * 377 * @param {string} event 378 * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining. 379 */ 380 trigger: function( event ) { 381 var base, args; 382 383 if ( ! this._mode ) { 945 * @param browser 946 */ 947 gallerySettings: function( browser ) { 948 if ( ! this.get('displaySettings') ) { 384 949 return; 385 950 } 386 951 387 args = _.toArray( arguments ); 388 base = this.id + ':' + event; 389 390 // Trigger `{this.id}:{event}:{this._mode}` event on the frame. 391 args[0] = base + ':' + this._mode; 392 this.view.trigger.apply( this.view, args ); 393 394 // Trigger `{this.id}:{event}` event on the frame. 395 args[0] = base; 396 this.view.trigger.apply( this.view, args ); 397 return this; 952 var library = this.get('library'); 953 954 if ( ! library || ! browser ) { 955 return; 956 } 957 958 library.gallery = library.gallery || new Backbone.Model(); 959 960 browser.sidebar.set({ 961 gallery: new wp.media.view.Settings.Gallery({ 962 controller: this, 963 model: library.gallery, 964 priority: 40 965 }) 966 }); 967 968 browser.toolbar.set( 'reverse', { 969 text: l10n.reverseOrder, 970 priority: 80, 971 972 click: function() { 973 library.reset( library.toArray().reverse() ); 974 } 975 }); 398 976 } 399 977 }); 400 978 401 module.exports = Region; 402 403 404 /***/ }), 405 /* 28 */ 406 /***/ (function(module, exports) { 407 979 module.exports = GalleryEdit; 980 981 },{}],10:[function(require,module,exports){ 408 982 /** 409 * wp.media.controller.StateMachine 410 * 411 * A state machine keeps track of state. It is in one state at a time, 412 * and can change from one state to another. 413 * 414 * States are stored as models in a Backbone collection. 415 * 416 * @since 3.5.0 983 * wp.media.controller.ImageDetails 984 * 985 * A state for editing the attachment display settings of an image that's been 986 * inserted into the editor. 417 987 * 418 988 * @class 989 * @augments wp.media.controller.State 419 990 * @augments Backbone.Model 420 * @mixin 421 * @mixes Backbone.Events 422 * 423 * @param {Array} states 991 * 992 * @param {object} [attributes] The attributes hash passed to the state. 993 * @param {string} [attributes.id=image-details] Unique identifier. 994 * @param {string} [attributes.title=Image Details] Title for the state. Displays in the frame's title region. 995 * @param {wp.media.model.Attachment} attributes.image The image's model. 996 * @param {string|false} [attributes.content=image-details] Initial mode for the content region. 997 * @param {string|false} [attributes.menu=false] Initial mode for the menu region. 998 * @param {string|false} [attributes.router=false] Initial mode for the router region. 999 * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region. 1000 * @param {boolean} [attributes.editing=false] Unused. 1001 * @param {int} [attributes.priority=60] Unused. 1002 * 1003 * @todo This state inherits some defaults from media.controller.Library.prototype.defaults, 1004 * however this may not do anything. 424 1005 */ 425 var StateMachine = function( states ) { 426 // @todo This is dead code. The states collection gets created in media.view.Frame._createStates. 427 this.states = new Backbone.Collection( states ); 428 }; 429 430 // Use Backbone's self-propagating `extend` inheritance method. 431 StateMachine.extend = Backbone.Model.extend; 432 433 _.extend( StateMachine.prototype, Backbone.Events, { 434 /** 435 * Fetch a state. 1006 var State = wp.media.controller.State, 1007 Library = wp.media.controller.Library, 1008 l10n = wp.media.view.l10n, 1009 ImageDetails; 1010 1011 ImageDetails = State.extend({ 1012 defaults: _.defaults({ 1013 id: 'image-details', 1014 title: l10n.imageDetailsTitle, 1015 content: 'image-details', 1016 menu: false, 1017 router: false, 1018 toolbar: 'image-details', 1019 editing: false, 1020 priority: 60 1021 }, Library.prototype.defaults ), 1022 1023 /** 1024 * @since 3.9.0 436 1025 * 437 * If no `id` is provided, returns the active state. 438 * 439 * Implicitly creates states. 440 * 441 * Ensure that the `states` collection exists so the `StateMachine` 442 * can be used as a mixin. 443 * 444 * @since 3.5.0 445 * 446 * @param {string} id 447 * @returns {wp.media.controller.State} Returns a State model 448 * from the StateMachine collection 449 */ 450 state: function( id ) { 451 this.states = this.states || new Backbone.Collection(); 452 453 // Default to the active state. 454 id = id || this._state; 455 456 if ( id && ! this.states.get( id ) ) { 457 this.states.add({ id: id }); 458 } 459 return this.states.get( id ); 460 }, 461 462 /** 463 * Sets the active state. 464 * 465 * Bail if we're trying to select the current state, if we haven't 466 * created the `states` collection, or are trying to select a state 467 * that does not exist. 468 * 469 * @since 3.5.0 470 * 471 * @param {string} id 472 * 473 * @fires wp.media.controller.State#deactivate 474 * @fires wp.media.controller.State#activate 475 * 476 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining 477 */ 478 setState: function( id ) { 479 var previous = this.state(); 480 481 if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) { 482 return this; 483 } 484 485 if ( previous ) { 486 previous.trigger('deactivate'); 487 this._lastState = previous.id; 488 } 489 490 this._state = id; 491 this.state().trigger('activate'); 492 493 return this; 494 }, 495 496 /** 497 * Returns the previous active state. 498 * 499 * Call the `state()` method with no parameters to retrieve the current 500 * active state. 501 * 502 * @since 3.5.0 503 * 504 * @returns {wp.media.controller.State} Returns a State model 505 * from the StateMachine collection 506 */ 507 lastState: function() { 508 if ( this._lastState ) { 509 return this.state( this._lastState ); 510 } 1026 * @param options Attributes 1027 */ 1028 initialize: function( options ) { 1029 this.image = options.image; 1030 State.prototype.initialize.apply( this, arguments ); 1031 }, 1032 1033 /** 1034 * @since 3.9.0 1035 */ 1036 activate: function() { 1037 this.frame.modal.$el.addClass('image-details'); 511 1038 } 512 1039 }); 513 1040 514 // Map all event binding and triggering on a StateMachine to its `states` collection. 515 _.each([ 'on', 'off', 'trigger' ], function( method ) { 516 /** 517 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining. 518 */ 519 StateMachine.prototype[ method ] = function() { 520 // Ensure that the `states` collection exists so the `StateMachine` 521 // can be used as a mixin. 522 this.states = this.states || new Backbone.Collection(); 523 // Forward the method to the `states` collection. 524 this.states[ method ].apply( this.states, arguments ); 525 return this; 526 }; 527 }); 528 529 module.exports = StateMachine; 530 531 532 /***/ }), 533 /* 29 */ 534 /***/ (function(module, exports) { 535 536 /** 537 * wp.media.controller.State 538 * 539 * A state is a step in a workflow that when set will trigger the controllers 540 * for the regions to be updated as specified in the frame. 541 * 542 * A state has an event-driven lifecycle: 543 * 544 * 'ready' triggers when a state is added to a state machine's collection. 545 * 'activate' triggers when a state is activated by a state machine. 546 * 'deactivate' triggers when a state is deactivated by a state machine. 547 * 'reset' is not triggered automatically. It should be invoked by the 548 * proper controller to reset the state to its default. 549 * 550 * @class 551 * @augments Backbone.Model 552 */ 553 var State = Backbone.Model.extend({ 554 /** 555 * Constructor. 556 * 557 * @since 3.5.0 558 */ 559 constructor: function() { 560 this.on( 'activate', this._preActivate, this ); 561 this.on( 'activate', this.activate, this ); 562 this.on( 'activate', this._postActivate, this ); 563 this.on( 'deactivate', this._deactivate, this ); 564 this.on( 'deactivate', this.deactivate, this ); 565 this.on( 'reset', this.reset, this ); 566 this.on( 'ready', this._ready, this ); 567 this.on( 'ready', this.ready, this ); 568 /** 569 * Call parent constructor with passed arguments 570 */ 571 Backbone.Model.apply( this, arguments ); 572 this.on( 'change:menu', this._updateMenu, this ); 573 }, 574 /** 575 * Ready event callback. 576 * 577 * @abstract 578 * @since 3.5.0 579 */ 580 ready: function() {}, 581 582 /** 583 * Activate event callback. 584 * 585 * @abstract 586 * @since 3.5.0 587 */ 588 activate: function() {}, 589 590 /** 591 * Deactivate event callback. 592 * 593 * @abstract 594 * @since 3.5.0 595 */ 596 deactivate: function() {}, 597 598 /** 599 * Reset event callback. 600 * 601 * @abstract 602 * @since 3.5.0 603 */ 604 reset: function() {}, 605 606 /** 607 * @access private 608 * @since 3.5.0 609 */ 610 _ready: function() { 611 this._updateMenu(); 612 }, 613 614 /** 615 * @access private 616 * @since 3.5.0 617 */ 618 _preActivate: function() { 619 this.active = true; 620 }, 621 622 /** 623 * @access private 624 * @since 3.5.0 625 */ 626 _postActivate: function() { 627 this.on( 'change:menu', this._menu, this ); 628 this.on( 'change:titleMode', this._title, this ); 629 this.on( 'change:content', this._content, this ); 630 this.on( 'change:toolbar', this._toolbar, this ); 631 632 this.frame.on( 'title:render:default', this._renderTitle, this ); 633 634 this._title(); 635 this._menu(); 636 this._toolbar(); 637 this._content(); 638 this._router(); 639 }, 640 641 /** 642 * @access private 643 * @since 3.5.0 644 */ 645 _deactivate: function() { 646 this.active = false; 647 648 this.frame.off( 'title:render:default', this._renderTitle, this ); 649 650 this.off( 'change:menu', this._menu, this ); 651 this.off( 'change:titleMode', this._title, this ); 652 this.off( 'change:content', this._content, this ); 653 this.off( 'change:toolbar', this._toolbar, this ); 654 }, 655 656 /** 657 * @access private 658 * @since 3.5.0 659 */ 660 _title: function() { 661 this.frame.title.render( this.get('titleMode') || 'default' ); 662 }, 663 664 /** 665 * @access private 666 * @since 3.5.0 667 */ 668 _renderTitle: function( view ) { 669 view.$el.text( this.get('title') || '' ); 670 }, 671 672 /** 673 * @access private 674 * @since 3.5.0 675 */ 676 _router: function() { 677 var router = this.frame.router, 678 mode = this.get('router'), 679 view; 680 681 this.frame.$el.toggleClass( 'hide-router', ! mode ); 682 if ( ! mode ) { 683 return; 684 } 685 686 this.frame.router.render( mode ); 687 688 view = router.get(); 689 if ( view && view.select ) { 690 view.select( this.frame.content.mode() ); 691 } 692 }, 693 694 /** 695 * @access private 696 * @since 3.5.0 697 */ 698 _menu: function() { 699 var menu = this.frame.menu, 700 mode = this.get('menu'), 701 view; 702 703 this.frame.$el.toggleClass( 'hide-menu', ! mode ); 704 if ( ! mode ) { 705 return; 706 } 707 708 menu.mode( mode ); 709 710 view = menu.get(); 711 if ( view && view.select ) { 712 view.select( this.id ); 713 } 714 }, 715 716 /** 717 * @access private 718 * @since 3.5.0 719 */ 720 _updateMenu: function() { 721 var previous = this.previous('menu'), 722 menu = this.get('menu'); 723 724 if ( previous ) { 725 this.frame.off( 'menu:render:' + previous, this._renderMenu, this ); 726 } 727 728 if ( menu ) { 729 this.frame.on( 'menu:render:' + menu, this._renderMenu, this ); 730 } 731 }, 732 733 /** 734 * Create a view in the media menu for the state. 735 * 736 * @access private 737 * @since 3.5.0 738 * 739 * @param {media.view.Menu} view The menu view. 740 */ 741 _renderMenu: function( view ) { 742 var menuItem = this.get('menuItem'), 743 title = this.get('title'), 744 priority = this.get('priority'); 745 746 if ( ! menuItem && title ) { 747 menuItem = { text: title }; 748 749 if ( priority ) { 750 menuItem.priority = priority; 751 } 752 } 753 754 if ( ! menuItem ) { 755 return; 756 } 757 758 view.set( this.id, menuItem ); 759 } 760 }); 761 762 _.each(['toolbar','content'], function( region ) { 763 /** 764 * @access private 765 */ 766 State.prototype[ '_' + region ] = function() { 767 var mode = this.get( region ); 768 if ( mode ) { 769 this.frame[ region ].render( mode ); 770 } 771 }; 772 }); 773 774 module.exports = State; 775 776 777 /***/ }), 778 /* 30 */ 779 /***/ (function(module, exports) { 780 781 /** 782 * wp.media.selectionSync 783 * 784 * Sync an attachments selection in a state with another state. 785 * 786 * Allows for selecting multiple images in the Insert Media workflow, and then 787 * switching to the Insert Gallery workflow while preserving the attachments selection. 788 * 789 * @mixin 790 */ 791 var selectionSync = { 792 /** 793 * @since 3.5.0 794 */ 795 syncSelection: function() { 796 var selection = this.get('selection'), 797 manager = this.frame._selection; 798 799 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 800 return; 801 } 802 803 // If the selection supports multiple items, validate the stored 804 // attachments based on the new selection's conditions. Record 805 // the attachments that are not included; we'll maintain a 806 // reference to those. Other attachments are considered in flux. 807 if ( selection.multiple ) { 808 selection.reset( [], { silent: true }); 809 selection.validateAll( manager.attachments ); 810 manager.difference = _.difference( manager.attachments.models, selection.models ); 811 } 812 813 // Sync the selection's single item with the master. 814 selection.single( manager.single ); 815 }, 816 817 /** 818 * Record the currently active attachments, which is a combination 819 * of the selection's attachments and the set of selected 820 * attachments that this specific selection considered invalid. 821 * Reset the difference and record the single attachment. 822 * 823 * @since 3.5.0 824 */ 825 recordSelection: function() { 826 var selection = this.get('selection'), 827 manager = this.frame._selection; 828 829 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 830 return; 831 } 832 833 if ( selection.multiple ) { 834 manager.attachments.reset( selection.toArray().concat( manager.difference ) ); 835 manager.difference = []; 836 } else { 837 manager.attachments.add( selection.toArray() ); 838 } 839 840 manager.single = selection._single; 841 } 842 }; 843 844 module.exports = selectionSync; 845 846 847 /***/ }), 848 /* 31 */ 849 /***/ (function(module, exports) { 850 1041 module.exports = ImageDetails; 1042 1043 },{}],11:[function(require,module,exports){ 851 1044 /** 852 1045 * wp.media.controller.Library … … 1120 1313 module.exports = Library; 1121 1314 1122 1123 /***/ }), 1124 /* 32 */ 1125 /***/ (function(module, exports) { 1126 1315 },{}],12:[function(require,module,exports){ 1127 1316 /** 1128 * wp.media.controller.ImageDetails 1129 * 1130 * A state for editing the attachment display settings of an image that's been 1131 * inserted into the editor. 1132 * 1133 * @class 1134 * @augments wp.media.controller.State 1135 * @augments Backbone.Model 1136 * 1137 * @param {object} [attributes] The attributes hash passed to the state. 1138 * @param {string} [attributes.id=image-details] Unique identifier. 1139 * @param {string} [attributes.title=Image Details] Title for the state. Displays in the frame's title region. 1140 * @param {wp.media.model.Attachment} attributes.image The image's model. 1141 * @param {string|false} [attributes.content=image-details] Initial mode for the content region. 1142 * @param {string|false} [attributes.menu=false] Initial mode for the menu region. 1143 * @param {string|false} [attributes.router=false] Initial mode for the router region. 1144 * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region. 1145 * @param {boolean} [attributes.editing=false] Unused. 1146 * @param {int} [attributes.priority=60] Unused. 1147 * 1148 * @todo This state inherits some defaults from media.controller.Library.prototype.defaults, 1149 * however this may not do anything. 1150 */ 1151 var State = wp.media.controller.State, 1152 Library = wp.media.controller.Library, 1153 l10n = wp.media.view.l10n, 1154 ImageDetails; 1155 1156 ImageDetails = State.extend({ 1157 defaults: _.defaults({ 1158 id: 'image-details', 1159 title: l10n.imageDetailsTitle, 1160 content: 'image-details', 1161 menu: false, 1162 router: false, 1163 toolbar: 'image-details', 1164 editing: false, 1165 priority: 60 1166 }, Library.prototype.defaults ), 1167 1168 /** 1169 * @since 3.9.0 1170 * 1171 * @param options Attributes 1172 */ 1173 initialize: function( options ) { 1174 this.image = options.image; 1175 State.prototype.initialize.apply( this, arguments ); 1176 }, 1177 1178 /** 1179 * @since 3.9.0 1180 */ 1181 activate: function() { 1182 this.frame.modal.$el.addClass('image-details'); 1183 } 1184 }); 1185 1186 module.exports = ImageDetails; 1187 1188 1189 /***/ }), 1190 /* 33 */ 1191 /***/ (function(module, exports) { 1192 1193 /** 1194 * wp.media.controller.GalleryEdit 1195 * 1196 * A state for editing a gallery's images and settings. 1317 * wp.media.controller.MediaLibrary 1197 1318 * 1198 1319 * @class … … 1200 1321 * @augments wp.media.controller.State 1201 1322 * @augments Backbone.Model 1202 *1203 * @param {object} [attributes] The attributes hash passed to the state.1204 * @param {string} [attributes.id=gallery-edit] Unique identifier.1205 * @param {string} [attributes.title=Edit Gallery] Title for the state. Displays in the frame's title region.1206 * @param {wp.media.model.Attachments} [attributes.library] The collection of attachments in the gallery.1207 * If one is not supplied, an empty media.model.Selection collection is created.1208 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled.1209 * @param {boolean} [attributes.searchable=false] Whether the library is searchable.1210 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection.1211 * @param {boolean} [attributes.date=true] Whether to show the date filter in the browser's toolbar.1212 * @param {string|false} [attributes.content=browse] Initial mode for the content region.1213 * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region.1214 * @param {boolean} [attributes.describe=true] Whether to offer UI to describe attachments - e.g. captioning images in a gallery.1215 * @param {boolean} [attributes.displaySettings=true] Whether to show the attachment display settings interface.1216 * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable.1217 * @param {int} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments.1218 * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance.1219 * @param {int} [attributes.priority=60] The priority for the state link in the media menu.1220 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state.1221 * Defaults to false for this state, because the library passed in *is* the selection.1222 * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`.1223 * If none supplied, defaults to wp.media.view.Attachment.EditLibrary.1224 1323 */ 1225 1324 var Library = wp.media.controller.Library, 1226 l10n = wp.media.view.l10n, 1227 GalleryEdit; 1228 1229 GalleryEdit = Library.extend({ 1230 defaults: { 1231 id: 'gallery-edit', 1232 title: l10n.editGalleryTitle, 1233 multiple: false, 1234 searchable: false, 1235 sortable: true, 1236 date: false, 1237 display: false, 1238 content: 'browse', 1239 toolbar: 'gallery-edit', 1240 describe: true, 1241 displaySettings: true, 1242 dragInfo: true, 1243 idealColumnWidth: 170, 1244 editing: false, 1245 priority: 60, 1246 syncSelection: false 1247 }, 1248 1249 /** 1250 * @since 3.5.0 1251 */ 1252 initialize: function() { 1253 // If we haven't been provided a `library`, create a `Selection`. 1254 if ( ! this.get('library') ) { 1255 this.set( 'library', new wp.media.model.Selection() ); 1256 } 1257 1258 // The single `Attachment` view to be used in the `Attachments` view. 1259 if ( ! this.get('AttachmentView') ) { 1260 this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); 1261 } 1325 MediaLibrary; 1326 1327 MediaLibrary = Library.extend({ 1328 defaults: _.defaults({ 1329 // Attachments browser defaults. @see media.view.AttachmentsBrowser 1330 filterable: 'uploaded', 1331 1332 displaySettings: false, 1333 priority: 80, 1334 syncSelection: false 1335 }, Library.prototype.defaults ), 1336 1337 /** 1338 * @since 3.9.0 1339 * 1340 * @param options 1341 */ 1342 initialize: function( options ) { 1343 this.media = options.media; 1344 this.type = options.type; 1345 this.set( 'library', wp.media.query({ type: this.type }) ); 1262 1346 1263 1347 Library.prototype.initialize.apply( this, arguments ); … … 1265 1349 1266 1350 /** 1267 * @since 3. 5.01351 * @since 3.9.0 1268 1352 */ 1269 1353 activate: function() { 1270 var library = this.get('library'); 1271 1272 // Limit the library to images only. 1273 library.props.set( 'type', 'image' ); 1274 1275 // Watch for uploaded attachments. 1276 this.get('library').observe( wp.Uploader.queue ); 1277 1278 this.frame.on( 'content:render:browse', this.gallerySettings, this ); 1279 1280 Library.prototype.activate.apply( this, arguments ); 1281 }, 1282 1283 /** 1284 * @since 3.5.0 1285 */ 1286 deactivate: function() { 1287 // Stop watching for uploaded attachments. 1288 this.get('library').unobserve( wp.Uploader.queue ); 1289 1290 this.frame.off( 'content:render:browse', this.gallerySettings, this ); 1291 1292 Library.prototype.deactivate.apply( this, arguments ); 1293 }, 1294 1295 /** 1296 * @since 3.5.0 1297 * 1298 * @param browser 1299 */ 1300 gallerySettings: function( browser ) { 1301 if ( ! this.get('displaySettings') ) { 1302 return; 1303 } 1304 1305 var library = this.get('library'); 1306 1307 if ( ! library || ! browser ) { 1308 return; 1309 } 1310 1311 library.gallery = library.gallery || new Backbone.Model(); 1312 1313 browser.sidebar.set({ 1314 gallery: new wp.media.view.Settings.Gallery({ 1315 controller: this, 1316 model: library.gallery, 1317 priority: 40 1318 }) 1319 }); 1320 1321 browser.toolbar.set( 'reverse', { 1322 text: l10n.reverseOrder, 1323 priority: 80, 1324 1325 click: function() { 1326 library.reset( library.toArray().reverse() ); 1327 } 1328 }); 1329 } 1330 }); 1331 1332 module.exports = GalleryEdit; 1333 1334 1335 /***/ }), 1336 /* 34 */ 1337 /***/ (function(module, exports) { 1338 1339 /** 1340 * wp.media.controller.GalleryAdd 1341 * 1342 * A state for selecting more images to add to a gallery. 1343 * 1344 * @class 1345 * @augments wp.media.controller.Library 1346 * @augments wp.media.controller.State 1347 * @augments Backbone.Model 1348 * 1349 * @param {object} [attributes] The attributes hash passed to the state. 1350 * @param {string} [attributes.id=gallery-library] Unique identifier. 1351 * @param {string} [attributes.title=Add to Gallery] Title for the state. Displays in the frame's title region. 1352 * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. 1353 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1354 * If one is not supplied, a collection of all images will be created. 1355 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 1356 * Accepts 'all', 'uploaded', or 'unattached'. 1357 * @param {string} [attributes.menu=gallery] Initial mode for the menu region. 1358 * @param {string} [attributes.content=upload] Initial mode for the content region. 1359 * Overridden by persistent user setting if 'contentUserSetting' is true. 1360 * @param {string} [attributes.router=browse] Initial mode for the router region. 1361 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 1362 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1363 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1364 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1365 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1366 * @param {int} [attributes.priority=100] The priority for the state link in the media menu. 1367 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 1368 * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. 1369 */ 1370 var Selection = wp.media.model.Selection, 1371 Library = wp.media.controller.Library, 1372 l10n = wp.media.view.l10n, 1373 GalleryAdd; 1374 1375 GalleryAdd = Library.extend({ 1376 defaults: _.defaults({ 1377 id: 'gallery-library', 1378 title: l10n.addToGalleryTitle, 1379 multiple: 'add', 1380 filterable: 'uploaded', 1381 menu: 'gallery', 1382 toolbar: 'gallery-add', 1383 priority: 100, 1384 syncSelection: false 1385 }, Library.prototype.defaults ), 1386 1387 /** 1388 * @since 3.5.0 1389 */ 1390 initialize: function() { 1391 // If a library wasn't supplied, create a library of images. 1392 if ( ! this.get('library') ) { 1393 this.set( 'library', wp.media.query({ type: 'image' }) ); 1394 } 1395 1396 Library.prototype.initialize.apply( this, arguments ); 1397 }, 1398 1399 /** 1400 * @since 3.5.0 1401 */ 1402 activate: function() { 1403 var library = this.get('library'), 1404 edit = this.frame.state('gallery-edit').get('library'); 1405 1406 if ( this.editLibrary && this.editLibrary !== edit ) { 1407 library.unobserve( this.editLibrary ); 1408 } 1409 1410 // Accepts attachments that exist in the original library and 1411 // that do not exist in gallery's library. 1412 library.validator = function( attachment ) { 1413 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments ); 1414 }; 1415 1416 // Reset the library to ensure that all attachments are re-added 1417 // to the collection. Do so silently, as calling `observe` will 1418 // trigger the `reset` event. 1419 library.reset( library.mirroring.models, { silent: true }); 1420 library.observe( edit ); 1421 this.editLibrary = edit; 1422 1354 // @todo this should use this.frame. 1355 if ( wp.media.frame.lastMime ) { 1356 this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) ); 1357 delete wp.media.frame.lastMime; 1358 } 1423 1359 Library.prototype.activate.apply( this, arguments ); 1424 1360 } 1425 1361 }); 1426 1362 1427 module.exports = GalleryAdd; 1428 1429 1430 /***/ }), 1431 /* 35 */ 1432 /***/ (function(module, exports) { 1433 1363 module.exports = MediaLibrary; 1364 1365 },{}],13:[function(require,module,exports){ 1434 1366 /** 1435 * wp.media.controller.CollectionEdit 1436 * 1437 * A state for editing a collection, which is used by audio and video playlists, 1438 * and can be used for other collections. 1367 * wp.media.controller.Region 1368 * 1369 * A region is a persistent application layout area. 1370 * 1371 * A region assumes one mode at any time, and can be switched to another. 1372 * 1373 * When mode changes, events are triggered on the region's parent view. 1374 * The parent view will listen to specific events and fill the region with an 1375 * appropriate view depending on mode. For example, a frame listens for the 1376 * 'browse' mode t be activated on the 'content' view and then fills the region 1377 * with an AttachmentsBrowser view. 1439 1378 * 1440 1379 * @class 1441 * @augments wp.media.controller.Library 1442 * @augments wp.media.controller.State 1443 * @augments Backbone.Model 1444 * 1445 * @param {object} [attributes] The attributes hash passed to the state. 1446 * @param {string} attributes.title Title for the state. Displays in the media menu and the frame's title region. 1447 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to edit. 1448 * If one is not supplied, an empty media.model.Selection collection is created. 1449 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 1450 * @param {string} [attributes.content=browse] Initial mode for the content region. 1451 * @param {string} attributes.menu Initial mode for the menu region. @todo this needs a better explanation. 1452 * @param {boolean} [attributes.searchable=false] Whether the library is searchable. 1453 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1454 * @param {boolean} [attributes.date=true] Whether to show the date filter in the browser's toolbar. 1455 * @param {boolean} [attributes.describe=true] Whether to offer UI to describe the attachments - e.g. captioning images in a gallery. 1456 * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable. 1457 * @param {boolean} [attributes.dragInfoText] Instructional text about the attachments being sortable. 1458 * @param {int} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments. 1459 * @param {boolean} [attributes.editing=false] Whether the gallery is being created, or editing an existing instance. 1460 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 1461 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 1462 * Defaults to false for this state, because the library passed in *is* the selection. 1463 * @param {view} [attributes.SettingsView] The view to edit the collection instance settings (e.g. Playlist settings with "Show tracklist" checkbox). 1464 * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`. 1465 * If none supplied, defaults to wp.media.view.Attachment.EditLibrary. 1466 * @param {string} attributes.type The collection's media type. (e.g. 'video'). 1467 * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). 1380 * 1381 * @param {object} options Options hash for the region. 1382 * @param {string} options.id Unique identifier for the region. 1383 * @param {Backbone.View} options.view A parent view the region exists within. 1384 * @param {string} options.selector jQuery selector for the region within the parent view. 1468 1385 */ 1469 var Library = wp.media.controller.Library, 1470 l10n = wp.media.view.l10n, 1471 $ = jQuery, 1472 CollectionEdit; 1473 1474 CollectionEdit = Library.extend({ 1475 defaults: { 1476 multiple: false, 1477 sortable: true, 1478 date: false, 1479 searchable: false, 1480 content: 'browse', 1481 describe: true, 1482 dragInfo: true, 1483 idealColumnWidth: 170, 1484 editing: false, 1485 priority: 60, 1486 SettingsView: false, 1487 syncSelection: false 1488 }, 1489 1490 /** 1491 * @since 3.9.0 1492 */ 1493 initialize: function() { 1494 var collectionType = this.get('collectionType'); 1495 1496 if ( 'video' === this.get( 'type' ) ) { 1497 collectionType = 'video-' + collectionType; 1498 } 1499 1500 this.set( 'id', collectionType + '-edit' ); 1501 this.set( 'toolbar', collectionType + '-edit' ); 1502 1503 // If we haven't been provided a `library`, create a `Selection`. 1504 if ( ! this.get('library') ) { 1505 this.set( 'library', new wp.media.model.Selection() ); 1506 } 1507 // The single `Attachment` view to be used in the `Attachments` view. 1508 if ( ! this.get('AttachmentView') ) { 1509 this.set( 'AttachmentView', wp.media.view.Attachment.EditLibrary ); 1510 } 1511 Library.prototype.initialize.apply( this, arguments ); 1512 }, 1513 1514 /** 1515 * @since 3.9.0 1516 */ 1517 activate: function() { 1518 var library = this.get('library'); 1519 1520 // Limit the library to images only. 1521 library.props.set( 'type', this.get( 'type' ) ); 1522 1523 // Watch for uploaded attachments. 1524 this.get('library').observe( wp.Uploader.queue ); 1525 1526 this.frame.on( 'content:render:browse', this.renderSettings, this ); 1527 1528 Library.prototype.activate.apply( this, arguments ); 1529 }, 1530 1531 /** 1532 * @since 3.9.0 1533 */ 1534 deactivate: function() { 1535 // Stop watching for uploaded attachments. 1536 this.get('library').unobserve( wp.Uploader.queue ); 1537 1538 this.frame.off( 'content:render:browse', this.renderSettings, this ); 1539 1540 Library.prototype.deactivate.apply( this, arguments ); 1541 }, 1542 1543 /** 1544 * Render the collection embed settings view in the browser sidebar. 1386 var Region = function( options ) { 1387 _.extend( this, _.pick( options || {}, 'id', 'view', 'selector' ) ); 1388 }; 1389 1390 // Use Backbone's self-propagating `extend` inheritance method. 1391 Region.extend = Backbone.Model.extend; 1392 1393 _.extend( Region.prototype, { 1394 /** 1395 * Activate a mode. 1545 1396 * 1546 * @todo This is against the pattern elsewhere in media. Typically the frame 1547 * is responsible for adding region mode callbacks. Explain. 1397 * @since 3.5.0 1548 1398 * 1549 * @ since 3.9.01399 * @param {string} mode 1550 1400 * 1551 * @param {wp.media.view.attachmentsBrowser} The attachments browser view. 1552 */ 1553 renderSettings: function( attachmentsBrowserView ) { 1554 var library = this.get('library'), 1555 collectionType = this.get('collectionType'), 1556 dragInfoText = this.get('dragInfoText'), 1557 SettingsView = this.get('SettingsView'), 1558 obj = {}; 1559 1560 if ( ! library || ! attachmentsBrowserView ) { 1401 * @fires this.view#{this.id}:activate:{this._mode} 1402 * @fires this.view#{this.id}:activate 1403 * @fires this.view#{this.id}:deactivate:{this._mode} 1404 * @fires this.view#{this.id}:deactivate 1405 * 1406 * @returns {wp.media.controller.Region} Returns itself to allow chaining. 1407 */ 1408 mode: function( mode ) { 1409 if ( ! mode ) { 1410 return this._mode; 1411 } 1412 // Bail if we're trying to change to the current mode. 1413 if ( mode === this._mode ) { 1414 return this; 1415 } 1416 1417 /** 1418 * Region mode deactivation event. 1419 * 1420 * @event this.view#{this.id}:deactivate:{this._mode} 1421 * @event this.view#{this.id}:deactivate 1422 */ 1423 this.trigger('deactivate'); 1424 1425 this._mode = mode; 1426 this.render( mode ); 1427 1428 /** 1429 * Region mode activation event. 1430 * 1431 * @event this.view#{this.id}:activate:{this._mode} 1432 * @event this.view#{this.id}:activate 1433 */ 1434 this.trigger('activate'); 1435 return this; 1436 }, 1437 /** 1438 * Render a mode. 1439 * 1440 * @since 3.5.0 1441 * 1442 * @param {string} mode 1443 * 1444 * @fires this.view#{this.id}:create:{this._mode} 1445 * @fires this.view#{this.id}:create 1446 * @fires this.view#{this.id}:render:{this._mode} 1447 * @fires this.view#{this.id}:render 1448 * 1449 * @returns {wp.media.controller.Region} Returns itself to allow chaining 1450 */ 1451 render: function( mode ) { 1452 // If the mode isn't active, activate it. 1453 if ( mode && mode !== this._mode ) { 1454 return this.mode( mode ); 1455 } 1456 1457 var set = { view: null }, 1458 view; 1459 1460 /** 1461 * Create region view event. 1462 * 1463 * Region view creation takes place in an event callback on the frame. 1464 * 1465 * @event this.view#{this.id}:create:{this._mode} 1466 * @event this.view#{this.id}:create 1467 */ 1468 this.trigger( 'create', set ); 1469 view = set.view; 1470 1471 /** 1472 * Render region view event. 1473 * 1474 * Region view creation takes place in an event callback on the frame. 1475 * 1476 * @event this.view#{this.id}:create:{this._mode} 1477 * @event this.view#{this.id}:create 1478 */ 1479 this.trigger( 'render', view ); 1480 if ( view ) { 1481 this.set( view ); 1482 } 1483 return this; 1484 }, 1485 1486 /** 1487 * Get the region's view. 1488 * 1489 * @since 3.5.0 1490 * 1491 * @returns {wp.media.View} 1492 */ 1493 get: function() { 1494 return this.view.views.first( this.selector ); 1495 }, 1496 1497 /** 1498 * Set the region's view as a subview of the frame. 1499 * 1500 * @since 3.5.0 1501 * 1502 * @param {Array|Object} views 1503 * @param {Object} [options={}] 1504 * @returns {wp.Backbone.Subviews} Subviews is returned to allow chaining 1505 */ 1506 set: function( views, options ) { 1507 if ( options ) { 1508 options.add = false; 1509 } 1510 return this.view.views.set( this.selector, views, options ); 1511 }, 1512 1513 /** 1514 * Trigger regional view events on the frame. 1515 * 1516 * @since 3.5.0 1517 * 1518 * @param {string} event 1519 * @returns {undefined|wp.media.controller.Region} Returns itself to allow chaining. 1520 */ 1521 trigger: function( event ) { 1522 var base, args; 1523 1524 if ( ! this._mode ) { 1561 1525 return; 1562 1526 } 1563 1527 1564 library[ collectionType ] = library[ collectionType ] || new Backbone.Model(); 1565 1566 obj[ collectionType ] = new SettingsView({ 1567 controller: this, 1568 model: library[ collectionType ], 1569 priority: 40 1570 }); 1571 1572 attachmentsBrowserView.sidebar.set( obj ); 1573 1574 if ( dragInfoText ) { 1575 attachmentsBrowserView.toolbar.set( 'dragInfo', new wp.media.View({ 1576 el: $( '<div class="instructions">' + dragInfoText + '</div>' )[0], 1577 priority: -40 1578 }) ); 1579 } 1580 1581 // Add the 'Reverse order' button to the toolbar. 1582 attachmentsBrowserView.toolbar.set( 'reverse', { 1583 text: l10n.reverseOrder, 1584 priority: 80, 1585 1586 click: function() { 1587 library.reset( library.toArray().reverse() ); 1588 } 1589 }); 1528 args = _.toArray( arguments ); 1529 base = this.id + ':' + event; 1530 1531 // Trigger `{this.id}:{event}:{this._mode}` event on the frame. 1532 args[0] = base + ':' + this._mode; 1533 this.view.trigger.apply( this.view, args ); 1534 1535 // Trigger `{this.id}:{event}` event on the frame. 1536 args[0] = base; 1537 this.view.trigger.apply( this.view, args ); 1538 return this; 1590 1539 } 1591 1540 }); 1592 1541 1593 module.exports = CollectionEdit; 1594 1595 1596 /***/ }), 1597 /* 36 */ 1598 /***/ (function(module, exports) { 1599 1600 /** 1601 * wp.media.controller.CollectionAdd 1602 * 1603 * A state for adding attachments to a collection (e.g. video playlist). 1604 * 1605 * @class 1606 * @augments wp.media.controller.Library 1607 * @augments wp.media.controller.State 1608 * @augments Backbone.Model 1609 * 1610 * @param {object} [attributes] The attributes hash passed to the state. 1611 * @param {string} [attributes.id=library] Unique identifier. 1612 * @param {string} attributes.title Title for the state. Displays in the frame's title region. 1613 * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. 1614 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1615 * If one is not supplied, a collection of attachments of the specified type will be created. 1616 * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. 1617 * Accepts 'all', 'uploaded', or 'unattached'. 1618 * @param {string} [attributes.menu=gallery] Initial mode for the menu region. 1619 * @param {string} [attributes.content=upload] Initial mode for the content region. 1620 * Overridden by persistent user setting if 'contentUserSetting' is true. 1621 * @param {string} [attributes.router=browse] Initial mode for the router region. 1622 * @param {string} [attributes.toolbar=gallery-add] Initial mode for the toolbar region. 1623 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1624 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1625 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1626 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1627 * @param {int} [attributes.priority=100] The priority for the state link in the media menu. 1628 * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. 1629 * Defaults to false because for this state, because the library of the Edit Gallery state is the selection. 1630 * @param {string} attributes.type The collection's media type. (e.g. 'video'). 1631 * @param {string} attributes.collectionType The collection type. (e.g. 'playlist'). 1632 */ 1633 var Selection = wp.media.model.Selection, 1634 Library = wp.media.controller.Library, 1635 CollectionAdd; 1636 1637 CollectionAdd = Library.extend({ 1638 defaults: _.defaults( { 1639 // Selection defaults. @see media.model.Selection 1640 multiple: 'add', 1641 // Attachments browser defaults. @see media.view.AttachmentsBrowser 1642 filterable: 'uploaded', 1643 1644 priority: 100, 1645 syncSelection: false 1646 }, Library.prototype.defaults ), 1647 1648 /** 1649 * @since 3.9.0 1650 */ 1651 initialize: function() { 1652 var collectionType = this.get('collectionType'); 1653 1654 if ( 'video' === this.get( 'type' ) ) { 1655 collectionType = 'video-' + collectionType; 1656 } 1657 1658 this.set( 'id', collectionType + '-library' ); 1659 this.set( 'toolbar', collectionType + '-add' ); 1660 this.set( 'menu', collectionType ); 1661 1662 // If we haven't been provided a `library`, create a `Selection`. 1663 if ( ! this.get('library') ) { 1664 this.set( 'library', wp.media.query({ type: this.get('type') }) ); 1665 } 1666 Library.prototype.initialize.apply( this, arguments ); 1667 }, 1668 1669 /** 1670 * @since 3.9.0 1671 */ 1672 activate: function() { 1673 var library = this.get('library'), 1674 editLibrary = this.get('editLibrary'), 1675 edit = this.frame.state( this.get('collectionType') + '-edit' ).get('library'); 1676 1677 if ( editLibrary && editLibrary !== edit ) { 1678 library.unobserve( editLibrary ); 1679 } 1680 1681 // Accepts attachments that exist in the original library and 1682 // that do not exist in gallery's library. 1683 library.validator = function( attachment ) { 1684 return !! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && Selection.prototype.validator.apply( this, arguments ); 1685 }; 1686 1687 // Reset the library to ensure that all attachments are re-added 1688 // to the collection. Do so silently, as calling `observe` will 1689 // trigger the `reset` event. 1690 library.reset( library.mirroring.models, { silent: true }); 1691 library.observe( edit ); 1692 this.set('editLibrary', edit); 1693 1694 Library.prototype.activate.apply( this, arguments ); 1695 } 1696 }); 1697 1698 module.exports = CollectionAdd; 1699 1700 1701 /***/ }), 1702 /* 37 */ 1703 /***/ (function(module, exports) { 1704 1705 /** 1706 * wp.media.controller.FeaturedImage 1707 * 1708 * A state for selecting a featured image for a post. 1709 * 1710 * @class 1711 * @augments wp.media.controller.Library 1712 * @augments wp.media.controller.State 1713 * @augments Backbone.Model 1714 * 1715 * @param {object} [attributes] The attributes hash passed to the state. 1716 * @param {string} [attributes.id=featured-image] Unique identifier. 1717 * @param {string} [attributes.title=Set Featured Image] Title for the state. Displays in the media menu and the frame's title region. 1718 * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. 1719 * If one is not supplied, a collection of all images will be created. 1720 * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. 1721 * @param {string} [attributes.content=upload] Initial mode for the content region. 1722 * Overridden by persistent user setting if 'contentUserSetting' is true. 1723 * @param {string} [attributes.menu=default] Initial mode for the menu region. 1724 * @param {string} [attributes.router=browse] Initial mode for the router region. 1725 * @param {string} [attributes.toolbar=featured-image] Initial mode for the toolbar region. 1726 * @param {int} [attributes.priority=60] The priority for the state link in the media menu. 1727 * @param {boolean} [attributes.searchable=true] Whether the library is searchable. 1728 * @param {boolean|string} [attributes.filterable=false] Whether the library is filterable, and if so what filters should be shown. 1729 * Accepts 'all', 'uploaded', or 'unattached'. 1730 * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. 1731 * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. 1732 * @param {boolean} [attributes.describe=false] Whether to offer UI to describe attachments - e.g. captioning images in a gallery. 1733 * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. 1734 * @param {boolean} [attributes.syncSelection=true] Whether the Attachments selection should be persisted from the last state. 1735 */ 1736 var Attachment = wp.media.model.Attachment, 1737 Library = wp.media.controller.Library, 1738 l10n = wp.media.view.l10n, 1739 FeaturedImage; 1740 1741 FeaturedImage = Library.extend({ 1742 defaults: _.defaults({ 1743 id: 'featured-image', 1744 title: l10n.setFeaturedImageTitle, 1745 multiple: false, 1746 filterable: 'uploaded', 1747 toolbar: 'featured-image', 1748 priority: 60, 1749 syncSelection: true 1750 }, Library.prototype.defaults ), 1751 1752 /** 1753 * @since 3.5.0 1754 */ 1755 initialize: function() { 1756 var library, comparator; 1757 1758 // If we haven't been provided a `library`, create a `Selection`. 1759 if ( ! this.get('library') ) { 1760 this.set( 'library', wp.media.query({ type: 'image' }) ); 1761 } 1762 1763 Library.prototype.initialize.apply( this, arguments ); 1764 1765 library = this.get('library'); 1766 comparator = library.comparator; 1767 1768 // Overload the library's comparator to push items that are not in 1769 // the mirrored query to the front of the aggregate collection. 1770 library.comparator = function( a, b ) { 1771 var aInQuery = !! this.mirroring.get( a.cid ), 1772 bInQuery = !! this.mirroring.get( b.cid ); 1773 1774 if ( ! aInQuery && bInQuery ) { 1775 return -1; 1776 } else if ( aInQuery && ! bInQuery ) { 1777 return 1; 1778 } else { 1779 return comparator.apply( this, arguments ); 1780 } 1781 }; 1782 1783 // Add all items in the selection to the library, so any featured 1784 // images that are not initially loaded still appear. 1785 library.observe( this.get('selection') ); 1786 }, 1787 1788 /** 1789 * @since 3.5.0 1790 */ 1791 activate: function() { 1792 this.updateSelection(); 1793 this.frame.on( 'open', this.updateSelection, this ); 1794 1795 Library.prototype.activate.apply( this, arguments ); 1796 }, 1797 1798 /** 1799 * @since 3.5.0 1800 */ 1801 deactivate: function() { 1802 this.frame.off( 'open', this.updateSelection, this ); 1803 1804 Library.prototype.deactivate.apply( this, arguments ); 1805 }, 1806 1807 /** 1808 * @since 3.5.0 1809 */ 1810 updateSelection: function() { 1811 var selection = this.get('selection'), 1812 id = wp.media.view.settings.post.featuredImageId, 1813 attachment; 1814 1815 if ( '' !== id && -1 !== id ) { 1816 attachment = Attachment.get( id ); 1817 attachment.fetch(); 1818 } 1819 1820 selection.reset( attachment ? [ attachment ] : [] ); 1821 } 1822 }); 1823 1824 module.exports = FeaturedImage; 1825 1826 1827 /***/ }), 1828 /* 38 */ 1829 /***/ (function(module, exports) { 1830 1542 module.exports = Region; 1543 1544 },{}],14:[function(require,module,exports){ 1831 1545 /** 1832 1546 * wp.media.controller.ReplaceImage … … 1936 1650 module.exports = ReplaceImage; 1937 1651 1938 1939 /***/ }), 1940 /* 39 */ 1941 /***/ (function(module, exports) { 1942 1943 /** 1944 * wp.media.controller.EditImage 1945 * 1946 * A state for editing (cropping, etc.) an image. 1947 * 1948 * @class 1949 * @augments wp.media.controller.State 1950 * @augments Backbone.Model 1951 * 1952 * @param {object} attributes The attributes hash passed to the state. 1953 * @param {wp.media.model.Attachment} attributes.model The attachment. 1954 * @param {string} [attributes.id=edit-image] Unique identifier. 1955 * @param {string} [attributes.title=Edit Image] Title for the state. Displays in the media menu and the frame's title region. 1956 * @param {string} [attributes.content=edit-image] Initial mode for the content region. 1957 * @param {string} [attributes.toolbar=edit-image] Initial mode for the toolbar region. 1958 * @param {string} [attributes.menu=false] Initial mode for the menu region. 1959 * @param {string} [attributes.url] Unused. @todo Consider removal. 1960 */ 1961 var l10n = wp.media.view.l10n, 1962 EditImage; 1963 1964 EditImage = wp.media.controller.State.extend({ 1965 defaults: { 1966 id: 'edit-image', 1967 title: l10n.editImage, 1968 menu: false, 1969 toolbar: 'edit-image', 1970 content: 'edit-image', 1971 url: '' 1972 }, 1973 1974 /** 1975 * @since 3.9.0 1976 */ 1977 activate: function() { 1978 this.listenTo( this.frame, 'toolbar:render:edit-image', this.toolbar ); 1979 }, 1980 1981 /** 1982 * @since 3.9.0 1983 */ 1984 deactivate: function() { 1985 this.stopListening( this.frame ); 1986 }, 1987 1988 /** 1989 * @since 3.9.0 1990 */ 1991 toolbar: function() { 1992 var frame = this.frame, 1993 lastState = frame.lastState(), 1994 previous = lastState && lastState.id; 1995 1996 frame.toolbar.set( new wp.media.view.Toolbar({ 1997 controller: frame, 1998 items: { 1999 back: { 2000 style: 'primary', 2001 text: l10n.back, 2002 priority: 20, 2003 click: function() { 2004 if ( previous ) { 2005 frame.setState( previous ); 2006 } else { 2007 frame.close(); 2008 } 2009 } 2010 } 2011 } 2012 }) ); 2013 } 2014 }); 2015 2016 module.exports = EditImage; 2017 2018 2019 /***/ }), 2020 /* 40 */ 2021 /***/ (function(module, exports) { 2022 2023 /** 2024 * wp.media.controller.MediaLibrary 2025 * 2026 * @class 2027 * @augments wp.media.controller.Library 2028 * @augments wp.media.controller.State 2029 * @augments Backbone.Model 2030 */ 2031 var Library = wp.media.controller.Library, 2032 MediaLibrary; 2033 2034 MediaLibrary = Library.extend({ 2035 defaults: _.defaults({ 2036 // Attachments browser defaults. @see media.view.AttachmentsBrowser 2037 filterable: 'uploaded', 2038 2039 displaySettings: false, 2040 priority: 80, 2041 syncSelection: false 2042 }, Library.prototype.defaults ), 2043 2044 /** 2045 * @since 3.9.0 2046 * 2047 * @param options 2048 */ 2049 initialize: function( options ) { 2050 this.media = options.media; 2051 this.type = options.type; 2052 this.set( 'library', wp.media.query({ type: this.type }) ); 2053 2054 Library.prototype.initialize.apply( this, arguments ); 2055 }, 2056 2057 /** 2058 * @since 3.9.0 2059 */ 2060 activate: function() { 2061 // @todo this should use this.frame. 2062 if ( wp.media.frame.lastMime ) { 2063 this.set( 'library', wp.media.query({ type: wp.media.frame.lastMime }) ); 2064 delete wp.media.frame.lastMime; 2065 } 2066 Library.prototype.activate.apply( this, arguments ); 2067 } 2068 }); 2069 2070 module.exports = MediaLibrary; 2071 2072 2073 /***/ }), 2074 /* 41 */ 2075 /***/ (function(module, exports) { 2076 2077 /** 2078 * wp.media.controller.Embed 2079 * 2080 * A state for embedding media from a URL. 2081 * 2082 * @class 2083 * @augments wp.media.controller.State 2084 * @augments Backbone.Model 2085 * 2086 * @param {object} attributes The attributes hash passed to the state. 2087 * @param {string} [attributes.id=embed] Unique identifier. 2088 * @param {string} [attributes.title=Insert From URL] Title for the state. Displays in the media menu and the frame's title region. 2089 * @param {string} [attributes.content=embed] Initial mode for the content region. 2090 * @param {string} [attributes.menu=default] Initial mode for the menu region. 2091 * @param {string} [attributes.toolbar=main-embed] Initial mode for the toolbar region. 2092 * @param {string} [attributes.menu=false] Initial mode for the menu region. 2093 * @param {int} [attributes.priority=120] The priority for the state link in the media menu. 2094 * @param {string} [attributes.type=link] The type of embed. Currently only link is supported. 2095 * @param {string} [attributes.url] The embed URL. 2096 * @param {object} [attributes.metadata={}] Properties of the embed, which will override attributes.url if set. 2097 */ 2098 var l10n = wp.media.view.l10n, 2099 $ = Backbone.$, 2100 Embed; 2101 2102 Embed = wp.media.controller.State.extend({ 2103 defaults: { 2104 id: 'embed', 2105 title: l10n.insertFromUrlTitle, 2106 content: 'embed', 2107 menu: 'default', 2108 toolbar: 'main-embed', 2109 priority: 120, 2110 type: 'link', 2111 url: '', 2112 metadata: {} 2113 }, 2114 2115 // The amount of time used when debouncing the scan. 2116 sensitivity: 400, 2117 2118 initialize: function(options) { 2119 this.metadata = options.metadata; 2120 this.debouncedScan = _.debounce( _.bind( this.scan, this ), this.sensitivity ); 2121 this.props = new Backbone.Model( this.metadata || { url: '' }); 2122 this.props.on( 'change:url', this.debouncedScan, this ); 2123 this.props.on( 'change:url', this.refresh, this ); 2124 this.on( 'scan', this.scanImage, this ); 2125 }, 2126 2127 /** 2128 * Trigger a scan of the embedded URL's content for metadata required to embed. 2129 * 2130 * @fires wp.media.controller.Embed#scan 2131 */ 2132 scan: function() { 2133 var scanners, 2134 embed = this, 2135 attributes = { 2136 type: 'link', 2137 scanners: [] 2138 }; 2139 2140 // Scan is triggered with the list of `attributes` to set on the 2141 // state, useful for the 'type' attribute and 'scanners' attribute, 2142 // an array of promise objects for asynchronous scan operations. 2143 if ( this.props.get('url') ) { 2144 this.trigger( 'scan', attributes ); 2145 } 2146 2147 if ( attributes.scanners.length ) { 2148 scanners = attributes.scanners = $.when.apply( $, attributes.scanners ); 2149 scanners.always( function() { 2150 if ( embed.get('scanners') === scanners ) { 2151 embed.set( 'loading', false ); 2152 } 2153 }); 2154 } else { 2155 attributes.scanners = null; 2156 } 2157 2158 attributes.loading = !! attributes.scanners; 2159 this.set( attributes ); 2160 }, 2161 /** 2162 * Try scanning the embed as an image to discover its dimensions. 2163 * 2164 * @param {Object} attributes 2165 */ 2166 scanImage: function( attributes ) { 2167 var frame = this.frame, 2168 state = this, 2169 url = this.props.get('url'), 2170 image = new Image(), 2171 deferred = $.Deferred(); 2172 2173 attributes.scanners.push( deferred.promise() ); 2174 2175 // Try to load the image and find its width/height. 2176 image.onload = function() { 2177 deferred.resolve(); 2178 2179 if ( state !== frame.state() || url !== state.props.get('url') ) { 2180 return; 2181 } 2182 2183 state.set({ 2184 type: 'image' 2185 }); 2186 2187 state.props.set({ 2188 width: image.width, 2189 height: image.height 2190 }); 2191 }; 2192 2193 image.onerror = deferred.reject; 2194 image.src = url; 2195 }, 2196 2197 refresh: function() { 2198 this.frame.toolbar.get().refresh(); 2199 }, 2200 2201 reset: function() { 2202 this.props.clear().set({ url: '' }); 2203 2204 if ( this.active ) { 2205 this.refresh(); 2206 } 2207 } 2208 }); 2209 2210 module.exports = Embed; 2211 2212 2213 /***/ }), 2214 /* 42 */ 2215 /***/ (function(module, exports) { 2216 2217 /** 2218 * wp.media.controller.Cropper 2219 * 2220 * A state for cropping an image. 2221 * 2222 * @class 2223 * @augments wp.media.controller.State 2224 * @augments Backbone.Model 2225 */ 2226 var l10n = wp.media.view.l10n, 2227 Cropper; 2228 2229 Cropper = wp.media.controller.State.extend({ 2230 defaults: { 2231 id: 'cropper', 2232 title: l10n.cropImage, 2233 // Region mode defaults. 2234 toolbar: 'crop', 2235 content: 'crop', 2236 router: false, 2237 2238 canSkipCrop: false 2239 }, 2240 2241 activate: function() { 2242 this.frame.on( 'content:create:crop', this.createCropContent, this ); 2243 this.frame.on( 'close', this.removeCropper, this ); 2244 this.set('selection', new Backbone.Collection(this.frame._selection.single)); 2245 }, 2246 2247 deactivate: function() { 2248 this.frame.toolbar.mode('browse'); 2249 }, 2250 2251 createCropContent: function() { 2252 this.cropperView = new wp.media.view.Cropper({ 2253 controller: this, 2254 attachment: this.get('selection').first() 2255 }); 2256 this.cropperView.on('image-loaded', this.createCropToolbar, this); 2257 this.frame.content.set(this.cropperView); 2258 2259 }, 2260 removeCropper: function() { 2261 this.imgSelect.cancelSelection(); 2262 this.imgSelect.setOptions({remove: true}); 2263 this.imgSelect.update(); 2264 this.cropperView.remove(); 2265 }, 2266 createCropToolbar: function() { 2267 var canSkipCrop, toolbarOptions; 2268 2269 canSkipCrop = this.get('canSkipCrop') || false; 2270 2271 toolbarOptions = { 2272 controller: this.frame, 2273 items: { 2274 insert: { 2275 style: 'primary', 2276 text: l10n.cropImage, 2277 priority: 80, 2278 requires: { library: false, selection: false }, 2279 2280 click: function() { 2281 var controller = this.controller, 2282 selection; 2283 2284 selection = controller.state().get('selection').first(); 2285 selection.set({cropDetails: controller.state().imgSelect.getSelection()}); 2286 2287 this.$el.text(l10n.cropping); 2288 this.$el.attr('disabled', true); 2289 2290 controller.state().doCrop( selection ).done( function( croppedImage ) { 2291 controller.trigger('cropped', croppedImage ); 2292 controller.close(); 2293 }).fail( function() { 2294 controller.trigger('content:error:crop'); 2295 }); 2296 } 2297 } 2298 } 2299 }; 2300 2301 if ( canSkipCrop ) { 2302 _.extend( toolbarOptions.items, { 2303 skip: { 2304 style: 'secondary', 2305 text: l10n.skipCropping, 2306 priority: 70, 2307 requires: { library: false, selection: false }, 2308 click: function() { 2309 var selection = this.controller.state().get('selection').first(); 2310 this.controller.state().cropperView.remove(); 2311 this.controller.trigger('skippedcrop', selection); 2312 this.controller.close(); 2313 } 2314 } 2315 }); 2316 } 2317 2318 this.frame.toolbar.set( new wp.media.view.Toolbar(toolbarOptions) ); 2319 }, 2320 2321 doCrop: function( attachment ) { 2322 return wp.ajax.post( 'custom-header-crop', { 2323 nonce: attachment.get('nonces').edit, 2324 id: attachment.get('id'), 2325 cropDetails: attachment.get('cropDetails') 2326 } ); 2327 } 2328 }); 2329 2330 module.exports = Cropper; 2331 2332 2333 /***/ }), 2334 /* 43 */ 2335 /***/ (function(module, exports) { 2336 2337 /** 2338 * wp.media.controller.CustomizeImageCropper 2339 * 2340 * A state for cropping an image. 2341 * 2342 * @class 2343 * @augments wp.media.controller.Cropper 2344 * @augments wp.media.controller.State 2345 * @augments Backbone.Model 2346 */ 2347 var Controller = wp.media.controller, 2348 CustomizeImageCropper; 2349 2350 CustomizeImageCropper = Controller.Cropper.extend({ 2351 doCrop: function( attachment ) { 2352 var cropDetails = attachment.get( 'cropDetails' ), 2353 control = this.get( 'control' ); 2354 2355 cropDetails.dst_width = control.params.width; 2356 cropDetails.dst_height = control.params.height; 2357 2358 return wp.ajax.post( 'crop-image', { 2359 wp_customize: 'on', 2360 nonce: attachment.get( 'nonces' ).edit, 2361 id: attachment.get( 'id' ), 2362 context: control.id, 2363 cropDetails: cropDetails 2364 } ); 2365 } 2366 }); 2367 2368 module.exports = CustomizeImageCropper; 2369 2370 2371 /***/ }), 2372 /* 44 */ 2373 /***/ (function(module, exports) { 2374 1652 },{}],15:[function(require,module,exports){ 2375 1653 /** 2376 1654 * wp.media.controller.SiteIconCropper … … 2421 1699 module.exports = SiteIconCropper; 2422 1700 2423 2424 /***/ }), 2425 /* 45 */ 2426 /***/ (function(module, exports) { 2427 1701 },{}],16:[function(require,module,exports){ 2428 1702 /** 2429 * wp.media.View 2430 * 2431 * The base view class for media. 2432 * 2433 * Undelegating events, removing events from the model, and 2434 * removing events from the controller mirror the code for 2435 * `Backbone.View.dispose` in Backbone 0.9.8 development. 2436 * 2437 * This behavior has since been removed, and should not be used 2438 * outside of the media manager. 1703 * wp.media.controller.StateMachine 1704 * 1705 * A state machine keeps track of state. It is in one state at a time, 1706 * and can change from one state to another. 1707 * 1708 * States are stored as models in a Backbone collection. 1709 * 1710 * @since 3.5.0 2439 1711 * 2440 1712 * @class 2441 * @augments wp.Backbone.View 2442 * @augments Backbone.View 1713 * @augments Backbone.Model 1714 * @mixin 1715 * @mixes Backbone.Events 1716 * 1717 * @param {Array} states 2443 1718 */ 2444 var View = wp.Backbone.View.extend({2445 constructor: function( options ) {2446 if ( options && options.controller ) {2447 this.controller = options.controller;2448 } 2449 wp.Backbone.View.apply( this, arguments ); 2450 }, 2451 /** 2452 * @todo The internal comment mentions this might have been a stop-gap 2453 * before Backbone 0.9.8 came out. Figure out if Backbone core takes2454 * care of this in Backbone.View now.1719 var StateMachine = function( states ) { 1720 // @todo This is dead code. The states collection gets created in media.view.Frame._createStates. 1721 this.states = new Backbone.Collection( states ); 1722 }; 1723 1724 // Use Backbone's self-propagating `extend` inheritance method. 1725 StateMachine.extend = Backbone.Model.extend; 1726 1727 _.extend( StateMachine.prototype, Backbone.Events, { 1728 /** 1729 * Fetch a state. 2455 1730 * 2456 * @returns {wp.media.View} Returns itself to allow chaining 2457 */ 2458 dispose: function() { 2459 // Undelegating events, removing events from the model, and 2460 // removing events from the controller mirror the code for 2461 // `Backbone.View.dispose` in Backbone 0.9.8 development. 2462 this.undelegateEvents(); 2463 2464 if ( this.model && this.model.off ) { 2465 this.model.off( null, null, this ); 2466 } 2467 2468 if ( this.collection && this.collection.off ) { 2469 this.collection.off( null, null, this ); 2470 } 2471 2472 // Unbind controller events. 2473 if ( this.controller && this.controller.off ) { 2474 this.controller.off( null, null, this ); 2475 } 1731 * If no `id` is provided, returns the active state. 1732 * 1733 * Implicitly creates states. 1734 * 1735 * Ensure that the `states` collection exists so the `StateMachine` 1736 * can be used as a mixin. 1737 * 1738 * @since 3.5.0 1739 * 1740 * @param {string} id 1741 * @returns {wp.media.controller.State} Returns a State model 1742 * from the StateMachine collection 1743 */ 1744 state: function( id ) { 1745 this.states = this.states || new Backbone.Collection(); 1746 1747 // Default to the active state. 1748 id = id || this._state; 1749 1750 if ( id && ! this.states.get( id ) ) { 1751 this.states.add({ id: id }); 1752 } 1753 return this.states.get( id ); 1754 }, 1755 1756 /** 1757 * Sets the active state. 1758 * 1759 * Bail if we're trying to select the current state, if we haven't 1760 * created the `states` collection, or are trying to select a state 1761 * that does not exist. 1762 * 1763 * @since 3.5.0 1764 * 1765 * @param {string} id 1766 * 1767 * @fires wp.media.controller.State#deactivate 1768 * @fires wp.media.controller.State#activate 1769 * 1770 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining 1771 */ 1772 setState: function( id ) { 1773 var previous = this.state(); 1774 1775 if ( ( previous && id === previous.id ) || ! this.states || ! this.states.get( id ) ) { 1776 return this; 1777 } 1778 1779 if ( previous ) { 1780 previous.trigger('deactivate'); 1781 this._lastState = previous.id; 1782 } 1783 1784 this._state = id; 1785 this.state().trigger('activate'); 2476 1786 2477 1787 return this; 2478 1788 }, 2479 /** 2480 * @returns {wp.media.View} Returns itself to allow chaining 2481 */ 2482 remove: function() { 2483 this.dispose(); 2484 /** 2485 * call 'remove' directly on the parent class 2486 */ 2487 return wp.Backbone.View.prototype.remove.apply( this, arguments ); 1789 1790 /** 1791 * Returns the previous active state. 1792 * 1793 * Call the `state()` method with no parameters to retrieve the current 1794 * active state. 1795 * 1796 * @since 3.5.0 1797 * 1798 * @returns {wp.media.controller.State} Returns a State model 1799 * from the StateMachine collection 1800 */ 1801 lastState: function() { 1802 if ( this._lastState ) { 1803 return this.state( this._lastState ); 1804 } 2488 1805 } 2489 1806 }); 2490 1807 2491 module.exports = View; 2492 2493 2494 /***/ }), 2495 /* 46 */ 2496 /***/ (function(module, exports) { 2497 2498 /** 2499 * wp.media.view.Frame 2500 * 2501 * A frame is a composite view consisting of one or more regions and one or more 2502 * states. 2503 * 2504 * @see wp.media.controller.State 2505 * @see wp.media.controller.Region 2506 * 2507 * @class 2508 * @augments wp.media.View 2509 * @augments wp.Backbone.View 2510 * @augments Backbone.View 2511 * @mixes wp.media.controller.StateMachine 2512 */ 2513 var Frame = wp.media.View.extend({ 2514 initialize: function() { 2515 _.defaults( this.options, { 2516 mode: [ 'select' ] 2517 }); 2518 this._createRegions(); 2519 this._createStates(); 2520 this._createModes(); 2521 }, 2522 2523 _createRegions: function() { 2524 // Clone the regions array. 2525 this.regions = this.regions ? this.regions.slice() : []; 2526 2527 // Initialize regions. 2528 _.each( this.regions, function( region ) { 2529 this[ region ] = new wp.media.controller.Region({ 2530 view: this, 2531 id: region, 2532 selector: '.media-frame-' + region 2533 }); 2534 }, this ); 2535 }, 2536 /** 2537 * Create the frame's states. 2538 * 2539 * @see wp.media.controller.State 2540 * @see wp.media.controller.StateMachine 2541 * 2542 * @fires wp.media.controller.State#ready 2543 */ 2544 _createStates: function() { 2545 // Create the default `states` collection. 2546 this.states = new Backbone.Collection( null, { 2547 model: wp.media.controller.State 2548 }); 2549 2550 // Ensure states have a reference to the frame. 2551 this.states.on( 'add', function( model ) { 2552 model.frame = this; 2553 model.trigger('ready'); 2554 }, this ); 2555 2556 if ( this.options.states ) { 2557 this.states.add( this.options.states ); 2558 } 2559 }, 2560 2561 /** 2562 * A frame can be in a mode or multiple modes at one time. 2563 * 2564 * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode. 2565 */ 2566 _createModes: function() { 2567 // Store active "modes" that the frame is in. Unrelated to region modes. 2568 this.activeModes = new Backbone.Collection(); 2569 this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) ); 2570 2571 _.each( this.options.mode, function( mode ) { 2572 this.activateMode( mode ); 2573 }, this ); 2574 }, 2575 /** 2576 * Reset all states on the frame to their defaults. 2577 * 2578 * @returns {wp.media.view.Frame} Returns itself to allow chaining 2579 */ 2580 reset: function() { 2581 this.states.invoke( 'trigger', 'reset' ); 2582 return this; 2583 }, 2584 /** 2585 * Map activeMode collection events to the frame. 2586 */ 2587 triggerModeEvents: function( model, collection, options ) { 2588 var collectionEvent, 2589 modeEventMap = { 2590 add: 'activate', 2591 remove: 'deactivate' 2592 }, 2593 eventToTrigger; 2594 // Probably a better way to do this. 2595 _.each( options, function( value, key ) { 2596 if ( value ) { 2597 collectionEvent = key; 2598 } 2599 } ); 2600 2601 if ( ! _.has( modeEventMap, collectionEvent ) ) { 2602 return; 2603 } 2604 2605 eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent]; 2606 this.trigger( eventToTrigger ); 2607 }, 2608 /** 2609 * Activate a mode on the frame. 2610 * 2611 * @param string mode Mode ID. 2612 * @returns {this} Returns itself to allow chaining. 2613 */ 2614 activateMode: function( mode ) { 2615 // Bail if the mode is already active. 2616 if ( this.isModeActive( mode ) ) { 2617 return; 2618 } 2619 this.activeModes.add( [ { id: mode } ] ); 2620 // Add a CSS class to the frame so elements can be styled for the mode. 2621 this.$el.addClass( 'mode-' + mode ); 2622 2623 return this; 2624 }, 2625 /** 2626 * Deactivate a mode on the frame. 2627 * 2628 * @param string mode Mode ID. 2629 * @returns {this} Returns itself to allow chaining. 2630 */ 2631 deactivateMode: function( mode ) { 2632 // Bail if the mode isn't active. 2633 if ( ! this.isModeActive( mode ) ) { 2634 return this; 2635 } 2636 this.activeModes.remove( this.activeModes.where( { id: mode } ) ); 2637 this.$el.removeClass( 'mode-' + mode ); 2638 /** 2639 * Frame mode deactivation event. 2640 * 2641 * @event this#{mode}:deactivate 2642 */ 2643 this.trigger( mode + ':deactivate' ); 2644 2645 return this; 2646 }, 2647 /** 2648 * Check if a mode is enabled on the frame. 2649 * 2650 * @param string mode Mode ID. 2651 * @return bool 2652 */ 2653 isModeActive: function( mode ) { 2654 return Boolean( this.activeModes.where( { id: mode } ).length ); 2655 } 2656 }); 2657 2658 // Make the `Frame` a `StateMachine`. 2659 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype ); 2660 2661 module.exports = Frame; 2662 2663 2664 /***/ }), 2665 /* 47 */ 2666 /***/ (function(module, exports) { 2667 2668 /** 2669 * wp.media.view.MediaFrame 2670 * 2671 * The frame used to create the media modal. 2672 * 2673 * @class 2674 * @augments wp.media.view.Frame 2675 * @augments wp.media.View 2676 * @augments wp.Backbone.View 2677 * @augments Backbone.View 2678 * @mixes wp.media.controller.StateMachine 2679 */ 2680 var Frame = wp.media.view.Frame, 2681 $ = jQuery, 2682 MediaFrame; 2683 2684 MediaFrame = Frame.extend({ 2685 className: 'media-frame', 2686 template: wp.template('media-frame'), 2687 regions: ['menu','title','content','toolbar','router'], 2688 2689 events: { 2690 'click div.media-frame-title h1': 'toggleMenu' 2691 }, 2692 2693 /** 2694 * @global wp.Uploader 2695 */ 2696 initialize: function() { 2697 Frame.prototype.initialize.apply( this, arguments ); 2698 2699 _.defaults( this.options, { 2700 title: '', 2701 modal: true, 2702 uploader: true 2703 }); 2704 2705 // Ensure core UI is enabled. 2706 this.$el.addClass('wp-core-ui'); 2707 2708 // Initialize modal container view. 2709 if ( this.options.modal ) { 2710 this.modal = new wp.media.view.Modal({ 2711 controller: this, 2712 title: this.options.title 2713 }); 2714 2715 this.modal.content( this ); 2716 } 2717 2718 // Force the uploader off if the upload limit has been exceeded or 2719 // if the browser isn't supported. 2720 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) { 2721 this.options.uploader = false; 2722 } 2723 2724 // Initialize window-wide uploader. 2725 if ( this.options.uploader ) { 2726 this.uploader = new wp.media.view.UploaderWindow({ 2727 controller: this, 2728 uploader: { 2729 dropzone: this.modal ? this.modal.$el : this.$el, 2730 container: this.$el 2731 } 2732 }); 2733 this.views.set( '.media-frame-uploader', this.uploader ); 2734 } 2735 2736 this.on( 'attach', _.bind( this.views.ready, this.views ), this ); 2737 2738 // Bind default title creation. 2739 this.on( 'title:create:default', this.createTitle, this ); 2740 this.title.mode('default'); 2741 2742 this.on( 'title:render', function( view ) { 2743 view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' ); 2744 }); 2745 2746 // Bind default menu. 2747 this.on( 'menu:create:default', this.createMenu, this ); 2748 }, 2749 /** 2750 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 2751 */ 2752 render: function() { 2753 // Activate the default state if no active state exists. 2754 if ( ! this.state() && this.options.state ) { 2755 this.setState( this.options.state ); 2756 } 2757 /** 2758 * call 'render' directly on the parent class 2759 */ 2760 return Frame.prototype.render.apply( this, arguments ); 2761 }, 2762 /** 2763 * @param {Object} title 2764 * @this wp.media.controller.Region 2765 */ 2766 createTitle: function( title ) { 2767 title.view = new wp.media.View({ 2768 controller: this, 2769 tagName: 'h1' 2770 }); 2771 }, 2772 /** 2773 * @param {Object} menu 2774 * @this wp.media.controller.Region 2775 */ 2776 createMenu: function( menu ) { 2777 menu.view = new wp.media.view.Menu({ 2778 controller: this 2779 }); 2780 }, 2781 2782 toggleMenu: function() { 2783 this.$el.find( '.media-menu' ).toggleClass( 'visible' ); 2784 }, 2785 2786 /** 2787 * @param {Object} toolbar 2788 * @this wp.media.controller.Region 2789 */ 2790 createToolbar: function( toolbar ) { 2791 toolbar.view = new wp.media.view.Toolbar({ 2792 controller: this 2793 }); 2794 }, 2795 /** 2796 * @param {Object} router 2797 * @this wp.media.controller.Region 2798 */ 2799 createRouter: function( router ) { 2800 router.view = new wp.media.view.Router({ 2801 controller: this 2802 }); 2803 }, 2804 /** 2805 * @param {Object} options 2806 */ 2807 createIframeStates: function( options ) { 2808 var settings = wp.media.view.settings, 2809 tabs = settings.tabs, 2810 tabUrl = settings.tabUrl, 2811 $postId; 2812 2813 if ( ! tabs || ! tabUrl ) { 2814 return; 2815 } 2816 2817 // Add the post ID to the tab URL if it exists. 2818 $postId = $('#post_ID'); 2819 if ( $postId.length ) { 2820 tabUrl += '&post_id=' + $postId.val(); 2821 } 2822 2823 // Generate the tab states. 2824 _.each( tabs, function( title, id ) { 2825 this.state( 'iframe:' + id ).set( _.defaults({ 2826 tab: id, 2827 src: tabUrl + '&tab=' + id, 2828 title: title, 2829 content: 'iframe', 2830 menu: 'default' 2831 }, options ) ); 2832 }, this ); 2833 2834 this.on( 'content:create:iframe', this.iframeContent, this ); 2835 this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this ); 2836 this.on( 'menu:render:default', this.iframeMenu, this ); 2837 this.on( 'open', this.hijackThickbox, this ); 2838 this.on( 'close', this.restoreThickbox, this ); 2839 }, 2840 2841 /** 2842 * @param {Object} content 2843 * @this wp.media.controller.Region 2844 */ 2845 iframeContent: function( content ) { 2846 this.$el.addClass('hide-toolbar'); 2847 content.view = new wp.media.view.Iframe({ 2848 controller: this 2849 }); 2850 }, 2851 2852 iframeContentCleanup: function() { 2853 this.$el.removeClass('hide-toolbar'); 2854 }, 2855 2856 iframeMenu: function( view ) { 2857 var views = {}; 2858 2859 if ( ! view ) { 2860 return; 2861 } 2862 2863 _.each( wp.media.view.settings.tabs, function( title, id ) { 2864 views[ 'iframe:' + id ] = { 2865 text: this.state( 'iframe:' + id ).get('title'), 2866 priority: 200 2867 }; 2868 }, this ); 2869 2870 view.set( views ); 2871 }, 2872 2873 hijackThickbox: function() { 2874 var frame = this; 2875 2876 if ( ! window.tb_remove || this._tb_remove ) { 2877 return; 2878 } 2879 2880 this._tb_remove = window.tb_remove; 2881 window.tb_remove = function() { 2882 frame.close(); 2883 frame.reset(); 2884 frame.setState( frame.options.state ); 2885 frame._tb_remove.call( window ); 2886 }; 2887 }, 2888 2889 restoreThickbox: function() { 2890 if ( ! this._tb_remove ) { 2891 return; 2892 } 2893 2894 window.tb_remove = this._tb_remove; 2895 delete this._tb_remove; 2896 } 2897 }); 2898 2899 // Map some of the modal's methods to the frame. 2900 _.each(['open','close','attach','detach','escape'], function( method ) { 2901 /** 2902 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 2903 */ 2904 MediaFrame.prototype[ method ] = function() { 2905 if ( this.modal ) { 2906 this.modal[ method ].apply( this.modal, arguments ); 2907 } 1808 // Map all event binding and triggering on a StateMachine to its `states` collection. 1809 _.each([ 'on', 'off', 'trigger' ], function( method ) { 1810 /** 1811 * @returns {wp.media.controller.StateMachine} Returns itself to allow chaining. 1812 */ 1813 StateMachine.prototype[ method ] = function() { 1814 // Ensure that the `states` collection exists so the `StateMachine` 1815 // can be used as a mixin. 1816 this.states = this.states || new Backbone.Collection(); 1817 // Forward the method to the `states` collection. 1818 this.states[ method ].apply( this.states, arguments ); 2908 1819 return this; 2909 1820 }; 2910 1821 }); 2911 1822 2912 module.exports = MediaFrame; 2913 2914 2915 /***/ }), 2916 /* 48 */ 2917 /***/ (function(module, exports) { 2918 1823 module.exports = StateMachine; 1824 1825 },{}],17:[function(require,module,exports){ 2919 1826 /** 2920 * wp.media.view.MediaFrame.Select 2921 * 2922 * A frame for selecting an item or items from the media library. 1827 * wp.media.controller.State 1828 * 1829 * A state is a step in a workflow that when set will trigger the controllers 1830 * for the regions to be updated as specified in the frame. 1831 * 1832 * A state has an event-driven lifecycle: 1833 * 1834 * 'ready' triggers when a state is added to a state machine's collection. 1835 * 'activate' triggers when a state is activated by a state machine. 1836 * 'deactivate' triggers when a state is deactivated by a state machine. 1837 * 'reset' is not triggered automatically. It should be invoked by the 1838 * proper controller to reset the state to its default. 2923 1839 * 2924 1840 * @class 2925 * @augments wp.media.view.MediaFrame 2926 * @augments wp.media.view.Frame 2927 * @augments wp.media.View 2928 * @augments wp.Backbone.View 2929 * @augments Backbone.View 2930 * @mixes wp.media.controller.StateMachine 1841 * @augments Backbone.Model 2931 1842 */ 2932 2933 var MediaFrame = wp.media.view.MediaFrame, 2934 l10n = wp.media.view.l10n, 2935 Select; 2936 2937 Select = MediaFrame.extend({ 2938 initialize: function() { 2939 // Call 'initialize' directly on the parent class. 2940 MediaFrame.prototype.initialize.apply( this, arguments ); 2941 2942 _.defaults( this.options, { 2943 selection: [], 2944 library: {}, 2945 multiple: false, 2946 state: 'library' 2947 }); 2948 2949 this.createSelection(); 2950 this.createStates(); 2951 this.bindHandlers(); 2952 }, 2953 2954 /** 2955 * Attach a selection collection to the frame. 1843 var State = Backbone.Model.extend({ 1844 /** 1845 * Constructor. 2956 1846 * 2957 * A selection is a collection of attachments used for a specific purpose 2958 * by a media frame. e.g. Selecting an attachment (or many) to insert into 2959 * post content. 1847 * @since 3.5.0 1848 */ 1849 constructor: function() { 1850 this.on( 'activate', this._preActivate, this ); 1851 this.on( 'activate', this.activate, this ); 1852 this.on( 'activate', this._postActivate, this ); 1853 this.on( 'deactivate', this._deactivate, this ); 1854 this.on( 'deactivate', this.deactivate, this ); 1855 this.on( 'reset', this.reset, this ); 1856 this.on( 'ready', this._ready, this ); 1857 this.on( 'ready', this.ready, this ); 1858 /** 1859 * Call parent constructor with passed arguments 1860 */ 1861 Backbone.Model.apply( this, arguments ); 1862 this.on( 'change:menu', this._updateMenu, this ); 1863 }, 1864 /** 1865 * Ready event callback. 2960 1866 * 2961 * @see media.model.Selection 2962 */ 2963 createSelection: function() { 2964 var selection = this.options.selection; 2965 2966 if ( ! (selection instanceof wp.media.model.Selection) ) { 2967 this.options.selection = new wp.media.model.Selection( selection, { 2968 multiple: this.options.multiple 2969 }); 2970 } 2971 2972 this._selection = { 2973 attachments: new wp.media.model.Attachments(), 2974 difference: [] 2975 }; 2976 }, 2977 2978 /** 2979 * Create the default states on the frame. 2980 */ 2981 createStates: function() { 2982 var options = this.options; 2983 2984 if ( this.options.states ) { 1867 * @abstract 1868 * @since 3.5.0 1869 */ 1870 ready: function() {}, 1871 1872 /** 1873 * Activate event callback. 1874 * 1875 * @abstract 1876 * @since 3.5.0 1877 */ 1878 activate: function() {}, 1879 1880 /** 1881 * Deactivate event callback. 1882 * 1883 * @abstract 1884 * @since 3.5.0 1885 */ 1886 deactivate: function() {}, 1887 1888 /** 1889 * Reset event callback. 1890 * 1891 * @abstract 1892 * @since 3.5.0 1893 */ 1894 reset: function() {}, 1895 1896 /** 1897 * @access private 1898 * @since 3.5.0 1899 */ 1900 _ready: function() { 1901 this._updateMenu(); 1902 }, 1903 1904 /** 1905 * @access private 1906 * @since 3.5.0 1907 */ 1908 _preActivate: function() { 1909 this.active = true; 1910 }, 1911 1912 /** 1913 * @access private 1914 * @since 3.5.0 1915 */ 1916 _postActivate: function() { 1917 this.on( 'change:menu', this._menu, this ); 1918 this.on( 'change:titleMode', this._title, this ); 1919 this.on( 'change:content', this._content, this ); 1920 this.on( 'change:toolbar', this._toolbar, this ); 1921 1922 this.frame.on( 'title:render:default', this._renderTitle, this ); 1923 1924 this._title(); 1925 this._menu(); 1926 this._toolbar(); 1927 this._content(); 1928 this._router(); 1929 }, 1930 1931 /** 1932 * @access private 1933 * @since 3.5.0 1934 */ 1935 _deactivate: function() { 1936 this.active = false; 1937 1938 this.frame.off( 'title:render:default', this._renderTitle, this ); 1939 1940 this.off( 'change:menu', this._menu, this ); 1941 this.off( 'change:titleMode', this._title, this ); 1942 this.off( 'change:content', this._content, this ); 1943 this.off( 'change:toolbar', this._toolbar, this ); 1944 }, 1945 1946 /** 1947 * @access private 1948 * @since 3.5.0 1949 */ 1950 _title: function() { 1951 this.frame.title.render( this.get('titleMode') || 'default' ); 1952 }, 1953 1954 /** 1955 * @access private 1956 * @since 3.5.0 1957 */ 1958 _renderTitle: function( view ) { 1959 view.$el.text( this.get('title') || '' ); 1960 }, 1961 1962 /** 1963 * @access private 1964 * @since 3.5.0 1965 */ 1966 _router: function() { 1967 var router = this.frame.router, 1968 mode = this.get('router'), 1969 view; 1970 1971 this.frame.$el.toggleClass( 'hide-router', ! mode ); 1972 if ( ! mode ) { 2985 1973 return; 2986 1974 } 2987 1975 2988 // Add the default states. 2989 this.states.add([ 2990 // Main states. 2991 new wp.media.controller.Library({ 2992 library: wp.media.query( options.library ), 2993 multiple: options.multiple, 2994 title: options.title, 2995 priority: 20 2996 }) 2997 ]); 2998 }, 2999 3000 /** 3001 * Bind region mode event callbacks. 1976 this.frame.router.render( mode ); 1977 1978 view = router.get(); 1979 if ( view && view.select ) { 1980 view.select( this.frame.content.mode() ); 1981 } 1982 }, 1983 1984 /** 1985 * @access private 1986 * @since 3.5.0 1987 */ 1988 _menu: function() { 1989 var menu = this.frame.menu, 1990 mode = this.get('menu'), 1991 view; 1992 1993 this.frame.$el.toggleClass( 'hide-menu', ! mode ); 1994 if ( ! mode ) { 1995 return; 1996 } 1997 1998 menu.mode( mode ); 1999 2000 view = menu.get(); 2001 if ( view && view.select ) { 2002 view.select( this.id ); 2003 } 2004 }, 2005 2006 /** 2007 * @access private 2008 * @since 3.5.0 2009 */ 2010 _updateMenu: function() { 2011 var previous = this.previous('menu'), 2012 menu = this.get('menu'); 2013 2014 if ( previous ) { 2015 this.frame.off( 'menu:render:' + previous, this._renderMenu, this ); 2016 } 2017 2018 if ( menu ) { 2019 this.frame.on( 'menu:render:' + menu, this._renderMenu, this ); 2020 } 2021 }, 2022 2023 /** 2024 * Create a view in the media menu for the state. 3002 2025 * 3003 * @see media.controller.Region.render 3004 */ 3005 bindHandlers: function() { 3006 this.on( 'router:create:browse', this.createRouter, this ); 3007 this.on( 'router:render:browse', this.browseRouter, this ); 3008 this.on( 'content:create:browse', this.browseContent, this ); 3009 this.on( 'content:render:upload', this.uploadContent, this ); 3010 this.on( 'toolbar:create:select', this.createSelectToolbar, this ); 3011 }, 3012 3013 /** 3014 * Render callback for the router region in the `browse` mode. 2026 * @access private 2027 * @since 3.5.0 3015 2028 * 3016 * @param {wp.media.view.Router} routerView 3017 */ 3018 browseRouter: function( routerView ) { 3019 routerView.set({ 3020 upload: { 3021 text: l10n.uploadFilesTitle, 3022 priority: 20 3023 }, 3024 browse: { 3025 text: l10n.mediaLibraryTitle, 3026 priority: 40 2029 * @param {media.view.Menu} view The menu view. 2030 */ 2031 _renderMenu: function( view ) { 2032 var menuItem = this.get('menuItem'), 2033 title = this.get('title'), 2034 priority = this.get('priority'); 2035 2036 if ( ! menuItem && title ) { 2037 menuItem = { text: title }; 2038 2039 if ( priority ) { 2040 menuItem.priority = priority; 3027 2041 } 3028 }); 3029 }, 3030 3031 /** 3032 * Render callback for the content region in the `browse` mode. 3033 * 3034 * @param {wp.media.controller.Region} contentRegion 3035 */ 3036 browseContent: function( contentRegion ) { 3037 var state = this.state(); 3038 3039 this.$el.removeClass('hide-toolbar'); 3040 3041 // Browse our library of attachments. 3042 contentRegion.view = new wp.media.view.AttachmentsBrowser({ 3043 controller: this, 3044 collection: state.get('library'), 3045 selection: state.get('selection'), 3046 model: state, 3047 sortable: state.get('sortable'), 3048 search: state.get('searchable'), 3049 filters: state.get('filterable'), 3050 date: state.get('date'), 3051 display: state.has('display') ? state.get('display') : state.get('displaySettings'), 3052 dragInfo: state.get('dragInfo'), 3053 3054 idealColumnWidth: state.get('idealColumnWidth'), 3055 suggestedWidth: state.get('suggestedWidth'), 3056 suggestedHeight: state.get('suggestedHeight'), 3057 3058 AttachmentView: state.get('AttachmentView') 3059 }); 3060 }, 3061 3062 /** 3063 * Render callback for the content region in the `upload` mode. 3064 */ 3065 uploadContent: function() { 3066 this.$el.removeClass( 'hide-toolbar' ); 3067 this.content.set( new wp.media.view.UploaderInline({ 3068 controller: this 3069 }) ); 3070 }, 3071 3072 /** 3073 * Toolbars 3074 * 3075 * @param {Object} toolbar 3076 * @param {Object} [options={}] 3077 * @this wp.media.controller.Region 3078 */ 3079 createSelectToolbar: function( toolbar, options ) { 3080 options = options || this.options.button || {}; 3081 options.controller = this; 3082 3083 toolbar.view = new wp.media.view.Toolbar.Select( options ); 2042 } 2043 2044 if ( ! menuItem ) { 2045 return; 2046 } 2047 2048 view.set( this.id, menuItem ); 3084 2049 } 3085 2050 }); 3086 2051 3087 module.exports = Select; 3088 3089 3090 /***/ }), 3091 /* 49 */ 3092 /***/ (function(module, exports) { 3093 2052 _.each(['toolbar','content'], function( region ) { 2053 /** 2054 * @access private 2055 */ 2056 State.prototype[ '_' + region ] = function() { 2057 var mode = this.get( region ); 2058 if ( mode ) { 2059 this.frame[ region ].render( mode ); 2060 } 2061 }; 2062 }); 2063 2064 module.exports = State; 2065 2066 },{}],18:[function(require,module,exports){ 3094 2067 /** 3095 * wp.media.view.MediaFrame.Post 3096 * 3097 * The frame for manipulating media on the Edit Post page. 3098 * 3099 * @class 3100 * @augments wp.media.view.MediaFrame.Select 3101 * @augments wp.media.view.MediaFrame 3102 * @augments wp.media.view.Frame 3103 * @augments wp.media.View 3104 * @augments wp.Backbone.View 3105 * @augments Backbone.View 3106 * @mixes wp.media.controller.StateMachine 2068 * wp.media.selectionSync 2069 * 2070 * Sync an attachments selection in a state with another state. 2071 * 2072 * Allows for selecting multiple images in the Insert Media workflow, and then 2073 * switching to the Insert Gallery workflow while preserving the attachments selection. 2074 * 2075 * @mixin 3107 2076 */ 3108 var Select = wp.media.view.MediaFrame.Select, 3109 Library = wp.media.controller.Library, 3110 l10n = wp.media.view.l10n, 3111 Post; 3112 3113 Post = Select.extend({ 3114 initialize: function() { 3115 this.counts = { 3116 audio: { 3117 count: wp.media.view.settings.attachmentCounts.audio, 3118 state: 'playlist' 3119 }, 3120 video: { 3121 count: wp.media.view.settings.attachmentCounts.video, 3122 state: 'video-playlist' 3123 } 3124 }; 3125 3126 _.defaults( this.options, { 3127 multiple: true, 3128 editing: false, 3129 state: 'insert', 3130 metadata: {} 3131 }); 3132 3133 // Call 'initialize' directly on the parent class. 3134 Select.prototype.initialize.apply( this, arguments ); 3135 this.createIframeStates(); 3136 3137 }, 3138 3139 /** 3140 * Create the default states. 3141 */ 3142 createStates: function() { 3143 var options = this.options; 3144 3145 this.states.add([ 3146 // Main states. 3147 new Library({ 3148 id: 'insert', 3149 title: l10n.insertMediaTitle, 3150 priority: 20, 3151 toolbar: 'main-insert', 3152 filterable: 'all', 3153 library: wp.media.query( options.library ), 3154 multiple: options.multiple ? 'reset' : false, 3155 editable: true, 3156 3157 // If the user isn't allowed to edit fields, 3158 // can they still edit it locally? 3159 allowLocalEdits: true, 3160 3161 // Show the attachment display settings. 3162 displaySettings: true, 3163 // Update user settings when users adjust the 3164 // attachment display settings. 3165 displayUserSettings: true 3166 }), 3167 3168 new Library({ 3169 id: 'gallery', 3170 title: l10n.createGalleryTitle, 3171 priority: 40, 3172 toolbar: 'main-gallery', 3173 filterable: 'uploaded', 3174 multiple: 'add', 3175 editable: false, 3176 3177 library: wp.media.query( _.defaults({ 3178 type: 'image' 3179 }, options.library ) ) 3180 }), 3181 3182 // Embed states. 3183 new wp.media.controller.Embed( { metadata: options.metadata } ), 3184 3185 new wp.media.controller.EditImage( { model: options.editImage } ), 3186 3187 // Gallery states. 3188 new wp.media.controller.GalleryEdit({ 3189 library: options.selection, 3190 editing: options.editing, 3191 menu: 'gallery' 3192 }), 3193 3194 new wp.media.controller.GalleryAdd(), 3195 3196 new Library({ 3197 id: 'playlist', 3198 title: l10n.createPlaylistTitle, 3199 priority: 60, 3200 toolbar: 'main-playlist', 3201 filterable: 'uploaded', 3202 multiple: 'add', 3203 editable: false, 3204 3205 library: wp.media.query( _.defaults({ 3206 type: 'audio' 3207 }, options.library ) ) 3208 }), 3209 3210 // Playlist states. 3211 new wp.media.controller.CollectionEdit({ 3212 type: 'audio', 3213 collectionType: 'playlist', 3214 title: l10n.editPlaylistTitle, 3215 SettingsView: wp.media.view.Settings.Playlist, 3216 library: options.selection, 3217 editing: options.editing, 3218 menu: 'playlist', 3219 dragInfoText: l10n.playlistDragInfo, 3220 dragInfo: false 3221 }), 3222 3223 new wp.media.controller.CollectionAdd({ 3224 type: 'audio', 3225 collectionType: 'playlist', 3226 title: l10n.addToPlaylistTitle 3227 }), 3228 3229 new Library({ 3230 id: 'video-playlist', 3231 title: l10n.createVideoPlaylistTitle, 3232 priority: 60, 3233 toolbar: 'main-video-playlist', 3234 filterable: 'uploaded', 3235 multiple: 'add', 3236 editable: false, 3237 3238 library: wp.media.query( _.defaults({ 3239 type: 'video' 3240 }, options.library ) ) 3241 }), 3242 3243 new wp.media.controller.CollectionEdit({ 3244 type: 'video', 3245 collectionType: 'playlist', 3246 title: l10n.editVideoPlaylistTitle, 3247 SettingsView: wp.media.view.Settings.Playlist, 3248 library: options.selection, 3249 editing: options.editing, 3250 menu: 'video-playlist', 3251 dragInfoText: l10n.videoPlaylistDragInfo, 3252 dragInfo: false 3253 }), 3254 3255 new wp.media.controller.CollectionAdd({ 3256 type: 'video', 3257 collectionType: 'playlist', 3258 title: l10n.addToVideoPlaylistTitle 3259 }) 3260 ]); 3261 3262 if ( wp.media.view.settings.post.featuredImageId ) { 3263 this.states.add( new wp.media.controller.FeaturedImage() ); 3264 } 3265 }, 3266 3267 bindHandlers: function() { 3268 var handlers, checkCounts; 3269 3270 Select.prototype.bindHandlers.apply( this, arguments ); 3271 3272 this.on( 'activate', this.activate, this ); 3273 3274 // Only bother checking media type counts if one of the counts is zero 3275 checkCounts = _.find( this.counts, function( type ) { 3276 return type.count === 0; 3277 } ); 3278 3279 if ( typeof checkCounts !== 'undefined' ) { 3280 this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts ); 3281 } 3282 3283 this.on( 'menu:create:gallery', this.createMenu, this ); 3284 this.on( 'menu:create:playlist', this.createMenu, this ); 3285 this.on( 'menu:create:video-playlist', this.createMenu, this ); 3286 this.on( 'toolbar:create:main-insert', this.createToolbar, this ); 3287 this.on( 'toolbar:create:main-gallery', this.createToolbar, this ); 3288 this.on( 'toolbar:create:main-playlist', this.createToolbar, this ); 3289 this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this ); 3290 this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this ); 3291 this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this ); 3292 3293 handlers = { 3294 menu: { 3295 'default': 'mainMenu', 3296 'gallery': 'galleryMenu', 3297 'playlist': 'playlistMenu', 3298 'video-playlist': 'videoPlaylistMenu' 3299 }, 3300 3301 content: { 3302 'embed': 'embedContent', 3303 'edit-image': 'editImageContent', 3304 'edit-selection': 'editSelectionContent' 3305 }, 3306 3307 toolbar: { 3308 'main-insert': 'mainInsertToolbar', 3309 'main-gallery': 'mainGalleryToolbar', 3310 'gallery-edit': 'galleryEditToolbar', 3311 'gallery-add': 'galleryAddToolbar', 3312 'main-playlist': 'mainPlaylistToolbar', 3313 'playlist-edit': 'playlistEditToolbar', 3314 'playlist-add': 'playlistAddToolbar', 3315 'main-video-playlist': 'mainVideoPlaylistToolbar', 3316 'video-playlist-edit': 'videoPlaylistEditToolbar', 3317 'video-playlist-add': 'videoPlaylistAddToolbar' 3318 } 3319 }; 3320 3321 _.each( handlers, function( regionHandlers, region ) { 3322 _.each( regionHandlers, function( callback, handler ) { 3323 this.on( region + ':render:' + handler, this[ callback ], this ); 3324 }, this ); 3325 }, this ); 3326 }, 3327 3328 activate: function() { 3329 // Hide menu items for states tied to particular media types if there are no items 3330 _.each( this.counts, function( type ) { 3331 if ( type.count < 1 ) { 3332 this.menuItemVisibility( type.state, 'hide' ); 3333 } 3334 }, this ); 3335 }, 3336 3337 mediaTypeCounts: function( model, attr ) { 3338 if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) { 3339 this.counts[ attr ].count++; 3340 this.menuItemVisibility( this.counts[ attr ].state, 'show' ); 3341 } 3342 }, 3343 3344 // Menus 3345 /** 3346 * @param {wp.Backbone.View} view 3347 */ 3348 mainMenu: function( view ) { 3349 view.set({ 3350 'library-separator': new wp.media.View({ 3351 className: 'separator', 3352 priority: 100 3353 }) 3354 }); 3355 }, 3356 3357 menuItemVisibility: function( state, visibility ) { 3358 var menu = this.menu.get(); 3359 if ( visibility === 'hide' ) { 3360 menu.hide( state ); 3361 } else if ( visibility === 'show' ) { 3362 menu.show( state ); 3363 } 3364 }, 3365 /** 3366 * @param {wp.Backbone.View} view 3367 */ 3368 galleryMenu: function( view ) { 3369 var lastState = this.lastState(), 3370 previous = lastState && lastState.id, 3371 frame = this; 3372 3373 view.set({ 3374 cancel: { 3375 text: l10n.cancelGalleryTitle, 3376 priority: 20, 3377 click: function() { 3378 if ( previous ) { 3379 frame.setState( previous ); 3380 } else { 3381 frame.close(); 3382 } 3383 3384 // Keep focus inside media modal 3385 // after canceling a gallery 3386 this.controller.modal.focusManager.focus(); 3387 } 3388 }, 3389 separateCancel: new wp.media.View({ 3390 className: 'separator', 3391 priority: 40 3392 }) 3393 }); 3394 }, 3395 3396 playlistMenu: function( view ) { 3397 var lastState = this.lastState(), 3398 previous = lastState && lastState.id, 3399 frame = this; 3400 3401 view.set({ 3402 cancel: { 3403 text: l10n.cancelPlaylistTitle, 3404 priority: 20, 3405 click: function() { 3406 if ( previous ) { 3407 frame.setState( previous ); 3408 } else { 3409 frame.close(); 3410 } 3411 } 3412 }, 3413 separateCancel: new wp.media.View({ 3414 className: 'separator', 3415 priority: 40 3416 }) 3417 }); 3418 }, 3419 3420 videoPlaylistMenu: function( view ) { 3421 var lastState = this.lastState(), 3422 previous = lastState && lastState.id, 3423 frame = this; 3424 3425 view.set({ 3426 cancel: { 3427 text: l10n.cancelVideoPlaylistTitle, 3428 priority: 20, 3429 click: function() { 3430 if ( previous ) { 3431 frame.setState( previous ); 3432 } else { 3433 frame.close(); 3434 } 3435 } 3436 }, 3437 separateCancel: new wp.media.View({ 3438 className: 'separator', 3439 priority: 40 3440 }) 3441 }); 3442 }, 3443 3444 // Content 3445 embedContent: function() { 3446 var view = new wp.media.view.Embed({ 3447 controller: this, 3448 model: this.state() 3449 }).render(); 3450 3451 this.content.set( view ); 3452 3453 if ( ! wp.media.isTouchDevice ) { 3454 view.url.focus(); 3455 } 3456 }, 3457 3458 editSelectionContent: function() { 3459 var state = this.state(), 3460 selection = state.get('selection'), 3461 view; 3462 3463 view = new wp.media.view.AttachmentsBrowser({ 3464 controller: this, 3465 collection: selection, 3466 selection: selection, 3467 model: state, 3468 sortable: true, 3469 search: false, 3470 date: false, 3471 dragInfo: true, 3472 3473 AttachmentView: wp.media.view.Attachments.EditSelection 3474 }).render(); 3475 3476 view.toolbar.set( 'backToLibrary', { 3477 text: l10n.returnToLibrary, 3478 priority: -100, 3479 3480 click: function() { 3481 this.controller.content.mode('browse'); 3482 } 3483 }); 3484 3485 // Browse our library of attachments. 3486 this.content.set( view ); 3487 3488 // Trigger the controller to set focus 3489 this.trigger( 'edit:selection', this ); 3490 }, 3491 3492 editImageContent: function() { 3493 var image = this.state().get('image'), 3494 view = new wp.media.view.EditImage( { model: image, controller: this } ).render(); 3495 3496 this.content.set( view ); 3497 3498 // after creating the wrapper view, load the actual editor via an ajax call 3499 view.loadEditor(); 3500 3501 }, 3502 3503 // Toolbars 3504 3505 /** 3506 * @param {wp.Backbone.View} view 3507 */ 3508 selectionStatusToolbar: function( view ) { 3509 var editable = this.state().get('editable'); 3510 3511 view.set( 'selection', new wp.media.view.Selection({ 3512 controller: this, 3513 collection: this.state().get('selection'), 3514 priority: -40, 3515 3516 // If the selection is editable, pass the callback to 3517 // switch the content mode. 3518 editable: editable && function() { 3519 this.controller.content.mode('edit-selection'); 3520 } 3521 }).render() ); 3522 }, 3523 3524 /** 3525 * @param {wp.Backbone.View} view 3526 */ 3527 mainInsertToolbar: function( view ) { 3528 var controller = this; 3529 3530 this.selectionStatusToolbar( view ); 3531 3532 view.set( 'insert', { 3533 style: 'primary', 3534 priority: 80, 3535 text: l10n.insertIntoPost, 3536 requires: { selection: true }, 3537 3538 /** 3539 * @fires wp.media.controller.State#insert 3540 */ 3541 click: function() { 3542 var state = controller.state(), 3543 selection = state.get('selection'); 3544 3545 controller.close(); 3546 state.trigger( 'insert', selection ).reset(); 3547 } 3548 }); 3549 }, 3550 3551 /** 3552 * @param {wp.Backbone.View} view 3553 */ 3554 mainGalleryToolbar: function( view ) { 3555 var controller = this; 3556 3557 this.selectionStatusToolbar( view ); 3558 3559 view.set( 'gallery', { 3560 style: 'primary', 3561 text: l10n.createNewGallery, 3562 priority: 60, 3563 requires: { selection: true }, 3564 3565 click: function() { 3566 var selection = controller.state().get('selection'), 3567 edit = controller.state('gallery-edit'), 3568 models = selection.where({ type: 'image' }); 3569 3570 edit.set( 'library', new wp.media.model.Selection( models, { 3571 props: selection.props.toJSON(), 3572 multiple: true 3573 }) ); 3574 3575 this.controller.setState('gallery-edit'); 3576 3577 // Keep focus inside media modal 3578 // after jumping to gallery view 3579 this.controller.modal.focusManager.focus(); 3580 } 3581 }); 3582 }, 3583 3584 mainPlaylistToolbar: function( view ) { 3585 var controller = this; 3586 3587 this.selectionStatusToolbar( view ); 3588 3589 view.set( 'playlist', { 3590 style: 'primary', 3591 text: l10n.createNewPlaylist, 3592 priority: 100, 3593 requires: { selection: true }, 3594 3595 click: function() { 3596 var selection = controller.state().get('selection'), 3597 edit = controller.state('playlist-edit'), 3598 models = selection.where({ type: 'audio' }); 3599 3600 edit.set( 'library', new wp.media.model.Selection( models, { 3601 props: selection.props.toJSON(), 3602 multiple: true 3603 }) ); 3604 3605 this.controller.setState('playlist-edit'); 3606 3607 // Keep focus inside media modal 3608 // after jumping to playlist view 3609 this.controller.modal.focusManager.focus(); 3610 } 3611 }); 3612 }, 3613 3614 mainVideoPlaylistToolbar: function( view ) { 3615 var controller = this; 3616 3617 this.selectionStatusToolbar( view ); 3618 3619 view.set( 'video-playlist', { 3620 style: 'primary', 3621 text: l10n.createNewVideoPlaylist, 3622 priority: 100, 3623 requires: { selection: true }, 3624 3625 click: function() { 3626 var selection = controller.state().get('selection'), 3627 edit = controller.state('video-playlist-edit'), 3628 models = selection.where({ type: 'video' }); 3629 3630 edit.set( 'library', new wp.media.model.Selection( models, { 3631 props: selection.props.toJSON(), 3632 multiple: true 3633 }) ); 3634 3635 this.controller.setState('video-playlist-edit'); 3636 3637 // Keep focus inside media modal 3638 // after jumping to video playlist view 3639 this.controller.modal.focusManager.focus(); 3640 } 3641 }); 3642 }, 3643 3644 featuredImageToolbar: function( toolbar ) { 3645 this.createSelectToolbar( toolbar, { 3646 text: l10n.setFeaturedImage, 3647 state: this.options.state 3648 }); 3649 }, 3650 3651 mainEmbedToolbar: function( toolbar ) { 3652 toolbar.view = new wp.media.view.Toolbar.Embed({ 3653 controller: this 3654 }); 3655 }, 3656 3657 galleryEditToolbar: function() { 3658 var editing = this.state().get('editing'); 3659 this.toolbar.set( new wp.media.view.Toolbar({ 3660 controller: this, 3661 items: { 3662 insert: { 3663 style: 'primary', 3664 text: editing ? l10n.updateGallery : l10n.insertGallery, 3665 priority: 80, 3666 requires: { library: true }, 3667 3668 /** 3669 * @fires wp.media.controller.State#update 3670 */ 3671 click: function() { 3672 var controller = this.controller, 3673 state = controller.state(); 3674 3675 controller.close(); 3676 state.trigger( 'update', state.get('library') ); 3677 3678 // Restore and reset the default state. 3679 controller.setState( controller.options.state ); 3680 controller.reset(); 3681 } 3682 } 3683 } 3684 }) ); 3685 }, 3686 3687 galleryAddToolbar: function() { 3688 this.toolbar.set( new wp.media.view.Toolbar({ 3689 controller: this, 3690 items: { 3691 insert: { 3692 style: 'primary', 3693 text: l10n.addToGallery, 3694 priority: 80, 3695 requires: { selection: true }, 3696 3697 /** 3698 * @fires wp.media.controller.State#reset 3699 */ 3700 click: function() { 3701 var controller = this.controller, 3702 state = controller.state(), 3703 edit = controller.state('gallery-edit'); 3704 3705 edit.get('library').add( state.get('selection').models ); 3706 state.trigger('reset'); 3707 controller.setState('gallery-edit'); 3708 } 3709 } 3710 } 3711 }) ); 3712 }, 3713 3714 playlistEditToolbar: function() { 3715 var editing = this.state().get('editing'); 3716 this.toolbar.set( new wp.media.view.Toolbar({ 3717 controller: this, 3718 items: { 3719 insert: { 3720 style: 'primary', 3721 text: editing ? l10n.updatePlaylist : l10n.insertPlaylist, 3722 priority: 80, 3723 requires: { library: true }, 3724 3725 /** 3726 * @fires wp.media.controller.State#update 3727 */ 3728 click: function() { 3729 var controller = this.controller, 3730 state = controller.state(); 3731 3732 controller.close(); 3733 state.trigger( 'update', state.get('library') ); 3734 3735 // Restore and reset the default state. 3736 controller.setState( controller.options.state ); 3737 controller.reset(); 3738 } 3739 } 3740 } 3741 }) ); 3742 }, 3743 3744 playlistAddToolbar: function() { 3745 this.toolbar.set( new wp.media.view.Toolbar({ 3746 controller: this, 3747 items: { 3748 insert: { 3749 style: 'primary', 3750 text: l10n.addToPlaylist, 3751 priority: 80, 3752 requires: { selection: true }, 3753 3754 /** 3755 * @fires wp.media.controller.State#reset 3756 */ 3757 click: function() { 3758 var controller = this.controller, 3759 state = controller.state(), 3760 edit = controller.state('playlist-edit'); 3761 3762 edit.get('library').add( state.get('selection').models ); 3763 state.trigger('reset'); 3764 controller.setState('playlist-edit'); 3765 } 3766 } 3767 } 3768 }) ); 3769 }, 3770 3771 videoPlaylistEditToolbar: function() { 3772 var editing = this.state().get('editing'); 3773 this.toolbar.set( new wp.media.view.Toolbar({ 3774 controller: this, 3775 items: { 3776 insert: { 3777 style: 'primary', 3778 text: editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist, 3779 priority: 140, 3780 requires: { library: true }, 3781 3782 click: function() { 3783 var controller = this.controller, 3784 state = controller.state(), 3785 library = state.get('library'); 3786 3787 library.type = 'video'; 3788 3789 controller.close(); 3790 state.trigger( 'update', library ); 3791 3792 // Restore and reset the default state. 3793 controller.setState( controller.options.state ); 3794 controller.reset(); 3795 } 3796 } 3797 } 3798 }) ); 3799 }, 3800 3801 videoPlaylistAddToolbar: function() { 3802 this.toolbar.set( new wp.media.view.Toolbar({ 3803 controller: this, 3804 items: { 3805 insert: { 3806 style: 'primary', 3807 text: l10n.addToVideoPlaylist, 3808 priority: 140, 3809 requires: { selection: true }, 3810 3811 click: function() { 3812 var controller = this.controller, 3813 state = controller.state(), 3814 edit = controller.state('video-playlist-edit'); 3815 3816 edit.get('library').add( state.get('selection').models ); 3817 state.trigger('reset'); 3818 controller.setState('video-playlist-edit'); 3819 } 3820 } 3821 } 3822 }) ); 2077 var selectionSync = { 2078 /** 2079 * @since 3.5.0 2080 */ 2081 syncSelection: function() { 2082 var selection = this.get('selection'), 2083 manager = this.frame._selection; 2084 2085 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 2086 return; 2087 } 2088 2089 // If the selection supports multiple items, validate the stored 2090 // attachments based on the new selection's conditions. Record 2091 // the attachments that are not included; we'll maintain a 2092 // reference to those. Other attachments are considered in flux. 2093 if ( selection.multiple ) { 2094 selection.reset( [], { silent: true }); 2095 selection.validateAll( manager.attachments ); 2096 manager.difference = _.difference( manager.attachments.models, selection.models ); 2097 } 2098 2099 // Sync the selection's single item with the master. 2100 selection.single( manager.single ); 2101 }, 2102 2103 /** 2104 * Record the currently active attachments, which is a combination 2105 * of the selection's attachments and the set of selected 2106 * attachments that this specific selection considered invalid. 2107 * Reset the difference and record the single attachment. 2108 * 2109 * @since 3.5.0 2110 */ 2111 recordSelection: function() { 2112 var selection = this.get('selection'), 2113 manager = this.frame._selection; 2114 2115 if ( ! this.get('syncSelection') || ! manager || ! selection ) { 2116 return; 2117 } 2118 2119 if ( selection.multiple ) { 2120 manager.attachments.reset( selection.toArray().concat( manager.difference ) ); 2121 manager.difference = []; 2122 } else { 2123 manager.attachments.add( selection.toArray() ); 2124 } 2125 2126 manager.single = selection._single; 3823 2127 } 3824 }); 3825 3826 module.exports = Post; 3827 3828 3829 /***/ }), 3830 /* 50 */ 3831 /***/ (function(module, exports) { 2128 }; 2129 2130 module.exports = selectionSync; 2131 2132 },{}],19:[function(require,module,exports){ 2133 var media = wp.media, 2134 $ = jQuery, 2135 l10n; 2136 2137 media.isTouchDevice = ( 'ontouchend' in document ); 2138 2139 // Link any localized strings. 2140 l10n = media.view.l10n = window._wpMediaViewsL10n || {}; 2141 2142 // Link any settings. 2143 media.view.settings = l10n.settings || {}; 2144 delete l10n.settings; 2145 2146 // Copy the `post` setting over to the model settings. 2147 media.model.settings.post = media.view.settings.post; 2148 2149 // Check if the browser supports CSS 3.0 transitions 2150 $.support.transition = (function(){ 2151 var style = document.documentElement.style, 2152 transitions = { 2153 WebkitTransition: 'webkitTransitionEnd', 2154 MozTransition: 'transitionend', 2155 OTransition: 'oTransitionEnd otransitionend', 2156 transition: 'transitionend' 2157 }, transition; 2158 2159 transition = _.find( _.keys( transitions ), function( transition ) { 2160 return ! _.isUndefined( style[ transition ] ); 2161 }); 2162 2163 return transition && { 2164 end: transitions[ transition ] 2165 }; 2166 }()); 3832 2167 3833 2168 /** 3834 * wp.media.view.MediaFrame.ImageDetails 3835 * 3836 * A media frame for manipulating an image that's already been inserted 3837 * into a post. 3838 * 3839 * @class 3840 * @augments wp.media.view.MediaFrame.Select 3841 * @augments wp.media.view.MediaFrame 3842 * @augments wp.media.view.Frame 3843 * @augments wp.media.View 3844 * @augments wp.Backbone.View 3845 * @augments Backbone.View 3846 * @mixes wp.media.controller.StateMachine 2169 * A shared event bus used to provide events into 2170 * the media workflows that 3rd-party devs can use to hook 2171 * in. 3847 2172 */ 3848 var Select = wp.media.view.MediaFrame.Select, 3849 l10n = wp.media.view.l10n, 3850 ImageDetails; 3851 3852 ImageDetails = Select.extend({ 3853 defaults: { 3854 id: 'image', 3855 url: '', 3856 menu: 'image-details', 3857 content: 'image-details', 3858 toolbar: 'image-details', 3859 type: 'link', 3860 title: l10n.imageDetailsTitle, 3861 priority: 120 3862 }, 3863 3864 initialize: function( options ) { 3865 this.image = new wp.media.model.PostImage( options.metadata ); 3866 this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } ); 3867 Select.prototype.initialize.apply( this, arguments ); 3868 }, 3869 3870 bindHandlers: function() { 3871 Select.prototype.bindHandlers.apply( this, arguments ); 3872 this.on( 'menu:create:image-details', this.createMenu, this ); 3873 this.on( 'content:create:image-details', this.imageDetailsContent, this ); 3874 this.on( 'content:render:edit-image', this.editImageContent, this ); 3875 this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this ); 3876 // override the select toolbar 3877 this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this ); 3878 }, 3879 3880 createStates: function() { 3881 this.states.add([ 3882 new wp.media.controller.ImageDetails({ 3883 image: this.image, 3884 editable: false 3885 }), 3886 new wp.media.controller.ReplaceImage({ 3887 id: 'replace-image', 3888 library: wp.media.query( { type: 'image' } ), 3889 image: this.image, 3890 multiple: false, 3891 title: l10n.imageReplaceTitle, 3892 toolbar: 'replace', 3893 priority: 80, 3894 displaySettings: true 3895 }), 3896 new wp.media.controller.EditImage( { 3897 image: this.image, 3898 selection: this.options.selection 3899 } ) 3900 ]); 3901 }, 3902 3903 imageDetailsContent: function( options ) { 3904 options.view = new wp.media.view.ImageDetails({ 3905 controller: this, 3906 model: this.state().image, 3907 attachment: this.state().image.attachment 3908 }); 3909 }, 3910 3911 editImageContent: function() { 3912 var state = this.state(), 3913 model = state.get('image'), 3914 view; 3915 3916 if ( ! model ) { 3917 return; 3918 } 3919 3920 view = new wp.media.view.EditImage( { model: model, controller: this } ).render(); 3921 3922 this.content.set( view ); 3923 3924 // after bringing in the frame, load the actual editor via an ajax call 3925 view.loadEditor(); 3926 3927 }, 3928 3929 renderImageDetailsToolbar: function() { 3930 this.toolbar.set( new wp.media.view.Toolbar({ 3931 controller: this, 3932 items: { 3933 select: { 3934 style: 'primary', 3935 text: l10n.update, 3936 priority: 80, 3937 3938 click: function() { 3939 var controller = this.controller, 3940 state = controller.state(); 3941 3942 controller.close(); 3943 3944 // not sure if we want to use wp.media.string.image which will create a shortcode or 3945 // perhaps wp.html.string to at least to build the <img /> 3946 state.trigger( 'update', controller.image.toJSON() ); 3947 3948 // Restore and reset the default state. 3949 controller.setState( controller.options.state ); 3950 controller.reset(); 3951 } 3952 } 3953 } 3954 }) ); 3955 }, 3956 3957 renderReplaceImageToolbar: function() { 3958 var frame = this, 3959 lastState = frame.lastState(), 3960 previous = lastState && lastState.id; 3961 3962 this.toolbar.set( new wp.media.view.Toolbar({ 3963 controller: this, 3964 items: { 3965 back: { 3966 text: l10n.back, 3967 priority: 20, 3968 click: function() { 3969 if ( previous ) { 3970 frame.setState( previous ); 3971 } else { 3972 frame.close(); 3973 } 3974 } 3975 }, 3976 3977 replace: { 3978 style: 'primary', 3979 text: l10n.replace, 3980 priority: 80, 3981 3982 click: function() { 3983 var controller = this.controller, 3984 state = controller.state(), 3985 selection = state.get( 'selection' ), 3986 attachment = selection.single(); 3987 3988 controller.close(); 3989 3990 controller.image.changeAttachment( attachment, state.display( attachment ) ); 3991 3992 // not sure if we want to use wp.media.string.image which will create a shortcode or 3993 // perhaps wp.html.string to at least to build the <img /> 3994 state.trigger( 'replace', controller.image.toJSON() ); 3995 3996 // Restore and reset the default state. 3997 controller.setState( controller.options.state ); 3998 controller.reset(); 3999 } 4000 } 4001 } 4002 }) ); 2173 media.events = _.extend( {}, Backbone.Events ); 2174 2175 /** 2176 * Makes it easier to bind events using transitions. 2177 * 2178 * @param {string} selector 2179 * @param {Number} sensitivity 2180 * @returns {Promise} 2181 */ 2182 media.transition = function( selector, sensitivity ) { 2183 var deferred = $.Deferred(); 2184 2185 sensitivity = sensitivity || 2000; 2186 2187 if ( $.support.transition ) { 2188 if ( ! (selector instanceof $) ) { 2189 selector = $( selector ); 2190 } 2191 2192 // Resolve the deferred when the first element finishes animating. 2193 selector.first().one( $.support.transition.end, deferred.resolve ); 2194 2195 // Just in case the event doesn't trigger, fire a callback. 2196 _.delay( deferred.resolve, sensitivity ); 2197 2198 // Otherwise, execute on the spot. 2199 } else { 2200 deferred.resolve(); 4003 2201 } 4004 2202 4005 }); 4006 4007 module.exports = ImageDetails; 4008 4009 4010 /***/ }), 4011 /* 51 */ 4012 /***/ (function(module, exports) { 4013 2203 return deferred.promise(); 2204 }; 2205 2206 media.controller.Region = require( './controllers/region.js' ); 2207 media.controller.StateMachine = require( './controllers/state-machine.js' ); 2208 media.controller.State = require( './controllers/state.js' ); 2209 2210 media.selectionSync = require( './utils/selection-sync.js' ); 2211 media.controller.Library = require( './controllers/library.js' ); 2212 media.controller.ImageDetails = require( './controllers/image-details.js' ); 2213 media.controller.GalleryEdit = require( './controllers/gallery-edit.js' ); 2214 media.controller.GalleryAdd = require( './controllers/gallery-add.js' ); 2215 media.controller.CollectionEdit = require( './controllers/collection-edit.js' ); 2216 media.controller.CollectionAdd = require( './controllers/collection-add.js' ); 2217 media.controller.FeaturedImage = require( './controllers/featured-image.js' ); 2218 media.controller.ReplaceImage = require( './controllers/replace-image.js' ); 2219 media.controller.EditImage = require( './controllers/edit-image.js' ); 2220 media.controller.MediaLibrary = require( './controllers/media-library.js' ); 2221 media.controller.Embed = require( './controllers/embed.js' ); 2222 media.controller.Cropper = require( './controllers/cropper.js' ); 2223 media.controller.CustomizeImageCropper = require( './controllers/customize-image-cropper.js' ); 2224 media.controller.SiteIconCropper = require( './controllers/site-icon-cropper.js' ); 2225 2226 media.View = require( './views/view.js' ); 2227 media.view.Frame = require( './views/frame.js' ); 2228 media.view.MediaFrame = require( './views/media-frame.js' ); 2229 media.view.MediaFrame.Select = require( './views/frame/select.js' ); 2230 media.view.MediaFrame.Post = require( './views/frame/post.js' ); 2231 media.view.MediaFrame.ImageDetails = require( './views/frame/image-details.js' ); 2232 media.view.Modal = require( './views/modal.js' ); 2233 media.view.FocusManager = require( './views/focus-manager.js' ); 2234 media.view.UploaderWindow = require( './views/uploader/window.js' ); 2235 media.view.EditorUploader = require( './views/uploader/editor.js' ); 2236 media.view.UploaderInline = require( './views/uploader/inline.js' ); 2237 media.view.UploaderStatus = require( './views/uploader/status.js' ); 2238 media.view.UploaderStatusError = require( './views/uploader/status-error.js' ); 2239 media.view.Toolbar = require( './views/toolbar.js' ); 2240 media.view.Toolbar.Select = require( './views/toolbar/select.js' ); 2241 media.view.Toolbar.Embed = require( './views/toolbar/embed.js' ); 2242 media.view.Button = require( './views/button.js' ); 2243 media.view.ButtonGroup = require( './views/button-group.js' ); 2244 media.view.PriorityList = require( './views/priority-list.js' ); 2245 media.view.MenuItem = require( './views/menu-item.js' ); 2246 media.view.Menu = require( './views/menu.js' ); 2247 media.view.RouterItem = require( './views/router-item.js' ); 2248 media.view.Router = require( './views/router.js' ); 2249 media.view.Sidebar = require( './views/sidebar.js' ); 2250 media.view.Attachment = require( './views/attachment.js' ); 2251 media.view.Attachment.Library = require( './views/attachment/library.js' ); 2252 media.view.Attachment.EditLibrary = require( './views/attachment/edit-library.js' ); 2253 media.view.Attachments = require( './views/attachments.js' ); 2254 media.view.Search = require( './views/search.js' ); 2255 media.view.AttachmentFilters = require( './views/attachment-filters.js' ); 2256 media.view.DateFilter = require( './views/attachment-filters/date.js' ); 2257 media.view.AttachmentFilters.Uploaded = require( './views/attachment-filters/uploaded.js' ); 2258 media.view.AttachmentFilters.All = require( './views/attachment-filters/all.js' ); 2259 media.view.AttachmentsBrowser = require( './views/attachments/browser.js' ); 2260 media.view.Selection = require( './views/selection.js' ); 2261 media.view.Attachment.Selection = require( './views/attachment/selection.js' ); 2262 media.view.Attachments.Selection = require( './views/attachments/selection.js' ); 2263 media.view.Attachment.EditSelection = require( './views/attachment/edit-selection.js' ); 2264 media.view.Settings = require( './views/settings.js' ); 2265 media.view.Settings.AttachmentDisplay = require( './views/settings/attachment-display.js' ); 2266 media.view.Settings.Gallery = require( './views/settings/gallery.js' ); 2267 media.view.Settings.Playlist = require( './views/settings/playlist.js' ); 2268 media.view.Attachment.Details = require( './views/attachment/details.js' ); 2269 media.view.AttachmentCompat = require( './views/attachment-compat.js' ); 2270 media.view.Iframe = require( './views/iframe.js' ); 2271 media.view.Embed = require( './views/embed.js' ); 2272 media.view.Label = require( './views/label.js' ); 2273 media.view.EmbedUrl = require( './views/embed/url.js' ); 2274 media.view.EmbedLink = require( './views/embed/link.js' ); 2275 media.view.EmbedImage = require( './views/embed/image.js' ); 2276 media.view.ImageDetails = require( './views/image-details.js' ); 2277 media.view.Cropper = require( './views/cropper.js' ); 2278 media.view.SiteIconCropper = require( './views/site-icon-cropper.js' ); 2279 media.view.SiteIconPreview = require( './views/site-icon-preview.js' ); 2280 media.view.EditImage = require( './views/edit-image.js' ); 2281 media.view.Spinner = require( './views/spinner.js' ); 2282 2283 },{"./controllers/collection-add.js":1,"./controllers/collection-edit.js":2,"./controllers/cropper.js":3,"./controllers/customize-image-cropper.js":4,"./controllers/edit-image.js":5,"./controllers/embed.js":6,"./controllers/featured-image.js":7,"./controllers/gallery-add.js":8,"./controllers/gallery-edit.js":9,"./controllers/image-details.js":10,"./controllers/library.js":11,"./controllers/media-library.js":12,"./controllers/region.js":13,"./controllers/replace-image.js":14,"./controllers/site-icon-cropper.js":15,"./controllers/state-machine.js":16,"./controllers/state.js":17,"./utils/selection-sync.js":18,"./views/attachment-compat.js":20,"./views/attachment-filters.js":21,"./views/attachment-filters/all.js":22,"./views/attachment-filters/date.js":23,"./views/attachment-filters/uploaded.js":24,"./views/attachment.js":25,"./views/attachment/details.js":26,"./views/attachment/edit-library.js":27,"./views/attachment/edit-selection.js":28,"./views/attachment/library.js":29,"./views/attachment/selection.js":30,"./views/attachments.js":31,"./views/attachments/browser.js":32,"./views/attachments/selection.js":33,"./views/button-group.js":34,"./views/button.js":35,"./views/cropper.js":36,"./views/edit-image.js":37,"./views/embed.js":38,"./views/embed/image.js":39,"./views/embed/link.js":40,"./views/embed/url.js":41,"./views/focus-manager.js":42,"./views/frame.js":43,"./views/frame/image-details.js":44,"./views/frame/post.js":45,"./views/frame/select.js":46,"./views/iframe.js":47,"./views/image-details.js":48,"./views/label.js":49,"./views/media-frame.js":50,"./views/menu-item.js":51,"./views/menu.js":52,"./views/modal.js":53,"./views/priority-list.js":54,"./views/router-item.js":55,"./views/router.js":56,"./views/search.js":57,"./views/selection.js":58,"./views/settings.js":59,"./views/settings/attachment-display.js":60,"./views/settings/gallery.js":61,"./views/settings/playlist.js":62,"./views/sidebar.js":63,"./views/site-icon-cropper.js":64,"./views/site-icon-preview.js":65,"./views/spinner.js":66,"./views/toolbar.js":67,"./views/toolbar/embed.js":68,"./views/toolbar/select.js":69,"./views/uploader/editor.js":70,"./views/uploader/inline.js":71,"./views/uploader/status-error.js":72,"./views/uploader/status.js":73,"./views/uploader/window.js":74,"./views/view.js":75}],20:[function(require,module,exports){ 4014 2284 /** 4015 * wp.media.view.Modal 4016 * 4017 * A modal view, which the media modal uses as its default container. 4018 * 4019 * @class 4020 * @augments wp.media.View 4021 * @augments wp.Backbone.View 4022 * @augments Backbone.View 4023 */ 4024 var $ = jQuery, 4025 Modal; 4026 4027 Modal = wp.media.View.extend({ 4028 tagName: 'div', 4029 template: wp.template('media-modal'), 4030 4031 attributes: { 4032 tabindex: 0 4033 }, 4034 4035 events: { 4036 'click .media-modal-backdrop, .media-modal-close': 'escapeHandler', 4037 'keydown': 'keydown' 4038 }, 4039 4040 initialize: function() { 4041 _.defaults( this.options, { 4042 container: document.body, 4043 title: '', 4044 propagate: true, 4045 freeze: true 4046 }); 4047 4048 this.focusManager = new wp.media.view.FocusManager({ 4049 el: this.el 4050 }); 4051 }, 4052 /** 4053 * @returns {Object} 4054 */ 4055 prepare: function() { 4056 return { 4057 title: this.options.title 4058 }; 4059 }, 4060 4061 /** 4062 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4063 */ 4064 attach: function() { 4065 if ( this.views.attached ) { 4066 return this; 4067 } 4068 4069 if ( ! this.views.rendered ) { 4070 this.render(); 4071 } 4072 4073 this.$el.appendTo( this.options.container ); 4074 4075 // Manually mark the view as attached and trigger ready. 4076 this.views.attached = true; 4077 this.views.ready(); 4078 4079 return this.propagate('attach'); 4080 }, 4081 4082 /** 4083 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4084 */ 4085 detach: function() { 4086 if ( this.$el.is(':visible') ) { 4087 this.close(); 4088 } 4089 4090 this.$el.detach(); 4091 this.views.attached = false; 4092 return this.propagate('detach'); 4093 }, 4094 4095 /** 4096 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4097 */ 4098 open: function() { 4099 var $el = this.$el, 4100 options = this.options, 4101 mceEditor; 4102 4103 if ( $el.is(':visible') ) { 4104 return this; 4105 } 4106 4107 if ( ! this.views.attached ) { 4108 this.attach(); 4109 } 4110 4111 // If the `freeze` option is set, record the window's scroll position. 4112 if ( options.freeze ) { 4113 this._freeze = { 4114 scrollTop: $( window ).scrollTop() 4115 }; 4116 } 4117 4118 // Disable page scrolling. 4119 $( 'body' ).addClass( 'modal-open' ); 4120 4121 $el.show(); 4122 4123 // Try to close the onscreen keyboard 4124 if ( 'ontouchend' in document ) { 4125 if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor ) && ! mceEditor.isHidden() && mceEditor.iframeElement ) { 4126 mceEditor.iframeElement.focus(); 4127 mceEditor.iframeElement.blur(); 4128 4129 setTimeout( function() { 4130 mceEditor.iframeElement.blur(); 4131 }, 100 ); 4132 } 4133 } 4134 4135 this.$el.focus(); 4136 4137 return this.propagate('open'); 4138 }, 4139 4140 /** 4141 * @param {Object} options 4142 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4143 */ 4144 close: function( options ) { 4145 var freeze = this._freeze; 4146 4147 if ( ! this.views.attached || ! this.$el.is(':visible') ) { 4148 return this; 4149 } 4150 4151 // Enable page scrolling. 4152 $( 'body' ).removeClass( 'modal-open' ); 4153 4154 // Hide modal and remove restricted media modal tab focus once it's closed 4155 this.$el.hide().undelegate( 'keydown' ); 4156 4157 // Put focus back in useful location once modal is closed 4158 $('#wpbody-content').focus(); 4159 4160 this.propagate('close'); 4161 4162 // If the `freeze` option is set, restore the container's scroll position. 4163 if ( freeze ) { 4164 $( window ).scrollTop( freeze.scrollTop ); 4165 } 4166 4167 if ( options && options.escape ) { 4168 this.propagate('escape'); 4169 } 4170 4171 return this; 4172 }, 4173 /** 4174 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4175 */ 4176 escape: function() { 4177 return this.close({ escape: true }); 4178 }, 4179 /** 4180 * @param {Object} event 4181 */ 4182 escapeHandler: function( event ) { 4183 event.preventDefault(); 4184 this.escape(); 4185 }, 4186 4187 /** 4188 * @param {Array|Object} content Views to register to '.media-modal-content' 4189 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4190 */ 4191 content: function( content ) { 4192 this.views.set( '.media-modal-content', content ); 4193 return this; 4194 }, 4195 4196 /** 4197 * Triggers a modal event and if the `propagate` option is set, 4198 * forwards events to the modal's controller. 4199 * 4200 * @param {string} id 4201 * @returns {wp.media.view.Modal} Returns itself to allow chaining 4202 */ 4203 propagate: function( id ) { 4204 this.trigger( id ); 4205 4206 if ( this.options.propagate ) { 4207 this.controller.trigger( id ); 4208 } 4209 4210 return this; 4211 }, 4212 /** 4213 * @param {Object} event 4214 */ 4215 keydown: function( event ) { 4216 // Close the modal when escape is pressed. 4217 if ( 27 === event.which && this.$el.is(':visible') ) { 4218 this.escape(); 4219 event.stopImmediatePropagation(); 4220 } 4221 } 4222 }); 4223 4224 module.exports = Modal; 4225 4226 4227 /***/ }), 4228 /* 52 */ 4229 /***/ (function(module, exports) { 4230 4231 /** 4232 * wp.media.view.FocusManager 4233 * 4234 * @class 4235 * @augments wp.media.View 4236 * @augments wp.Backbone.View 4237 * @augments Backbone.View 4238 */ 4239 var FocusManager = wp.media.View.extend({ 4240 4241 events: { 4242 'keydown': 'constrainTabbing' 4243 }, 4244 4245 focus: function() { // Reset focus on first left menu item 4246 this.$('.media-menu-item').first().focus(); 4247 }, 4248 /** 4249 * @param {Object} event 4250 */ 4251 constrainTabbing: function( event ) { 4252 var tabbables; 4253 4254 // Look for the tab key. 4255 if ( 9 !== event.keyCode ) { 4256 return; 4257 } 4258 4259 // Skip the file input added by Plupload. 4260 tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' ); 4261 4262 // Keep tab focus within media modal while it's open 4263 if ( tabbables.last()[0] === event.target && ! event.shiftKey ) { 4264 tabbables.first().focus(); 4265 return false; 4266 } else if ( tabbables.first()[0] === event.target && event.shiftKey ) { 4267 tabbables.last().focus(); 4268 return false; 4269 } 4270 } 4271 4272 }); 4273 4274 module.exports = FocusManager; 4275 4276 4277 /***/ }), 4278 /* 53 */ 4279 /***/ (function(module, exports) { 4280 4281 /** 4282 * wp.media.view.UploaderWindow 4283 * 4284 * An uploader window that allows for dragging and dropping media. 4285 * 4286 * @class 4287 * @augments wp.media.View 4288 * @augments wp.Backbone.View 4289 * @augments Backbone.View 4290 * 4291 * @param {object} [options] Options hash passed to the view. 4292 * @param {object} [options.uploader] Uploader properties. 4293 * @param {jQuery} [options.uploader.browser] 4294 * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone. 4295 * @param {object} [options.uploader.params] 4296 */ 4297 var $ = jQuery, 4298 UploaderWindow; 4299 4300 UploaderWindow = wp.media.View.extend({ 4301 tagName: 'div', 4302 className: 'uploader-window', 4303 template: wp.template('uploader-window'), 4304 4305 initialize: function() { 4306 var uploader; 4307 4308 this.$browser = $('<a href="#" class="browser" />').hide().appendTo('body'); 4309 4310 uploader = this.options.uploader = _.defaults( this.options.uploader || {}, { 4311 dropzone: this.$el, 4312 browser: this.$browser, 4313 params: {} 4314 }); 4315 4316 // Ensure the dropzone is a jQuery collection. 4317 if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) { 4318 uploader.dropzone = $( uploader.dropzone ); 4319 } 4320 4321 this.controller.on( 'activate', this.refresh, this ); 4322 4323 this.controller.on( 'detach', function() { 4324 this.$browser.remove(); 4325 }, this ); 4326 }, 4327 4328 refresh: function() { 4329 if ( this.uploader ) { 4330 this.uploader.refresh(); 4331 } 4332 }, 4333 4334 ready: function() { 4335 var postId = wp.media.view.settings.post.id, 4336 dropzone; 4337 4338 // If the uploader already exists, bail. 4339 if ( this.uploader ) { 4340 return; 4341 } 4342 4343 if ( postId ) { 4344 this.options.uploader.params.post_id = postId; 4345 } 4346 this.uploader = new wp.Uploader( this.options.uploader ); 4347 4348 dropzone = this.uploader.dropzone; 4349 dropzone.on( 'dropzone:enter', _.bind( this.show, this ) ); 4350 dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) ); 4351 4352 $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) ); 4353 }, 4354 4355 _ready: function() { 4356 this.controller.trigger( 'uploader:ready' ); 4357 }, 4358 4359 show: function() { 4360 var $el = this.$el.show(); 4361 4362 // Ensure that the animation is triggered by waiting until 4363 // the transparent element is painted into the DOM. 4364 _.defer( function() { 4365 $el.css({ opacity: 1 }); 4366 }); 4367 }, 4368 4369 hide: function() { 4370 var $el = this.$el.css({ opacity: 0 }); 4371 4372 wp.media.transition( $el ).done( function() { 4373 // Transition end events are subject to race conditions. 4374 // Make sure that the value is set as intended. 4375 if ( '0' === $el.css('opacity') ) { 4376 $el.hide(); 4377 } 4378 }); 4379 4380 // https://core.trac.wordpress.org/ticket/27341 4381 _.delay( function() { 4382 if ( '0' === $el.css('opacity') && $el.is(':visible') ) { 4383 $el.hide(); 4384 } 4385 }, 500 ); 4386 } 4387 }); 4388 4389 module.exports = UploaderWindow; 4390 4391 4392 /***/ }), 4393 /* 54 */ 4394 /***/ (function(module, exports) { 4395 4396 /** 4397 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap) 4398 * and relays drag'n'dropped files to a media workflow. 4399 * 4400 * wp.media.view.EditorUploader 2285 * wp.media.view.AttachmentCompat 2286 * 2287 * A view to display fields added via the `attachment_fields_to_edit` filter. 4401 2288 * 4402 2289 * @class … … 4406 2293 */ 4407 2294 var View = wp.media.View, 4408 l10n = wp.media.view.l10n, 4409 $ = jQuery, 4410 EditorUploader; 4411 4412 EditorUploader = View.extend({ 4413 tagName: 'div', 4414 className: 'uploader-editor', 4415 template: wp.template( 'uploader-editor' ), 4416 4417 localDrag: false, 4418 overContainer: false, 4419 overDropzone: false, 4420 draggingFile: null, 4421 4422 /** 4423 * Bind drag'n'drop events to callbacks. 4424 */ 2295 AttachmentCompat; 2296 2297 AttachmentCompat = View.extend({ 2298 tagName: 'form', 2299 className: 'compat-item', 2300 2301 events: { 2302 'submit': 'preventDefault', 2303 'change input': 'save', 2304 'change select': 'save', 2305 'change textarea': 'save' 2306 }, 2307 4425 2308 initialize: function() { 4426 this.initialized = false; 4427 4428 // Bail if not enabled or UA does not support drag'n'drop or File API. 4429 if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) { 4430 return this; 4431 } 4432 4433 this.$document = $(document); 4434 this.dropzones = []; 4435 this.files = []; 4436 4437 this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) ); 4438 this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) ); 4439 this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) ); 4440 this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) ); 4441 4442 this.$document.on( 'dragover', _.bind( this.containerDragover, this ) ); 4443 this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) ); 4444 4445 this.$document.on( 'dragstart dragend drop', _.bind( function( event ) { 4446 this.localDrag = event.type === 'dragstart'; 4447 }, this ) ); 4448 4449 this.initialized = true; 4450 return this; 4451 }, 4452 4453 /** 4454 * Check browser support for drag'n'drop. 4455 * 4456 * @return Boolean 4457 */ 4458 browserSupport: function() { 4459 var supports = false, div = document.createElement('div'); 4460 4461 supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div ); 4462 supports = supports && !! ( window.File && window.FileList && window.FileReader ); 4463 return supports; 4464 }, 4465 4466 isDraggingFile: function( event ) { 4467 if ( this.draggingFile !== null ) { 4468 return this.draggingFile; 4469 } 4470 4471 if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) { 4472 return false; 4473 } 4474 4475 this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 && 4476 _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1; 4477 4478 return this.draggingFile; 4479 }, 4480 4481 refresh: function( e ) { 4482 var dropzone_id; 4483 for ( dropzone_id in this.dropzones ) { 4484 // Hide the dropzones only if dragging has left the screen. 4485 this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone ); 4486 } 4487 4488 if ( ! _.isUndefined( e ) ) { 4489 $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone ); 4490 } 4491 4492 if ( ! this.overContainer && ! this.overDropzone ) { 4493 this.draggingFile = null; 4494 } 4495 4496 return this; 4497 }, 4498 4499 render: function() { 4500 if ( ! this.initialized ) { 4501 return this; 4502 } 4503 4504 View.prototype.render.apply( this, arguments ); 4505 $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) ); 4506 return this; 4507 }, 4508 4509 attach: function( index, editor ) { 4510 // Attach a dropzone to an editor. 4511 var dropzone = this.$el.clone(); 4512 this.dropzones.push( dropzone ); 4513 $( editor ).append( dropzone ); 4514 return this; 4515 }, 4516 4517 /** 4518 * When a file is dropped on the editor uploader, open up an editor media workflow 4519 * and upload the file immediately. 4520 * 4521 * @param {jQuery.Event} event The 'drop' event. 4522 */ 4523 drop: function( event ) { 4524 var $wrap, uploadView; 4525 4526 this.containerDragleave( event ); 4527 this.dropzoneDragleave( event ); 4528 4529 this.files = event.originalEvent.dataTransfer.files; 4530 if ( this.files.length < 1 ) { 4531 return; 4532 } 4533 4534 // Set the active editor to the drop target. 4535 $wrap = $( event.target ).parents( '.wp-editor-wrap' ); 4536 if ( $wrap.length > 0 && $wrap[0].id ) { 4537 window.wpActiveEditor = $wrap[0].id.slice( 3, -5 ); 4538 } 4539 4540 if ( ! this.workflow ) { 4541 this.workflow = wp.media.editor.open( window.wpActiveEditor, { 4542 frame: 'post', 4543 state: 'insert', 4544 title: l10n.addMedia, 4545 multiple: true 4546 }); 4547 4548 uploadView = this.workflow.uploader; 4549 4550 if ( uploadView.uploader && uploadView.uploader.ready ) { 4551 this.addFiles.apply( this ); 4552 } else { 4553 this.workflow.on( 'uploader:ready', this.addFiles, this ); 4554 } 4555 } else { 4556 this.workflow.state().reset(); 4557 this.addFiles.apply( this ); 4558 this.workflow.open(); 4559 } 4560 4561 return false; 4562 }, 4563 4564 /** 4565 * Add the files to the uploader. 4566 */ 4567 addFiles: function() { 4568 if ( this.files.length ) { 4569 this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) ); 4570 this.files = []; 4571 } 4572 return this; 4573 }, 4574 4575 containerDragover: function( event ) { 4576 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 4577 return; 4578 } 4579 4580 this.overContainer = true; 4581 this.refresh(); 4582 }, 4583 4584 containerDragleave: function() { 4585 this.overContainer = false; 4586 4587 // Throttle dragleave because it's called when bouncing from some elements to others. 4588 _.delay( _.bind( this.refresh, this ), 50 ); 4589 }, 4590 4591 dropzoneDragover: function( event ) { 4592 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 4593 return; 4594 } 4595 4596 this.overDropzone = true; 4597 this.refresh( event ); 4598 return false; 4599 }, 4600 4601 dropzoneDragleave: function( e ) { 4602 this.overDropzone = false; 4603 _.delay( _.bind( this.refresh, this, e ), 50 ); 4604 }, 4605 4606 click: function( e ) { 4607 // In the rare case where the dropzone gets stuck, hide it on click. 4608 this.containerDragleave( e ); 4609 this.dropzoneDragleave( e ); 4610 this.localDrag = false; 4611 } 4612 }); 4613 4614 module.exports = EditorUploader; 4615 4616 4617 /***/ }), 4618 /* 55 */ 4619 /***/ (function(module, exports) { 4620 4621 /** 4622 * wp.media.view.UploaderInline 4623 * 4624 * The inline uploader that shows up in the 'Upload Files' tab. 4625 * 4626 * @class 4627 * @augments wp.media.View 4628 * @augments wp.Backbone.View 4629 * @augments Backbone.View 4630 */ 4631 var View = wp.media.View, 4632 UploaderInline; 4633 4634 UploaderInline = View.extend({ 4635 tagName: 'div', 4636 className: 'uploader-inline', 4637 template: wp.template('uploader-inline'), 4638 4639 events: { 4640 'click .close': 'hide' 4641 }, 4642 4643 initialize: function() { 4644 _.defaults( this.options, { 4645 message: '', 4646 status: true, 4647 canClose: false 4648 }); 4649 4650 if ( ! this.options.$browser && this.controller.uploader ) { 4651 this.options.$browser = this.controller.uploader.$browser; 4652 } 4653 4654 if ( _.isUndefined( this.options.postId ) ) { 4655 this.options.postId = wp.media.view.settings.post.id; 4656 } 4657 4658 if ( this.options.status ) { 4659 this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({ 4660 controller: this.controller 4661 }) ); 4662 } 4663 }, 4664 4665 prepare: function() { 4666 var suggestedWidth = this.controller.state().get('suggestedWidth'), 4667 suggestedHeight = this.controller.state().get('suggestedHeight'), 4668 data = {}; 4669 4670 data.message = this.options.message; 4671 data.canClose = this.options.canClose; 4672 4673 if ( suggestedWidth && suggestedHeight ) { 4674 data.suggestedWidth = suggestedWidth; 4675 data.suggestedHeight = suggestedHeight; 4676 } 4677 4678 return data; 4679 }, 4680 /** 4681 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining 2309 this.listenTo( this.model, 'change:compat', this.render ); 2310 }, 2311 /** 2312 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 4682 2313 */ 4683 2314 dispose: function() { 4684 if ( this.disposing ) { 4685 /** 4686 * call 'dispose' directly on the parent class 4687 */ 4688 return View.prototype.dispose.apply( this, arguments ); 4689 } 4690 4691 // Run remove on `dispose`, so we can be sure to refresh the 4692 // uploader with a view-less DOM. Track whether we're disposing 4693 // so we don't trigger an infinite loop. 4694 this.disposing = true; 4695 return this.remove(); 4696 }, 4697 /** 4698 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining 4699 */ 4700 remove: function() { 4701 /** 4702 * call 'remove' directly on the parent class 4703 */ 4704 var result = View.prototype.remove.apply( this, arguments ); 4705 4706 _.defer( _.bind( this.refresh, this ) ); 4707 return result; 4708 }, 4709 4710 refresh: function() { 4711 var uploader = this.controller.uploader; 4712 4713 if ( uploader ) { 4714 uploader.refresh(); 4715 } 4716 }, 4717 /** 4718 * @returns {wp.media.view.UploaderInline} 4719 */ 4720 ready: function() { 4721 var $browser = this.options.$browser, 4722 $placeholder; 4723 4724 if ( this.controller.uploader ) { 4725 $placeholder = this.$('.browser'); 4726 4727 // Check if we've already replaced the placeholder. 4728 if ( $placeholder[0] === $browser[0] ) { 4729 return; 4730 } 4731 4732 $browser.detach().text( $placeholder.text() ); 4733 $browser[0].className = $placeholder[0].className; 4734 $placeholder.replaceWith( $browser.show() ); 4735 } 4736 4737 this.refresh(); 4738 return this; 4739 }, 4740 show: function() { 4741 this.$el.removeClass( 'hidden' ); 4742 }, 4743 hide: function() { 4744 this.$el.addClass( 'hidden' ); 4745 } 4746 4747 }); 4748 4749 module.exports = UploaderInline; 4750 4751 4752 /***/ }), 4753 /* 56 */ 4754 /***/ (function(module, exports) { 4755 4756 /** 4757 * wp.media.view.UploaderStatus 4758 * 4759 * An uploader status for on-going uploads. 4760 * 4761 * @class 4762 * @augments wp.media.View 4763 * @augments wp.Backbone.View 4764 * @augments Backbone.View 4765 */ 4766 var View = wp.media.View, 4767 UploaderStatus; 4768 4769 UploaderStatus = View.extend({ 4770 className: 'media-uploader-status', 4771 template: wp.template('uploader-status'), 4772 4773 events: { 4774 'click .upload-dismiss-errors': 'dismiss' 4775 }, 4776 4777 initialize: function() { 4778 this.queue = wp.Uploader.queue; 4779 this.queue.on( 'add remove reset', this.visibility, this ); 4780 this.queue.on( 'add remove reset change:percent', this.progress, this ); 4781 this.queue.on( 'add remove reset change:uploading', this.info, this ); 4782 4783 this.errors = wp.Uploader.errors; 4784 this.errors.reset(); 4785 this.errors.on( 'add remove reset', this.visibility, this ); 4786 this.errors.on( 'add', this.error, this ); 4787 }, 4788 /** 4789 * @global wp.Uploader 4790 * @returns {wp.media.view.UploaderStatus} 4791 */ 4792 dispose: function() { 4793 wp.Uploader.queue.off( null, null, this ); 4794 /** 4795 * call 'dispose' directly on the parent class 4796 */ 4797 View.prototype.dispose.apply( this, arguments ); 4798 return this; 4799 }, 4800 4801 visibility: function() { 4802 this.$el.toggleClass( 'uploading', !! this.queue.length ); 4803 this.$el.toggleClass( 'errors', !! this.errors.length ); 4804 this.$el.toggle( !! this.queue.length || !! this.errors.length ); 4805 }, 4806 4807 ready: function() { 4808 _.each({ 4809 '$bar': '.media-progress-bar div', 4810 '$index': '.upload-index', 4811 '$total': '.upload-total', 4812 '$filename': '.upload-filename' 4813 }, function( selector, key ) { 4814 this[ key ] = this.$( selector ); 4815 }, this ); 4816 4817 this.visibility(); 4818 this.progress(); 4819 this.info(); 4820 }, 4821 4822 progress: function() { 4823 var queue = this.queue, 4824 $bar = this.$bar; 4825 4826 if ( ! $bar || ! queue.length ) { 4827 return; 4828 } 4829 4830 $bar.width( ( queue.reduce( function( memo, attachment ) { 4831 if ( ! attachment.get('uploading') ) { 4832 return memo + 100; 4833 } 4834 4835 var percent = attachment.get('percent'); 4836 return memo + ( _.isNumber( percent ) ? percent : 100 ); 4837 }, 0 ) / queue.length ) + '%' ); 4838 }, 4839 4840 info: function() { 4841 var queue = this.queue, 4842 index = 0, active; 4843 4844 if ( ! queue.length ) { 4845 return; 4846 } 4847 4848 active = this.queue.find( function( attachment, i ) { 4849 index = i; 4850 return attachment.get('uploading'); 4851 }); 4852 4853 this.$index.text( index + 1 ); 4854 this.$total.text( queue.length ); 4855 this.$filename.html( active ? this.filename( active.get('filename') ) : '' ); 4856 }, 4857 /** 4858 * @param {string} filename 4859 * @returns {string} 4860 */ 4861 filename: function( filename ) { 4862 return _.escape( filename ); 4863 }, 4864 /** 4865 * @param {Backbone.Model} error 4866 */ 4867 error: function( error ) { 4868 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({ 4869 filename: this.filename( error.get('file').name ), 4870 message: error.get('message') 4871 }), { at: 0 }); 4872 }, 4873 4874 /** 4875 * @global wp.Uploader 4876 * 4877 * @param {Object} event 4878 */ 4879 dismiss: function( event ) { 4880 var errors = this.views.get('.upload-errors'); 4881 4882 event.preventDefault(); 4883 4884 if ( errors ) { 4885 _.invoke( errors, 'remove' ); 4886 } 4887 wp.Uploader.errors.reset(); 4888 } 4889 }); 4890 4891 module.exports = UploaderStatus; 4892 4893 4894 /***/ }), 4895 /* 57 */ 4896 /***/ (function(module, exports) { 4897 4898 /** 4899 * wp.media.view.UploaderStatusError 4900 * 4901 * @class 4902 * @augments wp.media.View 4903 * @augments wp.Backbone.View 4904 * @augments Backbone.View 4905 */ 4906 var UploaderStatusError = wp.media.View.extend({ 4907 className: 'upload-error', 4908 template: wp.template('uploader-status-error') 4909 }); 4910 4911 module.exports = UploaderStatusError; 4912 4913 4914 /***/ }), 4915 /* 58 */ 4916 /***/ (function(module, exports) { 4917 4918 /** 4919 * wp.media.view.Toolbar 4920 * 4921 * A toolbar which consists of a primary and a secondary section. Each sections 4922 * can be filled with views. 4923 * 4924 * @class 4925 * @augments wp.media.View 4926 * @augments wp.Backbone.View 4927 * @augments Backbone.View 4928 */ 4929 var View = wp.media.View, 4930 Toolbar; 4931 4932 Toolbar = View.extend({ 4933 tagName: 'div', 4934 className: 'media-toolbar', 4935 4936 initialize: function() { 4937 var state = this.controller.state(), 4938 selection = this.selection = state.get('selection'), 4939 library = this.library = state.get('library'); 4940 4941 this._views = {}; 4942 4943 // The toolbar is composed of two `PriorityList` views. 4944 this.primary = new wp.media.view.PriorityList(); 4945 this.secondary = new wp.media.view.PriorityList(); 4946 this.primary.$el.addClass('media-toolbar-primary search-form'); 4947 this.secondary.$el.addClass('media-toolbar-secondary'); 4948 4949 this.views.set([ this.secondary, this.primary ]); 4950 4951 if ( this.options.items ) { 4952 this.set( this.options.items, { silent: true }); 4953 } 4954 4955 if ( ! this.options.silent ) { 4956 this.render(); 4957 } 4958 4959 if ( selection ) { 4960 selection.on( 'add remove reset', this.refresh, this ); 4961 } 4962 4963 if ( library ) { 4964 library.on( 'add remove reset', this.refresh, this ); 4965 } 4966 }, 4967 /** 4968 * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining 4969 */ 4970 dispose: function() { 4971 if ( this.selection ) { 4972 this.selection.off( null, null, this ); 4973 } 4974 4975 if ( this.library ) { 4976 this.library.off( null, null, this ); 2315 if ( this.$(':focus').length ) { 2316 this.save(); 4977 2317 } 4978 2318 /** … … 4981 2321 return View.prototype.dispose.apply( this, arguments ); 4982 2322 }, 4983 4984 ready: function() { 4985 this.refresh(); 4986 }, 4987 4988 /** 4989 * @param {string} id 4990 * @param {Backbone.View|Object} view 4991 * @param {Object} [options={}] 4992 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining 4993 */ 4994 set: function( id, view, options ) { 4995 var list; 4996 options = options || {}; 4997 4998 // Accept an object with an `id` : `view` mapping. 4999 if ( _.isObject( id ) ) { 5000 _.each( id, function( view, id ) { 5001 this.set( id, view, { silent: true }); 5002 }, this ); 5003 5004 } else { 5005 if ( ! ( view instanceof Backbone.View ) ) { 5006 view.classes = [ 'media-button-' + id ].concat( view.classes || [] ); 5007 view = new wp.media.view.Button( view ).render(); 5008 } 5009 5010 view.controller = view.controller || this.controller; 5011 5012 this._views[ id ] = view; 5013 5014 list = view.options.priority < 0 ? 'secondary' : 'primary'; 5015 this[ list ].set( id, view, options ); 5016 } 5017 5018 if ( ! options.silent ) { 5019 this.refresh(); 5020 } 5021 2323 /** 2324 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 2325 */ 2326 render: function() { 2327 var compat = this.model.get('compat'); 2328 if ( ! compat || ! compat.item ) { 2329 return; 2330 } 2331 2332 this.views.detach(); 2333 this.$el.html( compat.item ); 2334 this.views.render(); 5022 2335 return this; 5023 2336 }, 5024 2337 /** 5025 * @param {string} id 5026 * @returns {wp.media.view.Button} 5027 */ 5028 get: function( id ) { 5029 return this._views[ id ]; 5030 }, 5031 /** 5032 * @param {string} id 5033 * @param {Object} options 5034 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining 5035 */ 5036 unset: function( id, options ) { 5037 delete this._views[ id ]; 5038 this.primary.unset( id, options ); 5039 this.secondary.unset( id, options ); 5040 5041 if ( ! options || ! options.silent ) { 5042 this.refresh(); 5043 } 5044 return this; 5045 }, 5046 5047 refresh: function() { 5048 var state = this.controller.state(), 5049 library = state.get('library'), 5050 selection = state.get('selection'); 5051 5052 _.each( this._views, function( button ) { 5053 if ( ! button.model || ! button.options || ! button.options.requires ) { 5054 return; 5055 } 5056 5057 var requires = button.options.requires, 5058 disabled = false; 5059 5060 // Prevent insertion of attachments if any of them are still uploading 5061 disabled = _.some( selection.models, function( attachment ) { 5062 return attachment.get('uploading') === true; 5063 }); 5064 5065 if ( requires.selection && selection && ! selection.length ) { 5066 disabled = true; 5067 } else if ( requires.library && library && ! library.length ) { 5068 disabled = true; 5069 } 5070 button.model.set( 'disabled', disabled ); 2338 * @param {Object} event 2339 */ 2340 preventDefault: function( event ) { 2341 event.preventDefault(); 2342 }, 2343 /** 2344 * @param {Object} event 2345 */ 2346 save: function( event ) { 2347 var data = {}; 2348 2349 if ( event ) { 2350 event.preventDefault(); 2351 } 2352 2353 _.each( this.$el.serializeArray(), function( pair ) { 2354 data[ pair.name ] = pair.value; 5071 2355 }); 2356 2357 this.controller.trigger( 'attachment:compat:waiting', ['waiting'] ); 2358 this.model.saveCompat( data ).always( _.bind( this.postSave, this ) ); 2359 }, 2360 2361 postSave: function() { 2362 this.controller.trigger( 'attachment:compat:ready', ['ready'] ); 5072 2363 } 5073 2364 }); 5074 2365 5075 module.exports = Toolbar; 5076 5077 5078 /***/ }), 5079 /* 59 */ 5080 /***/ (function(module, exports) { 5081 5082 /** 5083 * wp.media.view.Toolbar.Select 5084 * 5085 * @class 5086 * @augments wp.media.view.Toolbar 5087 * @augments wp.media.View 5088 * @augments wp.Backbone.View 5089 * @augments Backbone.View 5090 */ 5091 var Toolbar = wp.media.view.Toolbar, 5092 l10n = wp.media.view.l10n, 5093 Select; 5094 5095 Select = Toolbar.extend({ 5096 initialize: function() { 5097 var options = this.options; 5098 5099 _.bindAll( this, 'clickSelect' ); 5100 5101 _.defaults( options, { 5102 event: 'select', 5103 state: false, 5104 reset: true, 5105 close: true, 5106 text: l10n.select, 5107 5108 // Does the button rely on the selection? 5109 requires: { 5110 selection: true 5111 } 5112 }); 5113 5114 options.items = _.defaults( options.items || {}, { 5115 select: { 5116 style: 'primary', 5117 text: options.text, 5118 priority: 80, 5119 click: this.clickSelect, 5120 requires: options.requires 5121 } 5122 }); 5123 // Call 'initialize' directly on the parent class. 5124 Toolbar.prototype.initialize.apply( this, arguments ); 5125 }, 5126 5127 clickSelect: function() { 5128 var options = this.options, 5129 controller = this.controller; 5130 5131 if ( options.close ) { 5132 controller.close(); 5133 } 5134 5135 if ( options.event ) { 5136 controller.state().trigger( options.event ); 5137 } 5138 5139 if ( options.state ) { 5140 controller.setState( options.state ); 5141 } 5142 5143 if ( options.reset ) { 5144 controller.reset(); 5145 } 5146 } 5147 }); 5148 5149 module.exports = Select; 5150 5151 5152 /***/ }), 5153 /* 60 */ 5154 /***/ (function(module, exports) { 5155 5156 /** 5157 * wp.media.view.Toolbar.Embed 5158 * 5159 * @class 5160 * @augments wp.media.view.Toolbar.Select 5161 * @augments wp.media.view.Toolbar 5162 * @augments wp.media.View 5163 * @augments wp.Backbone.View 5164 * @augments Backbone.View 5165 */ 5166 var Select = wp.media.view.Toolbar.Select, 5167 l10n = wp.media.view.l10n, 5168 Embed; 5169 5170 Embed = Select.extend({ 5171 initialize: function() { 5172 _.defaults( this.options, { 5173 text: l10n.insertIntoPost, 5174 requires: false 5175 }); 5176 // Call 'initialize' directly on the parent class. 5177 Select.prototype.initialize.apply( this, arguments ); 5178 }, 5179 5180 refresh: function() { 5181 var url = this.controller.state().props.get('url'); 5182 this.get('select').model.set( 'disabled', ! url || url === 'http://' ); 5183 /** 5184 * call 'refresh' directly on the parent class 5185 */ 5186 Select.prototype.refresh.apply( this, arguments ); 5187 } 5188 }); 5189 5190 module.exports = Embed; 5191 5192 5193 /***/ }), 5194 /* 61 */ 5195 /***/ (function(module, exports) { 5196 5197 /** 5198 * wp.media.view.Button 5199 * 5200 * @class 5201 * @augments wp.media.View 5202 * @augments wp.Backbone.View 5203 * @augments Backbone.View 5204 */ 5205 var Button = wp.media.View.extend({ 5206 tagName: 'button', 5207 className: 'media-button', 5208 attributes: { type: 'button' }, 5209 5210 events: { 5211 'click': 'click' 5212 }, 5213 5214 defaults: { 5215 text: '', 5216 style: '', 5217 size: 'large', 5218 disabled: false 5219 }, 5220 5221 initialize: function() { 5222 /** 5223 * Create a model with the provided `defaults`. 5224 * 5225 * @member {Backbone.Model} 5226 */ 5227 this.model = new Backbone.Model( this.defaults ); 5228 5229 // If any of the `options` have a key from `defaults`, apply its 5230 // value to the `model` and remove it from the `options object. 5231 _.each( this.defaults, function( def, key ) { 5232 var value = this.options[ key ]; 5233 if ( _.isUndefined( value ) ) { 5234 return; 5235 } 5236 5237 this.model.set( key, value ); 5238 delete this.options[ key ]; 5239 }, this ); 5240 5241 this.listenTo( this.model, 'change', this.render ); 5242 }, 5243 /** 5244 * @returns {wp.media.view.Button} Returns itself to allow chaining 5245 */ 5246 render: function() { 5247 var classes = [ 'button', this.className ], 5248 model = this.model.toJSON(); 5249 5250 if ( model.style ) { 5251 classes.push( 'button-' + model.style ); 5252 } 5253 5254 if ( model.size ) { 5255 classes.push( 'button-' + model.size ); 5256 } 5257 5258 classes = _.uniq( classes.concat( this.options.classes ) ); 5259 this.el.className = classes.join(' '); 5260 5261 this.$el.attr( 'disabled', model.disabled ); 5262 this.$el.text( this.model.get('text') ); 5263 5264 return this; 5265 }, 5266 /** 5267 * @param {Object} event 5268 */ 5269 click: function( event ) { 5270 if ( '#' === this.attributes.href ) { 5271 event.preventDefault(); 5272 } 5273 5274 if ( this.options.click && ! this.model.get('disabled') ) { 5275 this.options.click.apply( this, arguments ); 5276 } 5277 } 5278 }); 5279 5280 module.exports = Button; 5281 5282 5283 /***/ }), 5284 /* 62 */ 5285 /***/ (function(module, exports) { 5286 5287 /** 5288 * wp.media.view.ButtonGroup 5289 * 5290 * @class 5291 * @augments wp.media.View 5292 * @augments wp.Backbone.View 5293 * @augments Backbone.View 5294 */ 5295 var $ = Backbone.$, 5296 ButtonGroup; 5297 5298 ButtonGroup = wp.media.View.extend({ 5299 tagName: 'div', 5300 className: 'button-group button-large media-button-group', 5301 5302 initialize: function() { 5303 /** 5304 * @member {wp.media.view.Button[]} 5305 */ 5306 this.buttons = _.map( this.options.buttons || [], function( button ) { 5307 if ( button instanceof Backbone.View ) { 5308 return button; 5309 } else { 5310 return new wp.media.view.Button( button ).render(); 5311 } 5312 }); 5313 5314 delete this.options.buttons; 5315 5316 if ( this.options.classes ) { 5317 this.$el.addClass( this.options.classes ); 5318 } 5319 }, 5320 5321 /** 5322 * @returns {wp.media.view.ButtonGroup} 5323 */ 5324 render: function() { 5325 this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() ); 5326 return this; 5327 } 5328 }); 5329 5330 module.exports = ButtonGroup; 5331 5332 5333 /***/ }), 5334 /* 63 */ 5335 /***/ (function(module, exports) { 5336 5337 /** 5338 * wp.media.view.PriorityList 5339 * 5340 * @class 5341 * @augments wp.media.View 5342 * @augments wp.Backbone.View 5343 * @augments Backbone.View 5344 */ 5345 var PriorityList = wp.media.View.extend({ 5346 tagName: 'div', 5347 5348 initialize: function() { 5349 this._views = {}; 5350 5351 this.set( _.extend( {}, this._views, this.options.views ), { silent: true }); 5352 delete this.options.views; 5353 5354 if ( ! this.options.silent ) { 5355 this.render(); 5356 } 5357 }, 5358 /** 5359 * @param {string} id 5360 * @param {wp.media.View|Object} view 5361 * @param {Object} options 5362 * @returns {wp.media.view.PriorityList} Returns itself to allow chaining 5363 */ 5364 set: function( id, view, options ) { 5365 var priority, views, index; 5366 5367 options = options || {}; 5368 5369 // Accept an object with an `id` : `view` mapping. 5370 if ( _.isObject( id ) ) { 5371 _.each( id, function( view, id ) { 5372 this.set( id, view ); 5373 }, this ); 5374 return this; 5375 } 5376 5377 if ( ! (view instanceof Backbone.View) ) { 5378 view = this.toView( view, id, options ); 5379 } 5380 view.controller = view.controller || this.controller; 5381 5382 this.unset( id ); 5383 5384 priority = view.options.priority || 10; 5385 views = this.views.get() || []; 5386 5387 _.find( views, function( existing, i ) { 5388 if ( existing.options.priority > priority ) { 5389 index = i; 5390 return true; 5391 } 5392 }); 5393 5394 this._views[ id ] = view; 5395 this.views.add( view, { 5396 at: _.isNumber( index ) ? index : views.length || 0 5397 }); 5398 5399 return this; 5400 }, 5401 /** 5402 * @param {string} id 5403 * @returns {wp.media.View} 5404 */ 5405 get: function( id ) { 5406 return this._views[ id ]; 5407 }, 5408 /** 5409 * @param {string} id 5410 * @returns {wp.media.view.PriorityList} 5411 */ 5412 unset: function( id ) { 5413 var view = this.get( id ); 5414 5415 if ( view ) { 5416 view.remove(); 5417 } 5418 5419 delete this._views[ id ]; 5420 return this; 5421 }, 5422 /** 5423 * @param {Object} options 5424 * @returns {wp.media.View} 5425 */ 5426 toView: function( options ) { 5427 return new wp.media.View( options ); 5428 } 5429 }); 5430 5431 module.exports = PriorityList; 5432 5433 5434 /***/ }), 5435 /* 64 */ 5436 /***/ (function(module, exports) { 5437 5438 /** 5439 * wp.media.view.MenuItem 5440 * 5441 * @class 5442 * @augments wp.media.View 5443 * @augments wp.Backbone.View 5444 * @augments Backbone.View 5445 */ 5446 var $ = jQuery, 5447 MenuItem; 5448 5449 MenuItem = wp.media.View.extend({ 5450 tagName: 'a', 5451 className: 'media-menu-item', 5452 5453 attributes: { 5454 href: '#' 5455 }, 5456 5457 events: { 5458 'click': '_click' 5459 }, 5460 /** 5461 * @param {Object} event 5462 */ 5463 _click: function( event ) { 5464 var clickOverride = this.options.click; 5465 5466 if ( event ) { 5467 event.preventDefault(); 5468 } 5469 5470 if ( clickOverride ) { 5471 clickOverride.call( this ); 5472 } else { 5473 this.click(); 5474 } 5475 5476 // When selecting a tab along the left side, 5477 // focus should be transferred into the main panel 5478 if ( ! wp.media.isTouchDevice ) { 5479 $('.media-frame-content input').first().focus(); 5480 } 5481 }, 5482 5483 click: function() { 5484 var state = this.options.state; 5485 5486 if ( state ) { 5487 this.controller.setState( state ); 5488 this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below 5489 } 5490 }, 5491 /** 5492 * @returns {wp.media.view.MenuItem} returns itself to allow chaining 5493 */ 5494 render: function() { 5495 var options = this.options; 5496 5497 if ( options.text ) { 5498 this.$el.text( options.text ); 5499 } else if ( options.html ) { 5500 this.$el.html( options.html ); 5501 } 5502 5503 return this; 5504 } 5505 }); 5506 5507 module.exports = MenuItem; 5508 5509 5510 /***/ }), 5511 /* 65 */ 5512 /***/ (function(module, exports) { 5513 5514 /** 5515 * wp.media.view.Menu 5516 * 5517 * @class 5518 * @augments wp.media.view.PriorityList 5519 * @augments wp.media.View 5520 * @augments wp.Backbone.View 5521 * @augments Backbone.View 5522 */ 5523 var MenuItem = wp.media.view.MenuItem, 5524 PriorityList = wp.media.view.PriorityList, 5525 Menu; 5526 5527 Menu = PriorityList.extend({ 5528 tagName: 'div', 5529 className: 'media-menu', 5530 property: 'state', 5531 ItemView: MenuItem, 5532 region: 'menu', 5533 5534 /* TODO: alternatively hide on any click anywhere 5535 events: { 5536 'click': 'click' 5537 }, 5538 5539 click: function() { 5540 this.$el.removeClass( 'visible' ); 5541 }, 5542 */ 5543 5544 /** 5545 * @param {Object} options 5546 * @param {string} id 5547 * @returns {wp.media.View} 5548 */ 5549 toView: function( options, id ) { 5550 options = options || {}; 5551 options[ this.property ] = options[ this.property ] || id; 5552 return new this.ItemView( options ).render(); 5553 }, 5554 5555 ready: function() { 5556 /** 5557 * call 'ready' directly on the parent class 5558 */ 5559 PriorityList.prototype.ready.apply( this, arguments ); 5560 this.visibility(); 5561 }, 5562 5563 set: function() { 5564 /** 5565 * call 'set' directly on the parent class 5566 */ 5567 PriorityList.prototype.set.apply( this, arguments ); 5568 this.visibility(); 5569 }, 5570 5571 unset: function() { 5572 /** 5573 * call 'unset' directly on the parent class 5574 */ 5575 PriorityList.prototype.unset.apply( this, arguments ); 5576 this.visibility(); 5577 }, 5578 5579 visibility: function() { 5580 var region = this.region, 5581 view = this.controller[ region ].get(), 5582 views = this.views.get(), 5583 hide = ! views || views.length < 2; 5584 5585 if ( this === view ) { 5586 this.controller.$el.toggleClass( 'hide-' + region, hide ); 5587 } 5588 }, 5589 /** 5590 * @param {string} id 5591 */ 5592 select: function( id ) { 5593 var view = this.get( id ); 5594 5595 if ( ! view ) { 5596 return; 5597 } 5598 5599 this.deselect(); 5600 view.$el.addClass('active'); 5601 }, 5602 5603 deselect: function() { 5604 this.$el.children().removeClass('active'); 5605 }, 5606 5607 hide: function( id ) { 5608 var view = this.get( id ); 5609 5610 if ( ! view ) { 5611 return; 5612 } 5613 5614 view.$el.addClass('hidden'); 5615 }, 5616 5617 show: function( id ) { 5618 var view = this.get( id ); 5619 5620 if ( ! view ) { 5621 return; 5622 } 5623 5624 view.$el.removeClass('hidden'); 5625 } 5626 }); 5627 5628 module.exports = Menu; 5629 5630 5631 /***/ }), 5632 /* 66 */ 5633 /***/ (function(module, exports) { 5634 5635 /** 5636 * wp.media.view.RouterItem 5637 * 5638 * @class 5639 * @augments wp.media.view.MenuItem 5640 * @augments wp.media.View 5641 * @augments wp.Backbone.View 5642 * @augments Backbone.View 5643 */ 5644 var RouterItem = wp.media.view.MenuItem.extend({ 5645 /** 5646 * On click handler to activate the content region's corresponding mode. 5647 */ 5648 click: function() { 5649 var contentMode = this.options.contentMode; 5650 if ( contentMode ) { 5651 this.controller.content.mode( contentMode ); 5652 } 5653 } 5654 }); 5655 5656 module.exports = RouterItem; 5657 5658 5659 /***/ }), 5660 /* 67 */ 5661 /***/ (function(module, exports) { 5662 5663 /** 5664 * wp.media.view.Router 5665 * 5666 * @class 5667 * @augments wp.media.view.Menu 5668 * @augments wp.media.view.PriorityList 5669 * @augments wp.media.View 5670 * @augments wp.Backbone.View 5671 * @augments Backbone.View 5672 */ 5673 var Menu = wp.media.view.Menu, 5674 Router; 5675 5676 Router = Menu.extend({ 5677 tagName: 'div', 5678 className: 'media-router', 5679 property: 'contentMode', 5680 ItemView: wp.media.view.RouterItem, 5681 region: 'router', 5682 5683 initialize: function() { 5684 this.controller.on( 'content:render', this.update, this ); 5685 // Call 'initialize' directly on the parent class. 5686 Menu.prototype.initialize.apply( this, arguments ); 5687 }, 5688 5689 update: function() { 5690 var mode = this.controller.content.mode(); 5691 if ( mode ) { 5692 this.select( mode ); 5693 } 5694 } 5695 }); 5696 5697 module.exports = Router; 5698 5699 5700 /***/ }), 5701 /* 68 */ 5702 /***/ (function(module, exports) { 5703 5704 /** 5705 * wp.media.view.Sidebar 5706 * 5707 * @class 5708 * @augments wp.media.view.PriorityList 5709 * @augments wp.media.View 5710 * @augments wp.Backbone.View 5711 * @augments Backbone.View 5712 */ 5713 var Sidebar = wp.media.view.PriorityList.extend({ 5714 className: 'media-sidebar' 5715 }); 5716 5717 module.exports = Sidebar; 5718 5719 5720 /***/ }), 5721 /* 69 */ 5722 /***/ (function(module, exports) { 5723 5724 /** 5725 * wp.media.view.Attachment 5726 * 5727 * @class 5728 * @augments wp.media.View 5729 * @augments wp.Backbone.View 5730 * @augments Backbone.View 5731 */ 5732 var View = wp.media.View, 5733 $ = jQuery, 5734 Attachment; 5735 5736 Attachment = View.extend({ 5737 tagName: 'li', 5738 className: 'attachment', 5739 template: wp.template('attachment'), 5740 5741 attributes: function() { 5742 return { 5743 'tabIndex': 0, 5744 'role': 'checkbox', 5745 'aria-label': this.model.get( 'title' ), 5746 'aria-checked': false, 5747 'data-id': this.model.get( 'id' ) 5748 }; 5749 }, 5750 5751 events: { 5752 'click .js--select-attachment': 'toggleSelectionHandler', 5753 'change [data-setting]': 'updateSetting', 5754 'change [data-setting] input': 'updateSetting', 5755 'change [data-setting] select': 'updateSetting', 5756 'change [data-setting] textarea': 'updateSetting', 5757 'click .attachment-close': 'removeFromLibrary', 5758 'click .check': 'checkClickHandler', 5759 'keydown': 'toggleSelectionHandler' 5760 }, 5761 5762 buttons: {}, 5763 5764 initialize: function() { 5765 var selection = this.options.selection, 5766 options = _.defaults( this.options, { 5767 rerenderOnModelChange: true 5768 } ); 5769 5770 if ( options.rerenderOnModelChange ) { 5771 this.listenTo( this.model, 'change', this.render ); 5772 } else { 5773 this.listenTo( this.model, 'change:percent', this.progress ); 5774 } 5775 this.listenTo( this.model, 'change:title', this._syncTitle ); 5776 this.listenTo( this.model, 'change:caption', this._syncCaption ); 5777 this.listenTo( this.model, 'change:artist', this._syncArtist ); 5778 this.listenTo( this.model, 'change:album', this._syncAlbum ); 5779 5780 // Update the selection. 5781 this.listenTo( this.model, 'add', this.select ); 5782 this.listenTo( this.model, 'remove', this.deselect ); 5783 if ( selection ) { 5784 selection.on( 'reset', this.updateSelect, this ); 5785 // Update the model's details view. 5786 this.listenTo( this.model, 'selection:single selection:unsingle', this.details ); 5787 this.details( this.model, this.controller.state().get('selection') ); 5788 } 5789 5790 this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave ); 5791 }, 5792 /** 5793 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 5794 */ 5795 dispose: function() { 5796 var selection = this.options.selection; 5797 5798 // Make sure all settings are saved before removing the view. 5799 this.updateAll(); 5800 5801 if ( selection ) { 5802 selection.off( null, null, this ); 5803 } 5804 /** 5805 * call 'dispose' directly on the parent class 5806 */ 5807 View.prototype.dispose.apply( this, arguments ); 5808 return this; 5809 }, 5810 /** 5811 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 5812 */ 5813 render: function() { 5814 var options = _.defaults( this.model.toJSON(), { 5815 orientation: 'landscape', 5816 uploading: false, 5817 type: '', 5818 subtype: '', 5819 icon: '', 5820 filename: '', 5821 caption: '', 5822 title: '', 5823 dateFormatted: '', 5824 width: '', 5825 height: '', 5826 compat: false, 5827 alt: '', 5828 description: '' 5829 }, this.options ); 5830 5831 options.buttons = this.buttons; 5832 options.describe = this.controller.state().get('describe'); 5833 5834 if ( 'image' === options.type ) { 5835 options.size = this.imageSize(); 5836 } 5837 5838 options.can = {}; 5839 if ( options.nonces ) { 5840 options.can.remove = !! options.nonces['delete']; 5841 options.can.save = !! options.nonces.update; 5842 } 5843 5844 if ( this.controller.state().get('allowLocalEdits') ) { 5845 options.allowLocalEdits = true; 5846 } 5847 5848 if ( options.uploading && ! options.percent ) { 5849 options.percent = 0; 5850 } 5851 5852 this.views.detach(); 5853 this.$el.html( this.template( options ) ); 5854 5855 this.$el.toggleClass( 'uploading', options.uploading ); 5856 5857 if ( options.uploading ) { 5858 this.$bar = this.$('.media-progress-bar div'); 5859 } else { 5860 delete this.$bar; 5861 } 5862 5863 // Check if the model is selected. 5864 this.updateSelect(); 5865 5866 // Update the save status. 5867 this.updateSave(); 5868 5869 this.views.render(); 5870 5871 return this; 5872 }, 5873 5874 progress: function() { 5875 if ( this.$bar && this.$bar.length ) { 5876 this.$bar.width( this.model.get('percent') + '%' ); 5877 } 5878 }, 5879 5880 /** 5881 * @param {Object} event 5882 */ 5883 toggleSelectionHandler: function( event ) { 5884 var method; 5885 5886 // Don't do anything inside inputs and on the attachment check and remove buttons. 5887 if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) { 5888 return; 5889 } 5890 5891 // Catch arrow events 5892 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 5893 this.controller.trigger( 'attachment:keydown:arrow', event ); 5894 return; 5895 } 5896 5897 // Catch enter and space events 5898 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 5899 return; 5900 } 5901 5902 event.preventDefault(); 5903 5904 // In the grid view, bubble up an edit:attachment event to the controller. 5905 if ( this.controller.isModeActive( 'grid' ) ) { 5906 if ( this.controller.isModeActive( 'edit' ) ) { 5907 // Pass the current target to restore focus when closing 5908 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget ); 5909 return; 5910 } 5911 5912 if ( this.controller.isModeActive( 'select' ) ) { 5913 method = 'toggle'; 5914 } 5915 } 5916 5917 if ( event.shiftKey ) { 5918 method = 'between'; 5919 } else if ( event.ctrlKey || event.metaKey ) { 5920 method = 'toggle'; 5921 } 5922 5923 this.toggleSelection({ 5924 method: method 5925 }); 5926 5927 this.controller.trigger( 'selection:toggle' ); 5928 }, 5929 /** 5930 * @param {Object} options 5931 */ 5932 toggleSelection: function( options ) { 5933 var collection = this.collection, 5934 selection = this.options.selection, 5935 model = this.model, 5936 method = options && options.method, 5937 single, models, singleIndex, modelIndex; 5938 5939 if ( ! selection ) { 5940 return; 5941 } 5942 5943 single = selection.single(); 5944 method = _.isUndefined( method ) ? selection.multiple : method; 5945 5946 // If the `method` is set to `between`, select all models that 5947 // exist between the current and the selected model. 5948 if ( 'between' === method && single && selection.multiple ) { 5949 // If the models are the same, short-circuit. 5950 if ( single === model ) { 5951 return; 5952 } 5953 5954 singleIndex = collection.indexOf( single ); 5955 modelIndex = collection.indexOf( this.model ); 5956 5957 if ( singleIndex < modelIndex ) { 5958 models = collection.models.slice( singleIndex, modelIndex + 1 ); 5959 } else { 5960 models = collection.models.slice( modelIndex, singleIndex + 1 ); 5961 } 5962 5963 selection.add( models ); 5964 selection.single( model ); 5965 return; 5966 5967 // If the `method` is set to `toggle`, just flip the selection 5968 // status, regardless of whether the model is the single model. 5969 } else if ( 'toggle' === method ) { 5970 selection[ this.selected() ? 'remove' : 'add' ]( model ); 5971 selection.single( model ); 5972 return; 5973 } else if ( 'add' === method ) { 5974 selection.add( model ); 5975 selection.single( model ); 5976 return; 5977 } 5978 5979 // Fixes bug that loses focus when selecting a featured image 5980 if ( ! method ) { 5981 method = 'add'; 5982 } 5983 5984 if ( method !== 'add' ) { 5985 method = 'reset'; 5986 } 5987 5988 if ( this.selected() ) { 5989 // If the model is the single model, remove it. 5990 // If it is not the same as the single model, 5991 // it now becomes the single model. 5992 selection[ single === model ? 'remove' : 'single' ]( model ); 5993 } else { 5994 // If the model is not selected, run the `method` on the 5995 // selection. By default, we `reset` the selection, but the 5996 // `method` can be set to `add` the model to the selection. 5997 selection[ method ]( model ); 5998 selection.single( model ); 5999 } 6000 }, 6001 6002 updateSelect: function() { 6003 this[ this.selected() ? 'select' : 'deselect' ](); 6004 }, 6005 /** 6006 * @returns {unresolved|Boolean} 6007 */ 6008 selected: function() { 6009 var selection = this.options.selection; 6010 if ( selection ) { 6011 return !! selection.get( this.model.cid ); 6012 } 6013 }, 6014 /** 6015 * @param {Backbone.Model} model 6016 * @param {Backbone.Collection} collection 6017 */ 6018 select: function( model, collection ) { 6019 var selection = this.options.selection, 6020 controller = this.controller; 6021 6022 // Check if a selection exists and if it's the collection provided. 6023 // If they're not the same collection, bail; we're in another 6024 // selection's event loop. 6025 if ( ! selection || ( collection && collection !== selection ) ) { 6026 return; 6027 } 6028 6029 // Bail if the model is already selected. 6030 if ( this.$el.hasClass( 'selected' ) ) { 6031 return; 6032 } 6033 6034 // Add 'selected' class to model, set aria-checked to true. 6035 this.$el.addClass( 'selected' ).attr( 'aria-checked', true ); 6036 // Make the checkbox tabable, except in media grid (bulk select mode). 6037 if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) { 6038 this.$( '.check' ).attr( 'tabindex', '0' ); 6039 } 6040 }, 6041 /** 6042 * @param {Backbone.Model} model 6043 * @param {Backbone.Collection} collection 6044 */ 6045 deselect: function( model, collection ) { 6046 var selection = this.options.selection; 6047 6048 // Check if a selection exists and if it's the collection provided. 6049 // If they're not the same collection, bail; we're in another 6050 // selection's event loop. 6051 if ( ! selection || ( collection && collection !== selection ) ) { 6052 return; 6053 } 6054 this.$el.removeClass( 'selected' ).attr( 'aria-checked', false ) 6055 .find( '.check' ).attr( 'tabindex', '-1' ); 6056 }, 6057 /** 6058 * @param {Backbone.Model} model 6059 * @param {Backbone.Collection} collection 6060 */ 6061 details: function( model, collection ) { 6062 var selection = this.options.selection, 6063 details; 6064 6065 if ( selection !== collection ) { 6066 return; 6067 } 6068 6069 details = selection.single(); 6070 this.$el.toggleClass( 'details', details === this.model ); 6071 }, 6072 /** 6073 * @param {string} size 6074 * @returns {Object} 6075 */ 6076 imageSize: function( size ) { 6077 var sizes = this.model.get('sizes'), matched = false; 6078 6079 size = size || 'medium'; 6080 6081 // Use the provided image size if possible. 6082 if ( sizes ) { 6083 if ( sizes[ size ] ) { 6084 matched = sizes[ size ]; 6085 } else if ( sizes.large ) { 6086 matched = sizes.large; 6087 } else if ( sizes.thumbnail ) { 6088 matched = sizes.thumbnail; 6089 } else if ( sizes.full ) { 6090 matched = sizes.full; 6091 } 6092 6093 if ( matched ) { 6094 return _.clone( matched ); 6095 } 6096 } 6097 6098 return { 6099 url: this.model.get('url'), 6100 width: this.model.get('width'), 6101 height: this.model.get('height'), 6102 orientation: this.model.get('orientation') 6103 }; 6104 }, 6105 /** 6106 * @param {Object} event 6107 */ 6108 updateSetting: function( event ) { 6109 var $setting = $( event.target ).closest('[data-setting]'), 6110 setting, value; 6111 6112 if ( ! $setting.length ) { 6113 return; 6114 } 6115 6116 setting = $setting.data('setting'); 6117 value = event.target.value; 6118 6119 if ( this.model.get( setting ) !== value ) { 6120 this.save( setting, value ); 6121 } 6122 }, 6123 6124 /** 6125 * Pass all the arguments to the model's save method. 6126 * 6127 * Records the aggregate status of all save requests and updates the 6128 * view's classes accordingly. 6129 */ 6130 save: function() { 6131 var view = this, 6132 save = this._save = this._save || { status: 'ready' }, 6133 request = this.model.save.apply( this.model, arguments ), 6134 requests = save.requests ? $.when( request, save.requests ) : request; 6135 6136 // If we're waiting to remove 'Saved.', stop. 6137 if ( save.savedTimer ) { 6138 clearTimeout( save.savedTimer ); 6139 } 6140 6141 this.updateSave('waiting'); 6142 save.requests = requests; 6143 requests.always( function() { 6144 // If we've performed another request since this one, bail. 6145 if ( save.requests !== requests ) { 6146 return; 6147 } 6148 6149 view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' ); 6150 save.savedTimer = setTimeout( function() { 6151 view.updateSave('ready'); 6152 delete save.savedTimer; 6153 }, 2000 ); 6154 }); 6155 }, 6156 /** 6157 * @param {string} status 6158 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 6159 */ 6160 updateSave: function( status ) { 6161 var save = this._save = this._save || { status: 'ready' }; 6162 6163 if ( status && status !== save.status ) { 6164 this.$el.removeClass( 'save-' + save.status ); 6165 save.status = status; 6166 } 6167 6168 this.$el.addClass( 'save-' + save.status ); 6169 return this; 6170 }, 6171 6172 updateAll: function() { 6173 var $settings = this.$('[data-setting]'), 6174 model = this.model, 6175 changed; 6176 6177 changed = _.chain( $settings ).map( function( el ) { 6178 var $input = $('input, textarea, select, [value]', el ), 6179 setting, value; 6180 6181 if ( ! $input.length ) { 6182 return; 6183 } 6184 6185 setting = $(el).data('setting'); 6186 value = $input.val(); 6187 6188 // Record the value if it changed. 6189 if ( model.get( setting ) !== value ) { 6190 return [ setting, value ]; 6191 } 6192 }).compact().object().value(); 6193 6194 if ( ! _.isEmpty( changed ) ) { 6195 model.save( changed ); 6196 } 6197 }, 6198 /** 6199 * @param {Object} event 6200 */ 6201 removeFromLibrary: function( event ) { 6202 // Catch enter and space events 6203 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 6204 return; 6205 } 6206 6207 // Stop propagation so the model isn't selected. 6208 event.stopPropagation(); 6209 6210 this.collection.remove( this.model ); 6211 }, 6212 6213 /** 6214 * Add the model if it isn't in the selection, if it is in the selection, 6215 * remove it. 6216 * 6217 * @param {[type]} event [description] 6218 * @return {[type]} [description] 6219 */ 6220 checkClickHandler: function ( event ) { 6221 var selection = this.options.selection; 6222 if ( ! selection ) { 6223 return; 6224 } 6225 event.stopPropagation(); 6226 if ( selection.where( { id: this.model.get( 'id' ) } ).length ) { 6227 selection.remove( this.model ); 6228 // Move focus back to the attachment tile (from the check). 6229 this.$el.focus(); 6230 } else { 6231 selection.add( this.model ); 6232 } 6233 } 6234 }); 6235 6236 // Ensure settings remain in sync between attachment views. 6237 _.each({ 6238 caption: '_syncCaption', 6239 title: '_syncTitle', 6240 artist: '_syncArtist', 6241 album: '_syncAlbum' 6242 }, function( method, setting ) { 6243 /** 6244 * @param {Backbone.Model} model 6245 * @param {string} value 6246 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 6247 */ 6248 Attachment.prototype[ method ] = function( model, value ) { 6249 var $setting = this.$('[data-setting="' + setting + '"]'); 6250 6251 if ( ! $setting.length ) { 6252 return this; 6253 } 6254 6255 // If the updated value is in sync with the value in the DOM, there 6256 // is no need to re-render. If we're currently editing the value, 6257 // it will automatically be in sync, suppressing the re-render for 6258 // the view we're editing, while updating any others. 6259 if ( value === $setting.find('input, textarea, select, [value]').val() ) { 6260 return this; 6261 } 6262 6263 return this.render(); 6264 }; 6265 }); 6266 6267 module.exports = Attachment; 6268 6269 6270 /***/ }), 6271 /* 70 */ 6272 /***/ (function(module, exports) { 6273 6274 /** 6275 * wp.media.view.Attachment.Library 6276 * 6277 * @class 6278 * @augments wp.media.view.Attachment 6279 * @augments wp.media.View 6280 * @augments wp.Backbone.View 6281 * @augments Backbone.View 6282 */ 6283 var Library = wp.media.view.Attachment.extend({ 6284 buttons: { 6285 check: true 6286 } 6287 }); 6288 6289 module.exports = Library; 6290 6291 6292 /***/ }), 6293 /* 71 */ 6294 /***/ (function(module, exports) { 6295 6296 /** 6297 * wp.media.view.Attachment.EditLibrary 6298 * 6299 * @class 6300 * @augments wp.media.view.Attachment 6301 * @augments wp.media.View 6302 * @augments wp.Backbone.View 6303 * @augments Backbone.View 6304 */ 6305 var EditLibrary = wp.media.view.Attachment.extend({ 6306 buttons: { 6307 close: true 6308 } 6309 }); 6310 6311 module.exports = EditLibrary; 6312 6313 6314 /***/ }), 6315 /* 72 */ 6316 /***/ (function(module, exports) { 6317 6318 /** 6319 * wp.media.view.Attachments 6320 * 6321 * @class 6322 * @augments wp.media.View 6323 * @augments wp.Backbone.View 6324 * @augments Backbone.View 6325 */ 6326 var View = wp.media.View, 6327 $ = jQuery, 6328 Attachments; 6329 6330 Attachments = View.extend({ 6331 tagName: 'ul', 6332 className: 'attachments', 6333 6334 attributes: { 6335 tabIndex: -1 6336 }, 6337 6338 initialize: function() { 6339 this.el.id = _.uniqueId('__attachments-view-'); 6340 6341 _.defaults( this.options, { 6342 refreshSensitivity: wp.media.isTouchDevice ? 300 : 200, 6343 refreshThreshold: 3, 6344 AttachmentView: wp.media.view.Attachment, 6345 sortable: false, 6346 resize: true, 6347 idealColumnWidth: $( window ).width() < 640 ? 135 : 150 6348 }); 6349 6350 this._viewsByCid = {}; 6351 this.$window = $( window ); 6352 this.resizeEvent = 'resize.media-modal-columns'; 6353 6354 this.collection.on( 'add', function( attachment ) { 6355 this.views.add( this.createAttachmentView( attachment ), { 6356 at: this.collection.indexOf( attachment ) 6357 }); 6358 }, this ); 6359 6360 this.collection.on( 'remove', function( attachment ) { 6361 var view = this._viewsByCid[ attachment.cid ]; 6362 delete this._viewsByCid[ attachment.cid ]; 6363 6364 if ( view ) { 6365 view.remove(); 6366 } 6367 }, this ); 6368 6369 this.collection.on( 'reset', this.render, this ); 6370 6371 this.listenTo( this.controller, 'library:selection:add', this.attachmentFocus ); 6372 6373 // Throttle the scroll handler and bind this. 6374 this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value(); 6375 6376 this.options.scrollElement = this.options.scrollElement || this.el; 6377 $( this.options.scrollElement ).on( 'scroll', this.scroll ); 6378 6379 this.initSortable(); 6380 6381 _.bindAll( this, 'setColumns' ); 6382 6383 if ( this.options.resize ) { 6384 this.on( 'ready', this.bindEvents ); 6385 this.controller.on( 'open', this.setColumns ); 6386 6387 // Call this.setColumns() after this view has been rendered in the DOM so 6388 // attachments get proper width applied. 6389 _.defer( this.setColumns, this ); 6390 } 6391 }, 6392 6393 bindEvents: function() { 6394 this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) ); 6395 }, 6396 6397 attachmentFocus: function() { 6398 this.$( 'li:first' ).focus(); 6399 }, 6400 6401 restoreFocus: function() { 6402 this.$( 'li.selected:first' ).focus(); 6403 }, 6404 6405 arrowEvent: function( event ) { 6406 var attachments = this.$el.children( 'li' ), 6407 perRow = this.columns, 6408 index = attachments.filter( ':focus' ).index(), 6409 row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow ); 6410 6411 if ( index === -1 ) { 6412 return; 6413 } 6414 6415 // Left arrow 6416 if ( 37 === event.keyCode ) { 6417 if ( 0 === index ) { 6418 return; 6419 } 6420 attachments.eq( index - 1 ).focus(); 6421 } 6422 6423 // Up arrow 6424 if ( 38 === event.keyCode ) { 6425 if ( 1 === row ) { 6426 return; 6427 } 6428 attachments.eq( index - perRow ).focus(); 6429 } 6430 6431 // Right arrow 6432 if ( 39 === event.keyCode ) { 6433 if ( attachments.length === index ) { 6434 return; 6435 } 6436 attachments.eq( index + 1 ).focus(); 6437 } 6438 6439 // Down arrow 6440 if ( 40 === event.keyCode ) { 6441 if ( Math.ceil( attachments.length / perRow ) === row ) { 6442 return; 6443 } 6444 attachments.eq( index + perRow ).focus(); 6445 } 6446 }, 6447 6448 dispose: function() { 6449 this.collection.props.off( null, null, this ); 6450 if ( this.options.resize ) { 6451 this.$window.off( this.resizeEvent ); 6452 } 6453 6454 /** 6455 * call 'dispose' directly on the parent class 6456 */ 6457 View.prototype.dispose.apply( this, arguments ); 6458 }, 6459 6460 setColumns: function() { 6461 var prev = this.columns, 6462 width = this.$el.width(); 6463 6464 if ( width ) { 6465 this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1; 6466 6467 if ( ! prev || prev !== this.columns ) { 6468 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns ); 6469 } 6470 } 6471 }, 6472 6473 initSortable: function() { 6474 var collection = this.collection; 6475 6476 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) { 6477 return; 6478 } 6479 6480 this.$el.sortable( _.extend({ 6481 // If the `collection` has a `comparator`, disable sorting. 6482 disabled: !! collection.comparator, 6483 6484 // Change the position of the attachment as soon as the 6485 // mouse pointer overlaps a thumbnail. 6486 tolerance: 'pointer', 6487 6488 // Record the initial `index` of the dragged model. 6489 start: function( event, ui ) { 6490 ui.item.data('sortableIndexStart', ui.item.index()); 6491 }, 6492 6493 // Update the model's index in the collection. 6494 // Do so silently, as the view is already accurate. 6495 update: function( event, ui ) { 6496 var model = collection.at( ui.item.data('sortableIndexStart') ), 6497 comparator = collection.comparator; 6498 6499 // Temporarily disable the comparator to prevent `add` 6500 // from re-sorting. 6501 delete collection.comparator; 6502 6503 // Silently shift the model to its new index. 6504 collection.remove( model, { 6505 silent: true 6506 }); 6507 collection.add( model, { 6508 silent: true, 6509 at: ui.item.index() 6510 }); 6511 6512 // Restore the comparator. 6513 collection.comparator = comparator; 6514 6515 // Fire the `reset` event to ensure other collections sync. 6516 collection.trigger( 'reset', collection ); 6517 6518 // If the collection is sorted by menu order, 6519 // update the menu order. 6520 collection.saveMenuOrder(); 6521 } 6522 }, this.options.sortable ) ); 6523 6524 // If the `orderby` property is changed on the `collection`, 6525 // check to see if we have a `comparator`. If so, disable sorting. 6526 collection.props.on( 'change:orderby', function() { 6527 this.$el.sortable( 'option', 'disabled', !! collection.comparator ); 6528 }, this ); 6529 6530 this.collection.props.on( 'change:orderby', this.refreshSortable, this ); 6531 this.refreshSortable(); 6532 }, 6533 6534 refreshSortable: function() { 6535 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) { 6536 return; 6537 } 6538 6539 // If the `collection` has a `comparator`, disable sorting. 6540 var collection = this.collection, 6541 orderby = collection.props.get('orderby'), 6542 enabled = 'menuOrder' === orderby || ! collection.comparator; 6543 6544 this.$el.sortable( 'option', 'disabled', ! enabled ); 6545 }, 6546 6547 /** 6548 * @param {wp.media.model.Attachment} attachment 6549 * @returns {wp.media.View} 6550 */ 6551 createAttachmentView: function( attachment ) { 6552 var view = new this.options.AttachmentView({ 6553 controller: this.controller, 6554 model: attachment, 6555 collection: this.collection, 6556 selection: this.options.selection 6557 }); 6558 6559 return this._viewsByCid[ attachment.cid ] = view; 6560 }, 6561 6562 prepare: function() { 6563 // Create all of the Attachment views, and replace 6564 // the list in a single DOM operation. 6565 if ( this.collection.length ) { 6566 this.views.set( this.collection.map( this.createAttachmentView, this ) ); 6567 6568 // If there are no elements, clear the views and load some. 6569 } else { 6570 this.views.unset(); 6571 this.collection.more().done( this.scroll ); 6572 } 6573 }, 6574 6575 ready: function() { 6576 // Trigger the scroll event to check if we're within the 6577 // threshold to query for additional attachments. 6578 this.scroll(); 6579 }, 6580 6581 scroll: function() { 6582 var view = this, 6583 el = this.options.scrollElement, 6584 scrollTop = el.scrollTop, 6585 toolbar; 6586 6587 // The scroll event occurs on the document, but the element 6588 // that should be checked is the document body. 6589 if ( el === document ) { 6590 el = document.body; 6591 scrollTop = $(document).scrollTop(); 6592 } 6593 6594 if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) { 6595 return; 6596 } 6597 6598 toolbar = this.views.parent.toolbar; 6599 6600 // Show the spinner only if we are close to the bottom. 6601 if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) { 6602 toolbar.get('spinner').show(); 6603 } 6604 6605 if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) { 6606 this.collection.more().done(function() { 6607 view.scroll(); 6608 toolbar.get('spinner').hide(); 6609 }); 6610 } 6611 } 6612 }); 6613 6614 module.exports = Attachments; 6615 6616 6617 /***/ }), 6618 /* 73 */ 6619 /***/ (function(module, exports) { 6620 6621 /** 6622 * wp.media.view.Search 6623 * 6624 * @class 6625 * @augments wp.media.View 6626 * @augments wp.Backbone.View 6627 * @augments Backbone.View 6628 */ 6629 var l10n = wp.media.view.l10n, 6630 Search; 6631 6632 Search = wp.media.View.extend({ 6633 tagName: 'input', 6634 className: 'search', 6635 id: 'media-search-input', 6636 6637 attributes: { 6638 type: 'search', 6639 placeholder: l10n.search 6640 }, 6641 6642 events: { 6643 'input': 'search', 6644 'keyup': 'search', 6645 'change': 'search', 6646 'search': 'search' 6647 }, 6648 6649 /** 6650 * @returns {wp.media.view.Search} Returns itself to allow chaining 6651 */ 6652 render: function() { 6653 this.el.value = this.model.escape('search'); 6654 return this; 6655 }, 6656 6657 search: function( event ) { 6658 if ( event.target.value ) { 6659 this.model.set( 'search', event.target.value ); 6660 } else { 6661 this.model.unset('search'); 6662 } 6663 } 6664 }); 6665 6666 module.exports = Search; 6667 6668 6669 /***/ }), 6670 /* 74 */ 6671 /***/ (function(module, exports) { 6672 2366 module.exports = AttachmentCompat; 2367 2368 },{}],21:[function(require,module,exports){ 6673 2369 /** 6674 2370 * wp.media.view.AttachmentFilters … … 6747 2443 module.exports = AttachmentFilters; 6748 2444 6749 6750 /***/ }), 6751 /* 75 */ 6752 /***/ (function(module, exports) { 6753 2445 },{}],22:[function(require,module,exports){ 2446 /** 2447 * wp.media.view.AttachmentFilters.All 2448 * 2449 * @class 2450 * @augments wp.media.view.AttachmentFilters 2451 * @augments wp.media.View 2452 * @augments wp.Backbone.View 2453 * @augments Backbone.View 2454 */ 2455 var l10n = wp.media.view.l10n, 2456 All; 2457 2458 All = wp.media.view.AttachmentFilters.extend({ 2459 createFilters: function() { 2460 var filters = {}; 2461 2462 _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) { 2463 filters[ key ] = { 2464 text: text, 2465 props: { 2466 status: null, 2467 type: key, 2468 uploadedTo: null, 2469 orderby: 'date', 2470 order: 'DESC' 2471 } 2472 }; 2473 }); 2474 2475 filters.all = { 2476 text: l10n.allMediaItems, 2477 props: { 2478 status: null, 2479 type: null, 2480 uploadedTo: null, 2481 orderby: 'date', 2482 order: 'DESC' 2483 }, 2484 priority: 10 2485 }; 2486 2487 if ( wp.media.view.settings.post.id ) { 2488 filters.uploaded = { 2489 text: l10n.uploadedToThisPost, 2490 props: { 2491 status: null, 2492 type: null, 2493 uploadedTo: wp.media.view.settings.post.id, 2494 orderby: 'menuOrder', 2495 order: 'ASC' 2496 }, 2497 priority: 20 2498 }; 2499 } 2500 2501 filters.unattached = { 2502 text: l10n.unattached, 2503 props: { 2504 status: null, 2505 uploadedTo: 0, 2506 type: null, 2507 orderby: 'menuOrder', 2508 order: 'ASC' 2509 }, 2510 priority: 50 2511 }; 2512 2513 if ( wp.media.view.settings.mediaTrash && 2514 this.controller.isModeActive( 'grid' ) ) { 2515 2516 filters.trash = { 2517 text: l10n.trash, 2518 props: { 2519 uploadedTo: null, 2520 status: 'trash', 2521 type: null, 2522 orderby: 'date', 2523 order: 'DESC' 2524 }, 2525 priority: 50 2526 }; 2527 } 2528 2529 this.filters = filters; 2530 } 2531 }); 2532 2533 module.exports = All; 2534 2535 },{}],23:[function(require,module,exports){ 6754 2536 /** 6755 2537 * A filter dropdown for month/dates. … … 6792 2574 module.exports = DateFilter; 6793 2575 6794 6795 /***/ }), 6796 /* 76 */ 6797 /***/ (function(module, exports) { 6798 2576 },{}],24:[function(require,module,exports){ 6799 2577 /** 6800 2578 * wp.media.view.AttachmentFilters.Uploaded … … 6855 2633 module.exports = Uploaded; 6856 2634 6857 6858 /***/ }), 6859 /* 77 */ 6860 /***/ (function(module, exports) { 6861 2635 },{}],25:[function(require,module,exports){ 6862 2636 /** 6863 * wp.media.view.Attachment Filters.All2637 * wp.media.view.Attachment 6864 2638 * 6865 2639 * @class 6866 * @augments wp.media.view.AttachmentFilters6867 2640 * @augments wp.media.View 6868 2641 * @augments wp.Backbone.View 6869 2642 * @augments Backbone.View 6870 2643 */ 6871 var l10n = wp.media.view.l10n, 6872 All; 6873 6874 All = wp.media.view.AttachmentFilters.extend({ 6875 createFilters: function() { 6876 var filters = {}; 6877 6878 _.each( wp.media.view.settings.mimeTypes || {}, function( text, key ) { 6879 filters[ key ] = { 6880 text: text, 6881 props: { 6882 status: null, 6883 type: key, 6884 uploadedTo: null, 6885 orderby: 'date', 6886 order: 'DESC' 6887 } 6888 }; 2644 var View = wp.media.View, 2645 $ = jQuery, 2646 Attachment; 2647 2648 Attachment = View.extend({ 2649 tagName: 'li', 2650 className: 'attachment', 2651 template: wp.template('attachment'), 2652 2653 attributes: function() { 2654 return { 2655 'tabIndex': 0, 2656 'role': 'checkbox', 2657 'aria-label': this.model.get( 'title' ), 2658 'aria-checked': false, 2659 'data-id': this.model.get( 'id' ) 2660 }; 2661 }, 2662 2663 events: { 2664 'click .js--select-attachment': 'toggleSelectionHandler', 2665 'change [data-setting]': 'updateSetting', 2666 'change [data-setting] input': 'updateSetting', 2667 'change [data-setting] select': 'updateSetting', 2668 'change [data-setting] textarea': 'updateSetting', 2669 'click .attachment-close': 'removeFromLibrary', 2670 'click .check': 'checkClickHandler', 2671 'keydown': 'toggleSelectionHandler' 2672 }, 2673 2674 buttons: {}, 2675 2676 initialize: function() { 2677 var selection = this.options.selection, 2678 options = _.defaults( this.options, { 2679 rerenderOnModelChange: true 2680 } ); 2681 2682 if ( options.rerenderOnModelChange ) { 2683 this.listenTo( this.model, 'change', this.render ); 2684 } else { 2685 this.listenTo( this.model, 'change:percent', this.progress ); 2686 } 2687 this.listenTo( this.model, 'change:title', this._syncTitle ); 2688 this.listenTo( this.model, 'change:caption', this._syncCaption ); 2689 this.listenTo( this.model, 'change:artist', this._syncArtist ); 2690 this.listenTo( this.model, 'change:album', this._syncAlbum ); 2691 2692 // Update the selection. 2693 this.listenTo( this.model, 'add', this.select ); 2694 this.listenTo( this.model, 'remove', this.deselect ); 2695 if ( selection ) { 2696 selection.on( 'reset', this.updateSelect, this ); 2697 // Update the model's details view. 2698 this.listenTo( this.model, 'selection:single selection:unsingle', this.details ); 2699 this.details( this.model, this.controller.state().get('selection') ); 2700 } 2701 2702 this.listenTo( this.controller, 'attachment:compat:waiting attachment:compat:ready', this.updateSave ); 2703 }, 2704 /** 2705 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 2706 */ 2707 dispose: function() { 2708 var selection = this.options.selection; 2709 2710 // Make sure all settings are saved before removing the view. 2711 this.updateAll(); 2712 2713 if ( selection ) { 2714 selection.off( null, null, this ); 2715 } 2716 /** 2717 * call 'dispose' directly on the parent class 2718 */ 2719 View.prototype.dispose.apply( this, arguments ); 2720 return this; 2721 }, 2722 /** 2723 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 2724 */ 2725 render: function() { 2726 var options = _.defaults( this.model.toJSON(), { 2727 orientation: 'landscape', 2728 uploading: false, 2729 type: '', 2730 subtype: '', 2731 icon: '', 2732 filename: '', 2733 caption: '', 2734 title: '', 2735 dateFormatted: '', 2736 width: '', 2737 height: '', 2738 compat: false, 2739 alt: '', 2740 description: '' 2741 }, this.options ); 2742 2743 options.buttons = this.buttons; 2744 options.describe = this.controller.state().get('describe'); 2745 2746 if ( 'image' === options.type ) { 2747 options.size = this.imageSize(); 2748 } 2749 2750 options.can = {}; 2751 if ( options.nonces ) { 2752 options.can.remove = !! options.nonces['delete']; 2753 options.can.save = !! options.nonces.update; 2754 } 2755 2756 if ( this.controller.state().get('allowLocalEdits') ) { 2757 options.allowLocalEdits = true; 2758 } 2759 2760 if ( options.uploading && ! options.percent ) { 2761 options.percent = 0; 2762 } 2763 2764 this.views.detach(); 2765 this.$el.html( this.template( options ) ); 2766 2767 this.$el.toggleClass( 'uploading', options.uploading ); 2768 2769 if ( options.uploading ) { 2770 this.$bar = this.$('.media-progress-bar div'); 2771 } else { 2772 delete this.$bar; 2773 } 2774 2775 // Check if the model is selected. 2776 this.updateSelect(); 2777 2778 // Update the save status. 2779 this.updateSave(); 2780 2781 this.views.render(); 2782 2783 return this; 2784 }, 2785 2786 progress: function() { 2787 if ( this.$bar && this.$bar.length ) { 2788 this.$bar.width( this.model.get('percent') + '%' ); 2789 } 2790 }, 2791 2792 /** 2793 * @param {Object} event 2794 */ 2795 toggleSelectionHandler: function( event ) { 2796 var method; 2797 2798 // Don't do anything inside inputs and on the attachment check and remove buttons. 2799 if ( 'INPUT' === event.target.nodeName || 'BUTTON' === event.target.nodeName ) { 2800 return; 2801 } 2802 2803 // Catch arrow events 2804 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 2805 this.controller.trigger( 'attachment:keydown:arrow', event ); 2806 return; 2807 } 2808 2809 // Catch enter and space events 2810 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 2811 return; 2812 } 2813 2814 event.preventDefault(); 2815 2816 // In the grid view, bubble up an edit:attachment event to the controller. 2817 if ( this.controller.isModeActive( 'grid' ) ) { 2818 if ( this.controller.isModeActive( 'edit' ) ) { 2819 // Pass the current target to restore focus when closing 2820 this.controller.trigger( 'edit:attachment', this.model, event.currentTarget ); 2821 return; 2822 } 2823 2824 if ( this.controller.isModeActive( 'select' ) ) { 2825 method = 'toggle'; 2826 } 2827 } 2828 2829 if ( event.shiftKey ) { 2830 method = 'between'; 2831 } else if ( event.ctrlKey || event.metaKey ) { 2832 method = 'toggle'; 2833 } 2834 2835 this.toggleSelection({ 2836 method: method 6889 2837 }); 6890 2838 6891 filters.all = { 6892 text: l10n.allMediaItems, 6893 props: { 6894 status: null, 6895 type: null, 6896 uploadedTo: null, 6897 orderby: 'date', 6898 order: 'DESC' 6899 }, 6900 priority: 10 2839 this.controller.trigger( 'selection:toggle' ); 2840 }, 2841 /** 2842 * @param {Object} options 2843 */ 2844 toggleSelection: function( options ) { 2845 var collection = this.collection, 2846 selection = this.options.selection, 2847 model = this.model, 2848 method = options && options.method, 2849 single, models, singleIndex, modelIndex; 2850 2851 if ( ! selection ) { 2852 return; 2853 } 2854 2855 single = selection.single(); 2856 method = _.isUndefined( method ) ? selection.multiple : method; 2857 2858 // If the `method` is set to `between`, select all models that 2859 // exist between the current and the selected model. 2860 if ( 'between' === method && single && selection.multiple ) { 2861 // If the models are the same, short-circuit. 2862 if ( single === model ) { 2863 return; 2864 } 2865 2866 singleIndex = collection.indexOf( single ); 2867 modelIndex = collection.indexOf( this.model ); 2868 2869 if ( singleIndex < modelIndex ) { 2870 models = collection.models.slice( singleIndex, modelIndex + 1 ); 2871 } else { 2872 models = collection.models.slice( modelIndex, singleIndex + 1 ); 2873 } 2874 2875 selection.add( models ); 2876 selection.single( model ); 2877 return; 2878 2879 // If the `method` is set to `toggle`, just flip the selection 2880 // status, regardless of whether the model is the single model. 2881 } else if ( 'toggle' === method ) { 2882 selection[ this.selected() ? 'remove' : 'add' ]( model ); 2883 selection.single( model ); 2884 return; 2885 } else if ( 'add' === method ) { 2886 selection.add( model ); 2887 selection.single( model ); 2888 return; 2889 } 2890 2891 // Fixes bug that loses focus when selecting a featured image 2892 if ( ! method ) { 2893 method = 'add'; 2894 } 2895 2896 if ( method !== 'add' ) { 2897 method = 'reset'; 2898 } 2899 2900 if ( this.selected() ) { 2901 // If the model is the single model, remove it. 2902 // If it is not the same as the single model, 2903 // it now becomes the single model. 2904 selection[ single === model ? 'remove' : 'single' ]( model ); 2905 } else { 2906 // If the model is not selected, run the `method` on the 2907 // selection. By default, we `reset` the selection, but the 2908 // `method` can be set to `add` the model to the selection. 2909 selection[ method ]( model ); 2910 selection.single( model ); 2911 } 2912 }, 2913 2914 updateSelect: function() { 2915 this[ this.selected() ? 'select' : 'deselect' ](); 2916 }, 2917 /** 2918 * @returns {unresolved|Boolean} 2919 */ 2920 selected: function() { 2921 var selection = this.options.selection; 2922 if ( selection ) { 2923 return !! selection.get( this.model.cid ); 2924 } 2925 }, 2926 /** 2927 * @param {Backbone.Model} model 2928 * @param {Backbone.Collection} collection 2929 */ 2930 select: function( model, collection ) { 2931 var selection = this.options.selection, 2932 controller = this.controller; 2933 2934 // Check if a selection exists and if it's the collection provided. 2935 // If they're not the same collection, bail; we're in another 2936 // selection's event loop. 2937 if ( ! selection || ( collection && collection !== selection ) ) { 2938 return; 2939 } 2940 2941 // Bail if the model is already selected. 2942 if ( this.$el.hasClass( 'selected' ) ) { 2943 return; 2944 } 2945 2946 // Add 'selected' class to model, set aria-checked to true. 2947 this.$el.addClass( 'selected' ).attr( 'aria-checked', true ); 2948 // Make the checkbox tabable, except in media grid (bulk select mode). 2949 if ( ! ( controller.isModeActive( 'grid' ) && controller.isModeActive( 'select' ) ) ) { 2950 this.$( '.check' ).attr( 'tabindex', '0' ); 2951 } 2952 }, 2953 /** 2954 * @param {Backbone.Model} model 2955 * @param {Backbone.Collection} collection 2956 */ 2957 deselect: function( model, collection ) { 2958 var selection = this.options.selection; 2959 2960 // Check if a selection exists and if it's the collection provided. 2961 // If they're not the same collection, bail; we're in another 2962 // selection's event loop. 2963 if ( ! selection || ( collection && collection !== selection ) ) { 2964 return; 2965 } 2966 this.$el.removeClass( 'selected' ).attr( 'aria-checked', false ) 2967 .find( '.check' ).attr( 'tabindex', '-1' ); 2968 }, 2969 /** 2970 * @param {Backbone.Model} model 2971 * @param {Backbone.Collection} collection 2972 */ 2973 details: function( model, collection ) { 2974 var selection = this.options.selection, 2975 details; 2976 2977 if ( selection !== collection ) { 2978 return; 2979 } 2980 2981 details = selection.single(); 2982 this.$el.toggleClass( 'details', details === this.model ); 2983 }, 2984 /** 2985 * @param {string} size 2986 * @returns {Object} 2987 */ 2988 imageSize: function( size ) { 2989 var sizes = this.model.get('sizes'), matched = false; 2990 2991 size = size || 'medium'; 2992 2993 // Use the provided image size if possible. 2994 if ( sizes ) { 2995 if ( sizes[ size ] ) { 2996 matched = sizes[ size ]; 2997 } else if ( sizes.large ) { 2998 matched = sizes.large; 2999 } else if ( sizes.thumbnail ) { 3000 matched = sizes.thumbnail; 3001 } else if ( sizes.full ) { 3002 matched = sizes.full; 3003 } 3004 3005 if ( matched ) { 3006 return _.clone( matched ); 3007 } 3008 } 3009 3010 return { 3011 url: this.model.get('url'), 3012 width: this.model.get('width'), 3013 height: this.model.get('height'), 3014 orientation: this.model.get('orientation') 6901 3015 }; 6902 6903 if ( wp.media.view.settings.post.id ) { 6904 filters.uploaded = { 6905 text: l10n.uploadedToThisPost, 6906 props: { 6907 status: null, 6908 type: null, 6909 uploadedTo: wp.media.view.settings.post.id, 6910 orderby: 'menuOrder', 6911 order: 'ASC' 6912 }, 6913 priority: 20 6914 }; 6915 } 6916 6917 filters.unattached = { 6918 text: l10n.unattached, 6919 props: { 6920 status: null, 6921 uploadedTo: 0, 6922 type: null, 6923 orderby: 'menuOrder', 6924 order: 'ASC' 6925 }, 6926 priority: 50 6927 }; 6928 6929 if ( wp.media.view.settings.mediaTrash && 6930 this.controller.isModeActive( 'grid' ) ) { 6931 6932 filters.trash = { 6933 text: l10n.trash, 6934 props: { 6935 uploadedTo: null, 6936 status: 'trash', 6937 type: null, 6938 orderby: 'date', 6939 order: 'DESC' 6940 }, 6941 priority: 50 6942 }; 6943 } 6944 6945 this.filters = filters; 3016 }, 3017 /** 3018 * @param {Object} event 3019 */ 3020 updateSetting: function( event ) { 3021 var $setting = $( event.target ).closest('[data-setting]'), 3022 setting, value; 3023 3024 if ( ! $setting.length ) { 3025 return; 3026 } 3027 3028 setting = $setting.data('setting'); 3029 value = event.target.value; 3030 3031 if ( this.model.get( setting ) !== value ) { 3032 this.save( setting, value ); 3033 } 3034 }, 3035 3036 /** 3037 * Pass all the arguments to the model's save method. 3038 * 3039 * Records the aggregate status of all save requests and updates the 3040 * view's classes accordingly. 3041 */ 3042 save: function() { 3043 var view = this, 3044 save = this._save = this._save || { status: 'ready' }, 3045 request = this.model.save.apply( this.model, arguments ), 3046 requests = save.requests ? $.when( request, save.requests ) : request; 3047 3048 // If we're waiting to remove 'Saved.', stop. 3049 if ( save.savedTimer ) { 3050 clearTimeout( save.savedTimer ); 3051 } 3052 3053 this.updateSave('waiting'); 3054 save.requests = requests; 3055 requests.always( function() { 3056 // If we've performed another request since this one, bail. 3057 if ( save.requests !== requests ) { 3058 return; 3059 } 3060 3061 view.updateSave( requests.state() === 'resolved' ? 'complete' : 'error' ); 3062 save.savedTimer = setTimeout( function() { 3063 view.updateSave('ready'); 3064 delete save.savedTimer; 3065 }, 2000 ); 3066 }); 3067 }, 3068 /** 3069 * @param {string} status 3070 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 3071 */ 3072 updateSave: function( status ) { 3073 var save = this._save = this._save || { status: 'ready' }; 3074 3075 if ( status && status !== save.status ) { 3076 this.$el.removeClass( 'save-' + save.status ); 3077 save.status = status; 3078 } 3079 3080 this.$el.addClass( 'save-' + save.status ); 3081 return this; 3082 }, 3083 3084 updateAll: function() { 3085 var $settings = this.$('[data-setting]'), 3086 model = this.model, 3087 changed; 3088 3089 changed = _.chain( $settings ).map( function( el ) { 3090 var $input = $('input, textarea, select, [value]', el ), 3091 setting, value; 3092 3093 if ( ! $input.length ) { 3094 return; 3095 } 3096 3097 setting = $(el).data('setting'); 3098 value = $input.val(); 3099 3100 // Record the value if it changed. 3101 if ( model.get( setting ) !== value ) { 3102 return [ setting, value ]; 3103 } 3104 }).compact().object().value(); 3105 3106 if ( ! _.isEmpty( changed ) ) { 3107 model.save( changed ); 3108 } 3109 }, 3110 /** 3111 * @param {Object} event 3112 */ 3113 removeFromLibrary: function( event ) { 3114 // Catch enter and space events 3115 if ( 'keydown' === event.type && 13 !== event.keyCode && 32 !== event.keyCode ) { 3116 return; 3117 } 3118 3119 // Stop propagation so the model isn't selected. 3120 event.stopPropagation(); 3121 3122 this.collection.remove( this.model ); 3123 }, 3124 3125 /** 3126 * Add the model if it isn't in the selection, if it is in the selection, 3127 * remove it. 3128 * 3129 * @param {[type]} event [description] 3130 * @return {[type]} [description] 3131 */ 3132 checkClickHandler: function ( event ) { 3133 var selection = this.options.selection; 3134 if ( ! selection ) { 3135 return; 3136 } 3137 event.stopPropagation(); 3138 if ( selection.where( { id: this.model.get( 'id' ) } ).length ) { 3139 selection.remove( this.model ); 3140 // Move focus back to the attachment tile (from the check). 3141 this.$el.focus(); 3142 } else { 3143 selection.add( this.model ); 3144 } 6946 3145 } 6947 3146 }); 6948 3147 6949 module.exports = All; 6950 6951 6952 /***/ }), 6953 /* 78 */ 6954 /***/ (function(module, exports) { 6955 3148 // Ensure settings remain in sync between attachment views. 3149 _.each({ 3150 caption: '_syncCaption', 3151 title: '_syncTitle', 3152 artist: '_syncArtist', 3153 album: '_syncAlbum' 3154 }, function( method, setting ) { 3155 /** 3156 * @param {Backbone.Model} model 3157 * @param {string} value 3158 * @returns {wp.media.view.Attachment} Returns itself to allow chaining 3159 */ 3160 Attachment.prototype[ method ] = function( model, value ) { 3161 var $setting = this.$('[data-setting="' + setting + '"]'); 3162 3163 if ( ! $setting.length ) { 3164 return this; 3165 } 3166 3167 // If the updated value is in sync with the value in the DOM, there 3168 // is no need to re-render. If we're currently editing the value, 3169 // it will automatically be in sync, suppressing the re-render for 3170 // the view we're editing, while updating any others. 3171 if ( value === $setting.find('input, textarea, select, [value]').val() ) { 3172 return this; 3173 } 3174 3175 return this.render(); 3176 }; 3177 }); 3178 3179 module.exports = Attachment; 3180 3181 },{}],26:[function(require,module,exports){ 3182 /** 3183 * wp.media.view.Attachment.Details 3184 * 3185 * @class 3186 * @augments wp.media.view.Attachment 3187 * @augments wp.media.View 3188 * @augments wp.Backbone.View 3189 * @augments Backbone.View 3190 */ 3191 var Attachment = wp.media.view.Attachment, 3192 l10n = wp.media.view.l10n, 3193 Details; 3194 3195 Details = Attachment.extend({ 3196 tagName: 'div', 3197 className: 'attachment-details', 3198 template: wp.template('attachment-details'), 3199 3200 attributes: function() { 3201 return { 3202 'tabIndex': 0, 3203 'data-id': this.model.get( 'id' ) 3204 }; 3205 }, 3206 3207 events: { 3208 'change [data-setting]': 'updateSetting', 3209 'change [data-setting] input': 'updateSetting', 3210 'change [data-setting] select': 'updateSetting', 3211 'change [data-setting] textarea': 'updateSetting', 3212 'click .delete-attachment': 'deleteAttachment', 3213 'click .trash-attachment': 'trashAttachment', 3214 'click .untrash-attachment': 'untrashAttachment', 3215 'click .edit-attachment': 'editAttachment', 3216 'keydown': 'toggleSelectionHandler' 3217 }, 3218 3219 initialize: function() { 3220 this.options = _.defaults( this.options, { 3221 rerenderOnModelChange: false 3222 }); 3223 3224 this.on( 'ready', this.initialFocus ); 3225 // Call 'initialize' directly on the parent class. 3226 Attachment.prototype.initialize.apply( this, arguments ); 3227 }, 3228 3229 initialFocus: function() { 3230 if ( ! wp.media.isTouchDevice ) { 3231 /* 3232 Previously focused the first ':input' (the readonly URL text field). 3233 Since the first ':input' is now a button (delete/trash): when pressing 3234 spacebar on an attachment, Firefox fires deleteAttachment/trashAttachment 3235 as soon as focus is moved. Explicitly target the first text field for now. 3236 @todo change initial focus logic, also for accessibility. 3237 */ 3238 this.$( 'input[type="text"]' ).eq( 0 ).focus(); 3239 } 3240 }, 3241 /** 3242 * @param {Object} event 3243 */ 3244 deleteAttachment: function( event ) { 3245 event.preventDefault(); 3246 3247 if ( window.confirm( l10n.warnDelete ) ) { 3248 this.model.destroy(); 3249 // Keep focus inside media modal 3250 // after image is deleted 3251 this.controller.modal.focusManager.focus(); 3252 } 3253 }, 3254 /** 3255 * @param {Object} event 3256 */ 3257 trashAttachment: function( event ) { 3258 var library = this.controller.library; 3259 event.preventDefault(); 3260 3261 if ( wp.media.view.settings.mediaTrash && 3262 'edit-metadata' === this.controller.content.mode() ) { 3263 3264 this.model.set( 'status', 'trash' ); 3265 this.model.save().done( function() { 3266 library._requery( true ); 3267 } ); 3268 } else { 3269 this.model.destroy(); 3270 } 3271 }, 3272 /** 3273 * @param {Object} event 3274 */ 3275 untrashAttachment: function( event ) { 3276 var library = this.controller.library; 3277 event.preventDefault(); 3278 3279 this.model.set( 'status', 'inherit' ); 3280 this.model.save().done( function() { 3281 library._requery( true ); 3282 } ); 3283 }, 3284 /** 3285 * @param {Object} event 3286 */ 3287 editAttachment: function( event ) { 3288 var editState = this.controller.states.get( 'edit-image' ); 3289 if ( window.imageEdit && editState ) { 3290 event.preventDefault(); 3291 3292 editState.set( 'image', this.model ); 3293 this.controller.setState( 'edit-image' ); 3294 } else { 3295 this.$el.addClass('needs-refresh'); 3296 } 3297 }, 3298 /** 3299 * When reverse tabbing(shift+tab) out of the right details panel, deliver 3300 * the focus to the item in the list that was being edited. 3301 * 3302 * @param {Object} event 3303 */ 3304 toggleSelectionHandler: function( event ) { 3305 if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) { 3306 this.controller.trigger( 'attachment:details:shift-tab', event ); 3307 return false; 3308 } 3309 3310 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 3311 this.controller.trigger( 'attachment:keydown:arrow', event ); 3312 return; 3313 } 3314 } 3315 }); 3316 3317 module.exports = Details; 3318 3319 },{}],27:[function(require,module,exports){ 3320 /** 3321 * wp.media.view.Attachment.EditLibrary 3322 * 3323 * @class 3324 * @augments wp.media.view.Attachment 3325 * @augments wp.media.View 3326 * @augments wp.Backbone.View 3327 * @augments Backbone.View 3328 */ 3329 var EditLibrary = wp.media.view.Attachment.extend({ 3330 buttons: { 3331 close: true 3332 } 3333 }); 3334 3335 module.exports = EditLibrary; 3336 3337 },{}],28:[function(require,module,exports){ 3338 /** 3339 * wp.media.view.Attachments.EditSelection 3340 * 3341 * @class 3342 * @augments wp.media.view.Attachment.Selection 3343 * @augments wp.media.view.Attachment 3344 * @augments wp.media.View 3345 * @augments wp.Backbone.View 3346 * @augments Backbone.View 3347 */ 3348 var EditSelection = wp.media.view.Attachment.Selection.extend({ 3349 buttons: { 3350 close: true 3351 } 3352 }); 3353 3354 module.exports = EditSelection; 3355 3356 },{}],29:[function(require,module,exports){ 3357 /** 3358 * wp.media.view.Attachment.Library 3359 * 3360 * @class 3361 * @augments wp.media.view.Attachment 3362 * @augments wp.media.View 3363 * @augments wp.Backbone.View 3364 * @augments Backbone.View 3365 */ 3366 var Library = wp.media.view.Attachment.extend({ 3367 buttons: { 3368 check: true 3369 } 3370 }); 3371 3372 module.exports = Library; 3373 3374 },{}],30:[function(require,module,exports){ 3375 /** 3376 * wp.media.view.Attachment.Selection 3377 * 3378 * @class 3379 * @augments wp.media.view.Attachment 3380 * @augments wp.media.View 3381 * @augments wp.Backbone.View 3382 * @augments Backbone.View 3383 */ 3384 var Selection = wp.media.view.Attachment.extend({ 3385 className: 'attachment selection', 3386 3387 // On click, just select the model, instead of removing the model from 3388 // the selection. 3389 toggleSelection: function() { 3390 this.options.selection.single( this.model ); 3391 } 3392 }); 3393 3394 module.exports = Selection; 3395 3396 },{}],31:[function(require,module,exports){ 3397 /** 3398 * wp.media.view.Attachments 3399 * 3400 * @class 3401 * @augments wp.media.View 3402 * @augments wp.Backbone.View 3403 * @augments Backbone.View 3404 */ 3405 var View = wp.media.View, 3406 $ = jQuery, 3407 Attachments; 3408 3409 Attachments = View.extend({ 3410 tagName: 'ul', 3411 className: 'attachments', 3412 3413 attributes: { 3414 tabIndex: -1 3415 }, 3416 3417 initialize: function() { 3418 this.el.id = _.uniqueId('__attachments-view-'); 3419 3420 _.defaults( this.options, { 3421 refreshSensitivity: wp.media.isTouchDevice ? 300 : 200, 3422 refreshThreshold: 3, 3423 AttachmentView: wp.media.view.Attachment, 3424 sortable: false, 3425 resize: true, 3426 idealColumnWidth: $( window ).width() < 640 ? 135 : 150 3427 }); 3428 3429 this._viewsByCid = {}; 3430 this.$window = $( window ); 3431 this.resizeEvent = 'resize.media-modal-columns'; 3432 3433 this.collection.on( 'add', function( attachment ) { 3434 this.views.add( this.createAttachmentView( attachment ), { 3435 at: this.collection.indexOf( attachment ) 3436 }); 3437 }, this ); 3438 3439 this.collection.on( 'remove', function( attachment ) { 3440 var view = this._viewsByCid[ attachment.cid ]; 3441 delete this._viewsByCid[ attachment.cid ]; 3442 3443 if ( view ) { 3444 view.remove(); 3445 } 3446 }, this ); 3447 3448 this.collection.on( 'reset', this.render, this ); 3449 3450 this.listenTo( this.controller, 'library:selection:add', this.attachmentFocus ); 3451 3452 // Throttle the scroll handler and bind this. 3453 this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value(); 3454 3455 this.options.scrollElement = this.options.scrollElement || this.el; 3456 $( this.options.scrollElement ).on( 'scroll', this.scroll ); 3457 3458 this.initSortable(); 3459 3460 _.bindAll( this, 'setColumns' ); 3461 3462 if ( this.options.resize ) { 3463 this.on( 'ready', this.bindEvents ); 3464 this.controller.on( 'open', this.setColumns ); 3465 3466 // Call this.setColumns() after this view has been rendered in the DOM so 3467 // attachments get proper width applied. 3468 _.defer( this.setColumns, this ); 3469 } 3470 }, 3471 3472 bindEvents: function() { 3473 this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) ); 3474 }, 3475 3476 attachmentFocus: function() { 3477 this.$( 'li:first' ).focus(); 3478 }, 3479 3480 restoreFocus: function() { 3481 this.$( 'li.selected:first' ).focus(); 3482 }, 3483 3484 arrowEvent: function( event ) { 3485 var attachments = this.$el.children( 'li' ), 3486 perRow = this.columns, 3487 index = attachments.filter( ':focus' ).index(), 3488 row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow ); 3489 3490 if ( index === -1 ) { 3491 return; 3492 } 3493 3494 // Left arrow 3495 if ( 37 === event.keyCode ) { 3496 if ( 0 === index ) { 3497 return; 3498 } 3499 attachments.eq( index - 1 ).focus(); 3500 } 3501 3502 // Up arrow 3503 if ( 38 === event.keyCode ) { 3504 if ( 1 === row ) { 3505 return; 3506 } 3507 attachments.eq( index - perRow ).focus(); 3508 } 3509 3510 // Right arrow 3511 if ( 39 === event.keyCode ) { 3512 if ( attachments.length === index ) { 3513 return; 3514 } 3515 attachments.eq( index + 1 ).focus(); 3516 } 3517 3518 // Down arrow 3519 if ( 40 === event.keyCode ) { 3520 if ( Math.ceil( attachments.length / perRow ) === row ) { 3521 return; 3522 } 3523 attachments.eq( index + perRow ).focus(); 3524 } 3525 }, 3526 3527 dispose: function() { 3528 this.collection.props.off( null, null, this ); 3529 if ( this.options.resize ) { 3530 this.$window.off( this.resizeEvent ); 3531 } 3532 3533 /** 3534 * call 'dispose' directly on the parent class 3535 */ 3536 View.prototype.dispose.apply( this, arguments ); 3537 }, 3538 3539 setColumns: function() { 3540 var prev = this.columns, 3541 width = this.$el.width(); 3542 3543 if ( width ) { 3544 this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1; 3545 3546 if ( ! prev || prev !== this.columns ) { 3547 this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns ); 3548 } 3549 } 3550 }, 3551 3552 initSortable: function() { 3553 var collection = this.collection; 3554 3555 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) { 3556 return; 3557 } 3558 3559 this.$el.sortable( _.extend({ 3560 // If the `collection` has a `comparator`, disable sorting. 3561 disabled: !! collection.comparator, 3562 3563 // Change the position of the attachment as soon as the 3564 // mouse pointer overlaps a thumbnail. 3565 tolerance: 'pointer', 3566 3567 // Record the initial `index` of the dragged model. 3568 start: function( event, ui ) { 3569 ui.item.data('sortableIndexStart', ui.item.index()); 3570 }, 3571 3572 // Update the model's index in the collection. 3573 // Do so silently, as the view is already accurate. 3574 update: function( event, ui ) { 3575 var model = collection.at( ui.item.data('sortableIndexStart') ), 3576 comparator = collection.comparator; 3577 3578 // Temporarily disable the comparator to prevent `add` 3579 // from re-sorting. 3580 delete collection.comparator; 3581 3582 // Silently shift the model to its new index. 3583 collection.remove( model, { 3584 silent: true 3585 }); 3586 collection.add( model, { 3587 silent: true, 3588 at: ui.item.index() 3589 }); 3590 3591 // Restore the comparator. 3592 collection.comparator = comparator; 3593 3594 // Fire the `reset` event to ensure other collections sync. 3595 collection.trigger( 'reset', collection ); 3596 3597 // If the collection is sorted by menu order, 3598 // update the menu order. 3599 collection.saveMenuOrder(); 3600 } 3601 }, this.options.sortable ) ); 3602 3603 // If the `orderby` property is changed on the `collection`, 3604 // check to see if we have a `comparator`. If so, disable sorting. 3605 collection.props.on( 'change:orderby', function() { 3606 this.$el.sortable( 'option', 'disabled', !! collection.comparator ); 3607 }, this ); 3608 3609 this.collection.props.on( 'change:orderby', this.refreshSortable, this ); 3610 this.refreshSortable(); 3611 }, 3612 3613 refreshSortable: function() { 3614 if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) { 3615 return; 3616 } 3617 3618 // If the `collection` has a `comparator`, disable sorting. 3619 var collection = this.collection, 3620 orderby = collection.props.get('orderby'), 3621 enabled = 'menuOrder' === orderby || ! collection.comparator; 3622 3623 this.$el.sortable( 'option', 'disabled', ! enabled ); 3624 }, 3625 3626 /** 3627 * @param {wp.media.model.Attachment} attachment 3628 * @returns {wp.media.View} 3629 */ 3630 createAttachmentView: function( attachment ) { 3631 var view = new this.options.AttachmentView({ 3632 controller: this.controller, 3633 model: attachment, 3634 collection: this.collection, 3635 selection: this.options.selection 3636 }); 3637 3638 return this._viewsByCid[ attachment.cid ] = view; 3639 }, 3640 3641 prepare: function() { 3642 // Create all of the Attachment views, and replace 3643 // the list in a single DOM operation. 3644 if ( this.collection.length ) { 3645 this.views.set( this.collection.map( this.createAttachmentView, this ) ); 3646 3647 // If there are no elements, clear the views and load some. 3648 } else { 3649 this.views.unset(); 3650 this.collection.more().done( this.scroll ); 3651 } 3652 }, 3653 3654 ready: function() { 3655 // Trigger the scroll event to check if we're within the 3656 // threshold to query for additional attachments. 3657 this.scroll(); 3658 }, 3659 3660 scroll: function() { 3661 var view = this, 3662 el = this.options.scrollElement, 3663 scrollTop = el.scrollTop, 3664 toolbar; 3665 3666 // The scroll event occurs on the document, but the element 3667 // that should be checked is the document body. 3668 if ( el === document ) { 3669 el = document.body; 3670 scrollTop = $(document).scrollTop(); 3671 } 3672 3673 if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) { 3674 return; 3675 } 3676 3677 toolbar = this.views.parent.toolbar; 3678 3679 // Show the spinner only if we are close to the bottom. 3680 if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) { 3681 toolbar.get('spinner').show(); 3682 } 3683 3684 if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) { 3685 this.collection.more().done(function() { 3686 view.scroll(); 3687 toolbar.get('spinner').hide(); 3688 }); 3689 } 3690 } 3691 }); 3692 3693 module.exports = Attachments; 3694 3695 },{}],32:[function(require,module,exports){ 6956 3696 /** 6957 3697 * wp.media.view.AttachmentsBrowser … … 7397 4137 module.exports = AttachmentsBrowser; 7398 4138 7399 7400 /***/ }), 7401 /* 79 */ 7402 /***/ (function(module, exports) { 7403 7404 /** 7405 * wp.media.view.Selection 7406 * 7407 * @class 7408 * @augments wp.media.View 7409 * @augments wp.Backbone.View 7410 * @augments Backbone.View 7411 */ 7412 var l10n = wp.media.view.l10n, 7413 Selection; 7414 7415 Selection = wp.media.View.extend({ 7416 tagName: 'div', 7417 className: 'media-selection', 7418 template: wp.template('media-selection'), 7419 7420 events: { 7421 'click .edit-selection': 'edit', 7422 'click .clear-selection': 'clear' 7423 }, 7424 7425 initialize: function() { 7426 _.defaults( this.options, { 7427 editable: false, 7428 clearable: true 7429 }); 7430 7431 /** 7432 * @member {wp.media.view.Attachments.Selection} 7433 */ 7434 this.attachments = new wp.media.view.Attachments.Selection({ 7435 controller: this.controller, 7436 collection: this.collection, 7437 selection: this.collection, 7438 model: new Backbone.Model() 7439 }); 7440 7441 this.views.set( '.selection-view', this.attachments ); 7442 this.collection.on( 'add remove reset', this.refresh, this ); 7443 this.controller.on( 'content:activate', this.refresh, this ); 7444 }, 7445 7446 ready: function() { 7447 this.refresh(); 7448 }, 7449 7450 refresh: function() { 7451 // If the selection hasn't been rendered, bail. 7452 if ( ! this.$el.children().length ) { 7453 return; 7454 } 7455 7456 var collection = this.collection, 7457 editing = 'edit-selection' === this.controller.content.mode(); 7458 7459 // If nothing is selected, display nothing. 7460 this.$el.toggleClass( 'empty', ! collection.length ); 7461 this.$el.toggleClass( 'one', 1 === collection.length ); 7462 this.$el.toggleClass( 'editing', editing ); 7463 7464 this.$('.count').text( l10n.selected.replace('%d', collection.length) ); 7465 }, 7466 7467 edit: function( event ) { 7468 event.preventDefault(); 7469 if ( this.options.editable ) { 7470 this.options.editable.call( this, this.collection ); 7471 } 7472 }, 7473 7474 clear: function( event ) { 7475 event.preventDefault(); 7476 this.collection.reset(); 7477 7478 // Keep focus inside media modal 7479 // after clear link is selected 7480 this.controller.modal.focusManager.focus(); 7481 } 7482 }); 7483 7484 module.exports = Selection; 7485 7486 7487 /***/ }), 7488 /* 80 */ 7489 /***/ (function(module, exports) { 7490 7491 /** 7492 * wp.media.view.Attachment.Selection 7493 * 7494 * @class 7495 * @augments wp.media.view.Attachment 7496 * @augments wp.media.View 7497 * @augments wp.Backbone.View 7498 * @augments Backbone.View 7499 */ 7500 var Selection = wp.media.view.Attachment.extend({ 7501 className: 'attachment selection', 7502 7503 // On click, just select the model, instead of removing the model from 7504 // the selection. 7505 toggleSelection: function() { 7506 this.options.selection.single( this.model ); 7507 } 7508 }); 7509 7510 module.exports = Selection; 7511 7512 7513 /***/ }), 7514 /* 81 */ 7515 /***/ (function(module, exports) { 7516 4139 },{}],33:[function(require,module,exports){ 7517 4140 /** 7518 4141 * wp.media.view.Attachments.Selection … … 7544 4167 module.exports = Selection; 7545 4168 7546 7547 /***/ }), 7548 /* 82 */ 7549 /***/ (function(module, exports) { 7550 4169 },{}],34:[function(require,module,exports){ 7551 4170 /** 7552 * wp.media.view. Attachments.EditSelection4171 * wp.media.view.ButtonGroup 7553 4172 * 7554 4173 * @class 7555 * @augments wp.media.view.Attachment.Selection7556 * @augments wp.media.view.Attachment7557 4174 * @augments wp.media.View 7558 4175 * @augments wp.Backbone.View 7559 4176 * @augments Backbone.View 7560 4177 */ 7561 var EditSelection = wp.media.view.Attachment.Selection.extend({ 7562 buttons: { 7563 close: true 4178 var $ = Backbone.$, 4179 ButtonGroup; 4180 4181 ButtonGroup = wp.media.View.extend({ 4182 tagName: 'div', 4183 className: 'button-group button-large media-button-group', 4184 4185 initialize: function() { 4186 /** 4187 * @member {wp.media.view.Button[]} 4188 */ 4189 this.buttons = _.map( this.options.buttons || [], function( button ) { 4190 if ( button instanceof Backbone.View ) { 4191 return button; 4192 } else { 4193 return new wp.media.view.Button( button ).render(); 4194 } 4195 }); 4196 4197 delete this.options.buttons; 4198 4199 if ( this.options.classes ) { 4200 this.$el.addClass( this.options.classes ); 4201 } 4202 }, 4203 4204 /** 4205 * @returns {wp.media.view.ButtonGroup} 4206 */ 4207 render: function() { 4208 this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() ); 4209 return this; 7564 4210 } 7565 4211 }); 7566 4212 7567 module.exports = EditSelection; 7568 7569 7570 /***/ }), 7571 /* 83 */ 7572 /***/ (function(module, exports) { 7573 4213 module.exports = ButtonGroup; 4214 4215 },{}],35:[function(require,module,exports){ 7574 4216 /** 7575 * wp.media.view.Settings 4217 * wp.media.view.Button 4218 * 4219 * @class 4220 * @augments wp.media.View 4221 * @augments wp.Backbone.View 4222 * @augments Backbone.View 4223 */ 4224 var Button = wp.media.View.extend({ 4225 tagName: 'button', 4226 className: 'media-button', 4227 attributes: { type: 'button' }, 4228 4229 events: { 4230 'click': 'click' 4231 }, 4232 4233 defaults: { 4234 text: '', 4235 style: '', 4236 size: 'large', 4237 disabled: false 4238 }, 4239 4240 initialize: function() { 4241 /** 4242 * Create a model with the provided `defaults`. 4243 * 4244 * @member {Backbone.Model} 4245 */ 4246 this.model = new Backbone.Model( this.defaults ); 4247 4248 // If any of the `options` have a key from `defaults`, apply its 4249 // value to the `model` and remove it from the `options object. 4250 _.each( this.defaults, function( def, key ) { 4251 var value = this.options[ key ]; 4252 if ( _.isUndefined( value ) ) { 4253 return; 4254 } 4255 4256 this.model.set( key, value ); 4257 delete this.options[ key ]; 4258 }, this ); 4259 4260 this.listenTo( this.model, 'change', this.render ); 4261 }, 4262 /** 4263 * @returns {wp.media.view.Button} Returns itself to allow chaining 4264 */ 4265 render: function() { 4266 var classes = [ 'button', this.className ], 4267 model = this.model.toJSON(); 4268 4269 if ( model.style ) { 4270 classes.push( 'button-' + model.style ); 4271 } 4272 4273 if ( model.size ) { 4274 classes.push( 'button-' + model.size ); 4275 } 4276 4277 classes = _.uniq( classes.concat( this.options.classes ) ); 4278 this.el.className = classes.join(' '); 4279 4280 this.$el.attr( 'disabled', model.disabled ); 4281 this.$el.text( this.model.get('text') ); 4282 4283 return this; 4284 }, 4285 /** 4286 * @param {Object} event 4287 */ 4288 click: function( event ) { 4289 if ( '#' === this.attributes.href ) { 4290 event.preventDefault(); 4291 } 4292 4293 if ( this.options.click && ! this.model.get('disabled') ) { 4294 this.options.click.apply( this, arguments ); 4295 } 4296 } 4297 }); 4298 4299 module.exports = Button; 4300 4301 },{}],36:[function(require,module,exports){ 4302 /** 4303 * wp.media.view.Cropper 4304 * 4305 * Uses the imgAreaSelect plugin to allow a user to crop an image. 4306 * 4307 * Takes imgAreaSelect options from 4308 * wp.customize.HeaderControl.calculateImageSelectOptions via 4309 * wp.customize.HeaderControl.openMM. 7576 4310 * 7577 4311 * @class … … 7581 4315 */ 7582 4316 var View = wp.media.View, 7583 $ = Backbone.$, 7584 Settings; 7585 7586 Settings = View.extend({ 7587 events: { 7588 'click button': 'updateHandler', 7589 'change input': 'updateHandler', 7590 'change select': 'updateHandler', 7591 'change textarea': 'updateHandler' 7592 }, 7593 4317 UploaderStatus = wp.media.view.UploaderStatus, 4318 l10n = wp.media.view.l10n, 4319 $ = jQuery, 4320 Cropper; 4321 4322 Cropper = View.extend({ 4323 className: 'crop-content', 4324 template: wp.template('crop-content'), 7594 4325 initialize: function() { 7595 this.model = this.model || new Backbone.Model(); 7596 this.listenTo( this.model, 'change', this.updateChanges ); 7597 }, 7598 4326 _.bindAll(this, 'onImageLoad'); 4327 }, 4328 ready: function() { 4329 this.controller.frame.on('content:error:crop', this.onError, this); 4330 this.$image = this.$el.find('.crop-image'); 4331 this.$image.on('load', this.onImageLoad); 4332 $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250)); 4333 }, 4334 remove: function() { 4335 $(window).off('resize.cropper'); 4336 this.$el.remove(); 4337 this.$el.off(); 4338 View.prototype.remove.apply(this, arguments); 4339 }, 7599 4340 prepare: function() { 7600 return _.defaults({ 7601 model: this.model.toJSON() 7602 }, this.options ); 7603 }, 7604 /** 7605 * @returns {wp.media.view.Settings} Returns itself to allow chaining 7606 */ 7607 render: function() { 7608 View.prototype.render.apply( this, arguments ); 7609 // Select the correct values. 7610 _( this.model.attributes ).chain().keys().each( this.update, this ); 7611 return this; 7612 }, 7613 /** 7614 * @param {string} key 7615 */ 7616 update: function( key ) { 7617 var value = this.model.get( key ), 7618 $setting = this.$('[data-setting="' + key + '"]'), 7619 $buttons, $value; 7620 7621 // Bail if we didn't find a matching setting. 7622 if ( ! $setting.length ) { 7623 return; 7624 } 7625 7626 // Attempt to determine how the setting is rendered and update 7627 // the selected value. 7628 7629 // Handle dropdowns. 7630 if ( $setting.is('select') ) { 7631 $value = $setting.find('[value="' + value + '"]'); 7632 7633 if ( $value.length ) { 7634 $setting.find('option').prop( 'selected', false ); 7635 $value.prop( 'selected', true ); 7636 } else { 7637 // If we can't find the desired value, record what *is* selected. 7638 this.model.set( key, $setting.find(':selected').val() ); 7639 } 7640 7641 // Handle button groups. 7642 } else if ( $setting.hasClass('button-group') ) { 7643 $buttons = $setting.find('button').removeClass('active'); 7644 $buttons.filter( '[value="' + value + '"]' ).addClass('active'); 7645 7646 // Handle text inputs and textareas. 7647 } else if ( $setting.is('input[type="text"], textarea') ) { 7648 if ( ! $setting.is(':focus') ) { 7649 $setting.val( value ); 7650 } 7651 // Handle checkboxes. 7652 } else if ( $setting.is('input[type="checkbox"]') ) { 7653 $setting.prop( 'checked', !! value && 'false' !== value ); 7654 } 7655 }, 7656 /** 7657 * @param {Object} event 7658 */ 7659 updateHandler: function( event ) { 7660 var $setting = $( event.target ).closest('[data-setting]'), 7661 value = event.target.value, 7662 userSetting; 7663 7664 event.preventDefault(); 7665 7666 if ( ! $setting.length ) { 7667 return; 7668 } 7669 7670 // Use the correct value for checkboxes. 7671 if ( $setting.is('input[type="checkbox"]') ) { 7672 value = $setting[0].checked; 7673 } 7674 7675 // Update the corresponding setting. 7676 this.model.set( $setting.data('setting'), value ); 7677 7678 // If the setting has a corresponding user setting, 7679 // update that as well. 7680 if ( userSetting = $setting.data('userSetting') ) { 7681 window.setUserSetting( userSetting, value ); 7682 } 7683 }, 7684 7685 updateChanges: function( model ) { 7686 if ( model.hasChanged() ) { 7687 _( model.changed ).chain().keys().each( this.update, this ); 7688 } 4341 return { 4342 title: l10n.cropYourImage, 4343 url: this.options.attachment.get('url') 4344 }; 4345 }, 4346 onImageLoad: function() { 4347 var imgOptions = this.controller.get('imgSelectOptions'); 4348 if (typeof imgOptions === 'function') { 4349 imgOptions = imgOptions(this.options.attachment, this.controller); 4350 } 4351 4352 imgOptions = _.extend(imgOptions, {parent: this.$el}); 4353 this.trigger('image-loaded'); 4354 this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions); 4355 }, 4356 onError: function() { 4357 var filename = this.options.attachment.get('filename'); 4358 4359 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({ 4360 filename: UploaderStatus.prototype.filename(filename), 4361 message: window._wpMediaViewsL10n.cropError 4362 }), { at: 0 }); 7689 4363 } 7690 4364 }); 7691 4365 7692 module.exports = Settings; 7693 7694 7695 /***/ }), 7696 /* 84 */ 7697 /***/ (function(module, exports) { 7698 4366 module.exports = Cropper; 4367 4368 },{}],37:[function(require,module,exports){ 7699 4369 /** 7700 * wp.media.view.Settings.AttachmentDisplay 7701 * 7702 * @class 7703 * @augments wp.media.view.Settings 7704 * @augments wp.media.View 7705 * @augments wp.Backbone.View 7706 * @augments Backbone.View 7707 */ 7708 var Settings = wp.media.view.Settings, 7709 AttachmentDisplay; 7710 7711 AttachmentDisplay = Settings.extend({ 7712 className: 'attachment-display-settings', 7713 template: wp.template('attachment-display-settings'), 7714 7715 initialize: function() { 7716 var attachment = this.options.attachment; 7717 7718 _.defaults( this.options, { 7719 userSettings: false 7720 }); 7721 // Call 'initialize' directly on the parent class. 7722 Settings.prototype.initialize.apply( this, arguments ); 7723 this.listenTo( this.model, 'change:link', this.updateLinkTo ); 7724 7725 if ( attachment ) { 7726 attachment.on( 'change:uploading', this.render, this ); 7727 } 7728 }, 7729 7730 dispose: function() { 7731 var attachment = this.options.attachment; 7732 if ( attachment ) { 7733 attachment.off( null, null, this ); 7734 } 7735 /** 7736 * call 'dispose' directly on the parent class 7737 */ 7738 Settings.prototype.dispose.apply( this, arguments ); 7739 }, 7740 /** 7741 * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining 7742 */ 7743 render: function() { 7744 var attachment = this.options.attachment; 7745 if ( attachment ) { 7746 _.extend( this.options, { 7747 sizes: attachment.get('sizes'), 7748 type: attachment.get('type') 7749 }); 7750 } 7751 /** 7752 * call 'render' directly on the parent class 7753 */ 7754 Settings.prototype.render.call( this ); 7755 this.updateLinkTo(); 7756 return this; 7757 }, 7758 7759 updateLinkTo: function() { 7760 var linkTo = this.model.get('link'), 7761 $input = this.$('.link-to-custom'), 7762 attachment = this.options.attachment; 7763 7764 if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) { 7765 $input.addClass( 'hidden' ); 7766 return; 7767 } 7768 7769 if ( attachment ) { 7770 if ( 'post' === linkTo ) { 7771 $input.val( attachment.get('link') ); 7772 } else if ( 'file' === linkTo ) { 7773 $input.val( attachment.get('url') ); 7774 } else if ( ! this.model.get('linkUrl') ) { 7775 $input.val('http://'); 7776 } 7777 7778 $input.prop( 'readonly', 'custom' !== linkTo ); 7779 } 7780 7781 $input.removeClass( 'hidden' ); 7782 7783 // If the input is visible, focus and select its contents. 7784 if ( ! wp.media.isTouchDevice && $input.is(':visible') ) { 7785 $input.focus()[0].select(); 7786 } 7787 } 7788 }); 7789 7790 module.exports = AttachmentDisplay; 7791 7792 7793 /***/ }), 7794 /* 85 */ 7795 /***/ (function(module, exports) { 7796 7797 /** 7798 * wp.media.view.Settings.Gallery 7799 * 7800 * @class 7801 * @augments wp.media.view.Settings 7802 * @augments wp.media.View 7803 * @augments wp.Backbone.View 7804 * @augments Backbone.View 7805 */ 7806 var Gallery = wp.media.view.Settings.extend({ 7807 className: 'collection-settings gallery-settings', 7808 template: wp.template('gallery-settings') 7809 }); 7810 7811 module.exports = Gallery; 7812 7813 7814 /***/ }), 7815 /* 86 */ 7816 /***/ (function(module, exports) { 7817 7818 /** 7819 * wp.media.view.Settings.Playlist 7820 * 7821 * @class 7822 * @augments wp.media.view.Settings 7823 * @augments wp.media.View 7824 * @augments wp.Backbone.View 7825 * @augments Backbone.View 7826 */ 7827 var Playlist = wp.media.view.Settings.extend({ 7828 className: 'collection-settings playlist-settings', 7829 template: wp.template('playlist-settings') 7830 }); 7831 7832 module.exports = Playlist; 7833 7834 7835 /***/ }), 7836 /* 87 */ 7837 /***/ (function(module, exports) { 7838 7839 /** 7840 * wp.media.view.Attachment.Details 7841 * 7842 * @class 7843 * @augments wp.media.view.Attachment 7844 * @augments wp.media.View 7845 * @augments wp.Backbone.View 7846 * @augments Backbone.View 7847 */ 7848 var Attachment = wp.media.view.Attachment, 7849 l10n = wp.media.view.l10n, 7850 Details; 7851 7852 Details = Attachment.extend({ 7853 tagName: 'div', 7854 className: 'attachment-details', 7855 template: wp.template('attachment-details'), 7856 7857 attributes: function() { 7858 return { 7859 'tabIndex': 0, 7860 'data-id': this.model.get( 'id' ) 7861 }; 7862 }, 7863 7864 events: { 7865 'change [data-setting]': 'updateSetting', 7866 'change [data-setting] input': 'updateSetting', 7867 'change [data-setting] select': 'updateSetting', 7868 'change [data-setting] textarea': 'updateSetting', 7869 'click .delete-attachment': 'deleteAttachment', 7870 'click .trash-attachment': 'trashAttachment', 7871 'click .untrash-attachment': 'untrashAttachment', 7872 'click .edit-attachment': 'editAttachment', 7873 'keydown': 'toggleSelectionHandler' 7874 }, 7875 7876 initialize: function() { 7877 this.options = _.defaults( this.options, { 7878 rerenderOnModelChange: false 7879 }); 7880 7881 this.on( 'ready', this.initialFocus ); 7882 // Call 'initialize' directly on the parent class. 7883 Attachment.prototype.initialize.apply( this, arguments ); 7884 }, 7885 7886 initialFocus: function() { 7887 if ( ! wp.media.isTouchDevice ) { 7888 /* 7889 Previously focused the first ':input' (the readonly URL text field). 7890 Since the first ':input' is now a button (delete/trash): when pressing 7891 spacebar on an attachment, Firefox fires deleteAttachment/trashAttachment 7892 as soon as focus is moved. Explicitly target the first text field for now. 7893 @todo change initial focus logic, also for accessibility. 7894 */ 7895 this.$( 'input[type="text"]' ).eq( 0 ).focus(); 7896 } 7897 }, 7898 /** 7899 * @param {Object} event 7900 */ 7901 deleteAttachment: function( event ) { 7902 event.preventDefault(); 7903 7904 if ( window.confirm( l10n.warnDelete ) ) { 7905 this.model.destroy(); 7906 // Keep focus inside media modal 7907 // after image is deleted 7908 this.controller.modal.focusManager.focus(); 7909 } 7910 }, 7911 /** 7912 * @param {Object} event 7913 */ 7914 trashAttachment: function( event ) { 7915 var library = this.controller.library; 7916 event.preventDefault(); 7917 7918 if ( wp.media.view.settings.mediaTrash && 7919 'edit-metadata' === this.controller.content.mode() ) { 7920 7921 this.model.set( 'status', 'trash' ); 7922 this.model.save().done( function() { 7923 library._requery( true ); 7924 } ); 7925 } else { 7926 this.model.destroy(); 7927 } 7928 }, 7929 /** 7930 * @param {Object} event 7931 */ 7932 untrashAttachment: function( event ) { 7933 var library = this.controller.library; 7934 event.preventDefault(); 7935 7936 this.model.set( 'status', 'inherit' ); 7937 this.model.save().done( function() { 7938 library._requery( true ); 7939 } ); 7940 }, 7941 /** 7942 * @param {Object} event 7943 */ 7944 editAttachment: function( event ) { 7945 var editState = this.controller.states.get( 'edit-image' ); 7946 if ( window.imageEdit && editState ) { 7947 event.preventDefault(); 7948 7949 editState.set( 'image', this.model ); 7950 this.controller.setState( 'edit-image' ); 7951 } else { 7952 this.$el.addClass('needs-refresh'); 7953 } 7954 }, 7955 /** 7956 * When reverse tabbing(shift+tab) out of the right details panel, deliver 7957 * the focus to the item in the list that was being edited. 7958 * 7959 * @param {Object} event 7960 */ 7961 toggleSelectionHandler: function( event ) { 7962 if ( 'keydown' === event.type && 9 === event.keyCode && event.shiftKey && event.target === this.$( ':tabbable' ).get( 0 ) ) { 7963 this.controller.trigger( 'attachment:details:shift-tab', event ); 7964 return false; 7965 } 7966 7967 if ( 37 === event.keyCode || 38 === event.keyCode || 39 === event.keyCode || 40 === event.keyCode ) { 7968 this.controller.trigger( 'attachment:keydown:arrow', event ); 7969 return; 7970 } 7971 } 7972 }); 7973 7974 module.exports = Details; 7975 7976 7977 /***/ }), 7978 /* 88 */ 7979 /***/ (function(module, exports) { 7980 7981 /** 7982 * wp.media.view.AttachmentCompat 7983 * 7984 * A view to display fields added via the `attachment_fields_to_edit` filter. 4370 * wp.media.view.EditImage 7985 4371 * 7986 4372 * @class … … 7990 4376 */ 7991 4377 var View = wp.media.View, 7992 AttachmentCompat; 7993 7994 AttachmentCompat = View.extend({ 7995 tagName: 'form', 7996 className: 'compat-item', 7997 7998 events: { 7999 'submit': 'preventDefault', 8000 'change input': 'save', 8001 'change select': 'save', 8002 'change textarea': 'save' 8003 }, 8004 8005 initialize: function() { 8006 this.listenTo( this.model, 'change:compat', this.render ); 8007 }, 8008 /** 8009 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 8010 */ 8011 dispose: function() { 8012 if ( this.$(':focus').length ) { 8013 this.save(); 8014 } 8015 /** 8016 * call 'dispose' directly on the parent class 8017 */ 8018 return View.prototype.dispose.apply( this, arguments ); 8019 }, 8020 /** 8021 * @returns {wp.media.view.AttachmentCompat} Returns itself to allow chaining 8022 */ 8023 render: function() { 8024 var compat = this.model.get('compat'); 8025 if ( ! compat || ! compat.item ) { 8026 return; 8027 } 8028 8029 this.views.detach(); 8030 this.$el.html( compat.item ); 8031 this.views.render(); 8032 return this; 8033 }, 8034 /** 8035 * @param {Object} event 8036 */ 8037 preventDefault: function( event ) { 8038 event.preventDefault(); 8039 }, 8040 /** 8041 * @param {Object} event 8042 */ 8043 save: function( event ) { 8044 var data = {}; 8045 8046 if ( event ) { 8047 event.preventDefault(); 8048 } 8049 8050 _.each( this.$el.serializeArray(), function( pair ) { 8051 data[ pair.name ] = pair.value; 8052 }); 8053 8054 this.controller.trigger( 'attachment:compat:waiting', ['waiting'] ); 8055 this.model.saveCompat( data ).always( _.bind( this.postSave, this ) ); 8056 }, 8057 8058 postSave: function() { 8059 this.controller.trigger( 'attachment:compat:ready', ['ready'] ); 4378 EditImage; 4379 4380 EditImage = View.extend({ 4381 className: 'image-editor', 4382 template: wp.template('image-editor'), 4383 4384 initialize: function( options ) { 4385 this.editor = window.imageEdit; 4386 this.controller = options.controller; 4387 View.prototype.initialize.apply( this, arguments ); 4388 }, 4389 4390 prepare: function() { 4391 return this.model.toJSON(); 4392 }, 4393 4394 loadEditor: function() { 4395 var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this ); 4396 dfd.done( _.bind( this.focus, this ) ); 4397 }, 4398 4399 focus: function() { 4400 this.$( '.imgedit-submit .button' ).eq( 0 ).focus(); 4401 }, 4402 4403 back: function() { 4404 var lastState = this.controller.lastState(); 4405 this.controller.setState( lastState ); 4406 }, 4407 4408 refresh: function() { 4409 this.model.fetch(); 4410 }, 4411 4412 save: function() { 4413 var lastState = this.controller.lastState(); 4414 4415 this.model.fetch().done( _.bind( function() { 4416 this.controller.setState( lastState ); 4417 }, this ) ); 8060 4418 } 4419 8061 4420 }); 8062 4421 8063 module.exports = AttachmentCompat; 8064 8065 8066 /***/ }), 8067 /* 89 */ 8068 /***/ (function(module, exports) { 8069 8070 /** 8071 * wp.media.view.Iframe 8072 * 8073 * @class 8074 * @augments wp.media.View 8075 * @augments wp.Backbone.View 8076 * @augments Backbone.View 8077 */ 8078 var Iframe = wp.media.View.extend({ 8079 className: 'media-iframe', 8080 /** 8081 * @returns {wp.media.view.Iframe} Returns itself to allow chaining 8082 */ 8083 render: function() { 8084 this.views.detach(); 8085 this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' ); 8086 this.views.render(); 8087 return this; 8088 } 8089 }); 8090 8091 module.exports = Iframe; 8092 8093 8094 /***/ }), 8095 /* 90 */ 8096 /***/ (function(module, exports) { 8097 4422 module.exports = EditImage; 4423 4424 },{}],38:[function(require,module,exports){ 8098 4425 /** 8099 4426 * wp.media.view.Embed … … 8159 4486 module.exports = Embed; 8160 4487 8161 8162 /***/ }), 8163 /* 91 */ 8164 /***/ (function(module, exports) { 8165 4488 },{}],39:[function(require,module,exports){ 8166 4489 /** 8167 * wp.media.view. Label4490 * wp.media.view.EmbedImage 8168 4491 * 8169 4492 * @class 4493 * @augments wp.media.view.Settings.AttachmentDisplay 4494 * @augments wp.media.view.Settings 8170 4495 * @augments wp.media.View 8171 4496 * @augments wp.Backbone.View 8172 4497 * @augments Backbone.View 8173 4498 */ 8174 var Label = wp.media.View.extend({ 8175 tagName: 'label', 8176 className: 'screen-reader-text', 4499 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay, 4500 EmbedImage; 4501 4502 EmbedImage = AttachmentDisplay.extend({ 4503 className: 'embed-media-settings', 4504 template: wp.template('embed-image-settings'), 8177 4505 8178 4506 initialize: function() { 8179 this.value = this.options.value; 8180 }, 8181 8182 render: function() { 8183 this.$el.html( this.value ); 8184 8185 return this; 4507 /** 4508 * Call `initialize` directly on parent class with passed arguments 4509 */ 4510 AttachmentDisplay.prototype.initialize.apply( this, arguments ); 4511 this.listenTo( this.model, 'change:url', this.updateImage ); 4512 }, 4513 4514 updateImage: function() { 4515 this.$('img').attr( 'src', this.model.get('url') ); 8186 4516 } 8187 4517 }); 8188 4518 8189 module.exports = Label; 8190 8191 8192 /***/ }), 8193 /* 92 */ 8194 /***/ (function(module, exports) { 8195 8196 /** 8197 * wp.media.view.EmbedUrl 8198 * 8199 * @class 8200 * @augments wp.media.View 8201 * @augments wp.Backbone.View 8202 * @augments Backbone.View 8203 */ 8204 var View = wp.media.View, 8205 $ = jQuery, 8206 EmbedUrl; 8207 8208 EmbedUrl = View.extend({ 8209 tagName: 'label', 8210 className: 'embed-url', 8211 8212 events: { 8213 'input': 'url', 8214 'keyup': 'url', 8215 'change': 'url' 8216 }, 8217 8218 initialize: function() { 8219 this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') ); 8220 this.input = this.$input[0]; 8221 8222 this.spinner = $('<span class="spinner" />')[0]; 8223 this.$el.append([ this.input, this.spinner ]); 8224 8225 this.listenTo( this.model, 'change:url', this.render ); 8226 8227 if ( this.model.get( 'url' ) ) { 8228 _.delay( _.bind( function () { 8229 this.model.trigger( 'change:url' ); 8230 }, this ), 500 ); 8231 } 8232 }, 8233 /** 8234 * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining 8235 */ 8236 render: function() { 8237 var $input = this.$input; 8238 8239 if ( $input.is(':focus') ) { 8240 return; 8241 } 8242 8243 this.input.value = this.model.get('url') || 'http://'; 8244 /** 8245 * Call `render` directly on parent class with passed arguments 8246 */ 8247 View.prototype.render.apply( this, arguments ); 8248 return this; 8249 }, 8250 8251 ready: function() { 8252 if ( ! wp.media.isTouchDevice ) { 8253 this.focus(); 8254 } 8255 }, 8256 8257 url: function( event ) { 8258 this.model.set( 'url', event.target.value ); 8259 }, 8260 8261 /** 8262 * If the input is visible, focus and select its contents. 8263 */ 8264 focus: function() { 8265 var $input = this.$input; 8266 if ( $input.is(':visible') ) { 8267 $input.focus()[0].select(); 8268 } 8269 } 8270 }); 8271 8272 module.exports = EmbedUrl; 8273 8274 8275 /***/ }), 8276 /* 93 */ 8277 /***/ (function(module, exports) { 8278 4519 module.exports = EmbedImage; 4520 4521 },{}],40:[function(require,module,exports){ 8279 4522 /** 8280 4523 * wp.media.view.EmbedLink … … 8365 4608 module.exports = EmbedLink; 8366 4609 8367 8368 /***/ }), 8369 /* 94 */ 8370 /***/ (function(module, exports) { 8371 4610 },{}],41:[function(require,module,exports){ 8372 4611 /** 8373 * wp.media.view.Embed Image4612 * wp.media.view.EmbedUrl 8374 4613 * 8375 4614 * @class 8376 * @augments wp.media.view.Settings.AttachmentDisplay8377 * @augments wp.media.view.Settings8378 4615 * @augments wp.media.View 8379 4616 * @augments wp.Backbone.View 8380 4617 * @augments Backbone.View 8381 4618 */ 8382 var AttachmentDisplay = wp.media.view.Settings.AttachmentDisplay, 8383 EmbedImage; 8384 8385 EmbedImage = AttachmentDisplay.extend({ 8386 className: 'embed-media-settings', 8387 template: wp.template('embed-image-settings'), 4619 var View = wp.media.View, 4620 $ = jQuery, 4621 EmbedUrl; 4622 4623 EmbedUrl = View.extend({ 4624 tagName: 'label', 4625 className: 'embed-url', 4626 4627 events: { 4628 'input': 'url', 4629 'keyup': 'url', 4630 'change': 'url' 4631 }, 8388 4632 8389 4633 initialize: function() { 4634 this.$input = $('<input id="embed-url-field" type="url" />').val( this.model.get('url') ); 4635 this.input = this.$input[0]; 4636 4637 this.spinner = $('<span class="spinner" />')[0]; 4638 this.$el.append([ this.input, this.spinner ]); 4639 4640 this.listenTo( this.model, 'change:url', this.render ); 4641 4642 if ( this.model.get( 'url' ) ) { 4643 _.delay( _.bind( function () { 4644 this.model.trigger( 'change:url' ); 4645 }, this ), 500 ); 4646 } 4647 }, 4648 /** 4649 * @returns {wp.media.view.EmbedUrl} Returns itself to allow chaining 4650 */ 4651 render: function() { 4652 var $input = this.$input; 4653 4654 if ( $input.is(':focus') ) { 4655 return; 4656 } 4657 4658 this.input.value = this.model.get('url') || 'http://'; 8390 4659 /** 8391 * Call ` initialize` directly on parent class with passed arguments4660 * Call `render` directly on parent class with passed arguments 8392 4661 */ 8393 AttachmentDisplay.prototype.initialize.apply( this, arguments ); 8394 this.listenTo( this.model, 'change:url', this.updateImage ); 8395 }, 8396 8397 updateImage: function() { 8398 this.$('img').attr( 'src', this.model.get('url') ); 4662 View.prototype.render.apply( this, arguments ); 4663 return this; 4664 }, 4665 4666 ready: function() { 4667 if ( ! wp.media.isTouchDevice ) { 4668 this.focus(); 4669 } 4670 }, 4671 4672 url: function( event ) { 4673 this.model.set( 'url', event.target.value ); 4674 }, 4675 4676 /** 4677 * If the input is visible, focus and select its contents. 4678 */ 4679 focus: function() { 4680 var $input = this.$input; 4681 if ( $input.is(':visible') ) { 4682 $input.focus()[0].select(); 4683 } 8399 4684 } 8400 4685 }); 8401 4686 8402 module.exports = EmbedImage; 8403 8404 8405 /***/ }), 8406 /* 95 */ 8407 /***/ (function(module, exports) { 8408 4687 module.exports = EmbedUrl; 4688 4689 },{}],42:[function(require,module,exports){ 4690 /** 4691 * wp.media.view.FocusManager 4692 * 4693 * @class 4694 * @augments wp.media.View 4695 * @augments wp.Backbone.View 4696 * @augments Backbone.View 4697 */ 4698 var FocusManager = wp.media.View.extend({ 4699 4700 events: { 4701 'keydown': 'constrainTabbing' 4702 }, 4703 4704 focus: function() { // Reset focus on first left menu item 4705 this.$('.media-menu-item').first().focus(); 4706 }, 4707 /** 4708 * @param {Object} event 4709 */ 4710 constrainTabbing: function( event ) { 4711 var tabbables; 4712 4713 // Look for the tab key. 4714 if ( 9 !== event.keyCode ) { 4715 return; 4716 } 4717 4718 // Skip the file input added by Plupload. 4719 tabbables = this.$( ':tabbable' ).not( '.moxie-shim input[type="file"]' ); 4720 4721 // Keep tab focus within media modal while it's open 4722 if ( tabbables.last()[0] === event.target && ! event.shiftKey ) { 4723 tabbables.first().focus(); 4724 return false; 4725 } else if ( tabbables.first()[0] === event.target && event.shiftKey ) { 4726 tabbables.last().focus(); 4727 return false; 4728 } 4729 } 4730 4731 }); 4732 4733 module.exports = FocusManager; 4734 4735 },{}],43:[function(require,module,exports){ 4736 /** 4737 * wp.media.view.Frame 4738 * 4739 * A frame is a composite view consisting of one or more regions and one or more 4740 * states. 4741 * 4742 * @see wp.media.controller.State 4743 * @see wp.media.controller.Region 4744 * 4745 * @class 4746 * @augments wp.media.View 4747 * @augments wp.Backbone.View 4748 * @augments Backbone.View 4749 * @mixes wp.media.controller.StateMachine 4750 */ 4751 var Frame = wp.media.View.extend({ 4752 initialize: function() { 4753 _.defaults( this.options, { 4754 mode: [ 'select' ] 4755 }); 4756 this._createRegions(); 4757 this._createStates(); 4758 this._createModes(); 4759 }, 4760 4761 _createRegions: function() { 4762 // Clone the regions array. 4763 this.regions = this.regions ? this.regions.slice() : []; 4764 4765 // Initialize regions. 4766 _.each( this.regions, function( region ) { 4767 this[ region ] = new wp.media.controller.Region({ 4768 view: this, 4769 id: region, 4770 selector: '.media-frame-' + region 4771 }); 4772 }, this ); 4773 }, 4774 /** 4775 * Create the frame's states. 4776 * 4777 * @see wp.media.controller.State 4778 * @see wp.media.controller.StateMachine 4779 * 4780 * @fires wp.media.controller.State#ready 4781 */ 4782 _createStates: function() { 4783 // Create the default `states` collection. 4784 this.states = new Backbone.Collection( null, { 4785 model: wp.media.controller.State 4786 }); 4787 4788 // Ensure states have a reference to the frame. 4789 this.states.on( 'add', function( model ) { 4790 model.frame = this; 4791 model.trigger('ready'); 4792 }, this ); 4793 4794 if ( this.options.states ) { 4795 this.states.add( this.options.states ); 4796 } 4797 }, 4798 4799 /** 4800 * A frame can be in a mode or multiple modes at one time. 4801 * 4802 * For example, the manage media frame can be in the `Bulk Select` or `Edit` mode. 4803 */ 4804 _createModes: function() { 4805 // Store active "modes" that the frame is in. Unrelated to region modes. 4806 this.activeModes = new Backbone.Collection(); 4807 this.activeModes.on( 'add remove reset', _.bind( this.triggerModeEvents, this ) ); 4808 4809 _.each( this.options.mode, function( mode ) { 4810 this.activateMode( mode ); 4811 }, this ); 4812 }, 4813 /** 4814 * Reset all states on the frame to their defaults. 4815 * 4816 * @returns {wp.media.view.Frame} Returns itself to allow chaining 4817 */ 4818 reset: function() { 4819 this.states.invoke( 'trigger', 'reset' ); 4820 return this; 4821 }, 4822 /** 4823 * Map activeMode collection events to the frame. 4824 */ 4825 triggerModeEvents: function( model, collection, options ) { 4826 var collectionEvent, 4827 modeEventMap = { 4828 add: 'activate', 4829 remove: 'deactivate' 4830 }, 4831 eventToTrigger; 4832 // Probably a better way to do this. 4833 _.each( options, function( value, key ) { 4834 if ( value ) { 4835 collectionEvent = key; 4836 } 4837 } ); 4838 4839 if ( ! _.has( modeEventMap, collectionEvent ) ) { 4840 return; 4841 } 4842 4843 eventToTrigger = model.get('id') + ':' + modeEventMap[collectionEvent]; 4844 this.trigger( eventToTrigger ); 4845 }, 4846 /** 4847 * Activate a mode on the frame. 4848 * 4849 * @param string mode Mode ID. 4850 * @returns {this} Returns itself to allow chaining. 4851 */ 4852 activateMode: function( mode ) { 4853 // Bail if the mode is already active. 4854 if ( this.isModeActive( mode ) ) { 4855 return; 4856 } 4857 this.activeModes.add( [ { id: mode } ] ); 4858 // Add a CSS class to the frame so elements can be styled for the mode. 4859 this.$el.addClass( 'mode-' + mode ); 4860 4861 return this; 4862 }, 4863 /** 4864 * Deactivate a mode on the frame. 4865 * 4866 * @param string mode Mode ID. 4867 * @returns {this} Returns itself to allow chaining. 4868 */ 4869 deactivateMode: function( mode ) { 4870 // Bail if the mode isn't active. 4871 if ( ! this.isModeActive( mode ) ) { 4872 return this; 4873 } 4874 this.activeModes.remove( this.activeModes.where( { id: mode } ) ); 4875 this.$el.removeClass( 'mode-' + mode ); 4876 /** 4877 * Frame mode deactivation event. 4878 * 4879 * @event this#{mode}:deactivate 4880 */ 4881 this.trigger( mode + ':deactivate' ); 4882 4883 return this; 4884 }, 4885 /** 4886 * Check if a mode is enabled on the frame. 4887 * 4888 * @param string mode Mode ID. 4889 * @return bool 4890 */ 4891 isModeActive: function( mode ) { 4892 return Boolean( this.activeModes.where( { id: mode } ).length ); 4893 } 4894 }); 4895 4896 // Make the `Frame` a `StateMachine`. 4897 _.extend( Frame.prototype, wp.media.controller.StateMachine.prototype ); 4898 4899 module.exports = Frame; 4900 4901 },{}],44:[function(require,module,exports){ 4902 /** 4903 * wp.media.view.MediaFrame.ImageDetails 4904 * 4905 * A media frame for manipulating an image that's already been inserted 4906 * into a post. 4907 * 4908 * @class 4909 * @augments wp.media.view.MediaFrame.Select 4910 * @augments wp.media.view.MediaFrame 4911 * @augments wp.media.view.Frame 4912 * @augments wp.media.View 4913 * @augments wp.Backbone.View 4914 * @augments Backbone.View 4915 * @mixes wp.media.controller.StateMachine 4916 */ 4917 var Select = wp.media.view.MediaFrame.Select, 4918 l10n = wp.media.view.l10n, 4919 ImageDetails; 4920 4921 ImageDetails = Select.extend({ 4922 defaults: { 4923 id: 'image', 4924 url: '', 4925 menu: 'image-details', 4926 content: 'image-details', 4927 toolbar: 'image-details', 4928 type: 'link', 4929 title: l10n.imageDetailsTitle, 4930 priority: 120 4931 }, 4932 4933 initialize: function( options ) { 4934 this.image = new wp.media.model.PostImage( options.metadata ); 4935 this.options.selection = new wp.media.model.Selection( this.image.attachment, { multiple: false } ); 4936 Select.prototype.initialize.apply( this, arguments ); 4937 }, 4938 4939 bindHandlers: function() { 4940 Select.prototype.bindHandlers.apply( this, arguments ); 4941 this.on( 'menu:create:image-details', this.createMenu, this ); 4942 this.on( 'content:create:image-details', this.imageDetailsContent, this ); 4943 this.on( 'content:render:edit-image', this.editImageContent, this ); 4944 this.on( 'toolbar:render:image-details', this.renderImageDetailsToolbar, this ); 4945 // override the select toolbar 4946 this.on( 'toolbar:render:replace', this.renderReplaceImageToolbar, this ); 4947 }, 4948 4949 createStates: function() { 4950 this.states.add([ 4951 new wp.media.controller.ImageDetails({ 4952 image: this.image, 4953 editable: false 4954 }), 4955 new wp.media.controller.ReplaceImage({ 4956 id: 'replace-image', 4957 library: wp.media.query( { type: 'image' } ), 4958 image: this.image, 4959 multiple: false, 4960 title: l10n.imageReplaceTitle, 4961 toolbar: 'replace', 4962 priority: 80, 4963 displaySettings: true 4964 }), 4965 new wp.media.controller.EditImage( { 4966 image: this.image, 4967 selection: this.options.selection 4968 } ) 4969 ]); 4970 }, 4971 4972 imageDetailsContent: function( options ) { 4973 options.view = new wp.media.view.ImageDetails({ 4974 controller: this, 4975 model: this.state().image, 4976 attachment: this.state().image.attachment 4977 }); 4978 }, 4979 4980 editImageContent: function() { 4981 var state = this.state(), 4982 model = state.get('image'), 4983 view; 4984 4985 if ( ! model ) { 4986 return; 4987 } 4988 4989 view = new wp.media.view.EditImage( { model: model, controller: this } ).render(); 4990 4991 this.content.set( view ); 4992 4993 // after bringing in the frame, load the actual editor via an ajax call 4994 view.loadEditor(); 4995 4996 }, 4997 4998 renderImageDetailsToolbar: function() { 4999 this.toolbar.set( new wp.media.view.Toolbar({ 5000 controller: this, 5001 items: { 5002 select: { 5003 style: 'primary', 5004 text: l10n.update, 5005 priority: 80, 5006 5007 click: function() { 5008 var controller = this.controller, 5009 state = controller.state(); 5010 5011 controller.close(); 5012 5013 // not sure if we want to use wp.media.string.image which will create a shortcode or 5014 // perhaps wp.html.string to at least to build the <img /> 5015 state.trigger( 'update', controller.image.toJSON() ); 5016 5017 // Restore and reset the default state. 5018 controller.setState( controller.options.state ); 5019 controller.reset(); 5020 } 5021 } 5022 } 5023 }) ); 5024 }, 5025 5026 renderReplaceImageToolbar: function() { 5027 var frame = this, 5028 lastState = frame.lastState(), 5029 previous = lastState && lastState.id; 5030 5031 this.toolbar.set( new wp.media.view.Toolbar({ 5032 controller: this, 5033 items: { 5034 back: { 5035 text: l10n.back, 5036 priority: 20, 5037 click: function() { 5038 if ( previous ) { 5039 frame.setState( previous ); 5040 } else { 5041 frame.close(); 5042 } 5043 } 5044 }, 5045 5046 replace: { 5047 style: 'primary', 5048 text: l10n.replace, 5049 priority: 80, 5050 5051 click: function() { 5052 var controller = this.controller, 5053 state = controller.state(), 5054 selection = state.get( 'selection' ), 5055 attachment = selection.single(); 5056 5057 controller.close(); 5058 5059 controller.image.changeAttachment( attachment, state.display( attachment ) ); 5060 5061 // not sure if we want to use wp.media.string.image which will create a shortcode or 5062 // perhaps wp.html.string to at least to build the <img /> 5063 state.trigger( 'replace', controller.image.toJSON() ); 5064 5065 // Restore and reset the default state. 5066 controller.setState( controller.options.state ); 5067 controller.reset(); 5068 } 5069 } 5070 } 5071 }) ); 5072 } 5073 5074 }); 5075 5076 module.exports = ImageDetails; 5077 5078 },{}],45:[function(require,module,exports){ 5079 /** 5080 * wp.media.view.MediaFrame.Post 5081 * 5082 * The frame for manipulating media on the Edit Post page. 5083 * 5084 * @class 5085 * @augments wp.media.view.MediaFrame.Select 5086 * @augments wp.media.view.MediaFrame 5087 * @augments wp.media.view.Frame 5088 * @augments wp.media.View 5089 * @augments wp.Backbone.View 5090 * @augments Backbone.View 5091 * @mixes wp.media.controller.StateMachine 5092 */ 5093 var Select = wp.media.view.MediaFrame.Select, 5094 Library = wp.media.controller.Library, 5095 l10n = wp.media.view.l10n, 5096 Post; 5097 5098 Post = Select.extend({ 5099 initialize: function() { 5100 this.counts = { 5101 audio: { 5102 count: wp.media.view.settings.attachmentCounts.audio, 5103 state: 'playlist' 5104 }, 5105 video: { 5106 count: wp.media.view.settings.attachmentCounts.video, 5107 state: 'video-playlist' 5108 } 5109 }; 5110 5111 _.defaults( this.options, { 5112 multiple: true, 5113 editing: false, 5114 state: 'insert', 5115 metadata: {} 5116 }); 5117 5118 // Call 'initialize' directly on the parent class. 5119 Select.prototype.initialize.apply( this, arguments ); 5120 this.createIframeStates(); 5121 5122 }, 5123 5124 /** 5125 * Create the default states. 5126 */ 5127 createStates: function() { 5128 var options = this.options; 5129 5130 this.states.add([ 5131 // Main states. 5132 new Library({ 5133 id: 'insert', 5134 title: l10n.insertMediaTitle, 5135 priority: 20, 5136 toolbar: 'main-insert', 5137 filterable: 'all', 5138 library: wp.media.query( options.library ), 5139 multiple: options.multiple ? 'reset' : false, 5140 editable: true, 5141 5142 // If the user isn't allowed to edit fields, 5143 // can they still edit it locally? 5144 allowLocalEdits: true, 5145 5146 // Show the attachment display settings. 5147 displaySettings: true, 5148 // Update user settings when users adjust the 5149 // attachment display settings. 5150 displayUserSettings: true 5151 }), 5152 5153 new Library({ 5154 id: 'gallery', 5155 title: l10n.createGalleryTitle, 5156 priority: 40, 5157 toolbar: 'main-gallery', 5158 filterable: 'uploaded', 5159 multiple: 'add', 5160 editable: false, 5161 5162 library: wp.media.query( _.defaults({ 5163 type: 'image' 5164 }, options.library ) ) 5165 }), 5166 5167 // Embed states. 5168 new wp.media.controller.Embed( { metadata: options.metadata } ), 5169 5170 new wp.media.controller.EditImage( { model: options.editImage } ), 5171 5172 // Gallery states. 5173 new wp.media.controller.GalleryEdit({ 5174 library: options.selection, 5175 editing: options.editing, 5176 menu: 'gallery' 5177 }), 5178 5179 new wp.media.controller.GalleryAdd(), 5180 5181 new Library({ 5182 id: 'playlist', 5183 title: l10n.createPlaylistTitle, 5184 priority: 60, 5185 toolbar: 'main-playlist', 5186 filterable: 'uploaded', 5187 multiple: 'add', 5188 editable: false, 5189 5190 library: wp.media.query( _.defaults({ 5191 type: 'audio' 5192 }, options.library ) ) 5193 }), 5194 5195 // Playlist states. 5196 new wp.media.controller.CollectionEdit({ 5197 type: 'audio', 5198 collectionType: 'playlist', 5199 title: l10n.editPlaylistTitle, 5200 SettingsView: wp.media.view.Settings.Playlist, 5201 library: options.selection, 5202 editing: options.editing, 5203 menu: 'playlist', 5204 dragInfoText: l10n.playlistDragInfo, 5205 dragInfo: false 5206 }), 5207 5208 new wp.media.controller.CollectionAdd({ 5209 type: 'audio', 5210 collectionType: 'playlist', 5211 title: l10n.addToPlaylistTitle 5212 }), 5213 5214 new Library({ 5215 id: 'video-playlist', 5216 title: l10n.createVideoPlaylistTitle, 5217 priority: 60, 5218 toolbar: 'main-video-playlist', 5219 filterable: 'uploaded', 5220 multiple: 'add', 5221 editable: false, 5222 5223 library: wp.media.query( _.defaults({ 5224 type: 'video' 5225 }, options.library ) ) 5226 }), 5227 5228 new wp.media.controller.CollectionEdit({ 5229 type: 'video', 5230 collectionType: 'playlist', 5231 title: l10n.editVideoPlaylistTitle, 5232 SettingsView: wp.media.view.Settings.Playlist, 5233 library: options.selection, 5234 editing: options.editing, 5235 menu: 'video-playlist', 5236 dragInfoText: l10n.videoPlaylistDragInfo, 5237 dragInfo: false 5238 }), 5239 5240 new wp.media.controller.CollectionAdd({ 5241 type: 'video', 5242 collectionType: 'playlist', 5243 title: l10n.addToVideoPlaylistTitle 5244 }) 5245 ]); 5246 5247 if ( wp.media.view.settings.post.featuredImageId ) { 5248 this.states.add( new wp.media.controller.FeaturedImage() ); 5249 } 5250 }, 5251 5252 bindHandlers: function() { 5253 var handlers, checkCounts; 5254 5255 Select.prototype.bindHandlers.apply( this, arguments ); 5256 5257 this.on( 'activate', this.activate, this ); 5258 5259 // Only bother checking media type counts if one of the counts is zero 5260 checkCounts = _.find( this.counts, function( type ) { 5261 return type.count === 0; 5262 } ); 5263 5264 if ( typeof checkCounts !== 'undefined' ) { 5265 this.listenTo( wp.media.model.Attachments.all, 'change:type', this.mediaTypeCounts ); 5266 } 5267 5268 this.on( 'menu:create:gallery', this.createMenu, this ); 5269 this.on( 'menu:create:playlist', this.createMenu, this ); 5270 this.on( 'menu:create:video-playlist', this.createMenu, this ); 5271 this.on( 'toolbar:create:main-insert', this.createToolbar, this ); 5272 this.on( 'toolbar:create:main-gallery', this.createToolbar, this ); 5273 this.on( 'toolbar:create:main-playlist', this.createToolbar, this ); 5274 this.on( 'toolbar:create:main-video-playlist', this.createToolbar, this ); 5275 this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this ); 5276 this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this ); 5277 5278 handlers = { 5279 menu: { 5280 'default': 'mainMenu', 5281 'gallery': 'galleryMenu', 5282 'playlist': 'playlistMenu', 5283 'video-playlist': 'videoPlaylistMenu' 5284 }, 5285 5286 content: { 5287 'embed': 'embedContent', 5288 'edit-image': 'editImageContent', 5289 'edit-selection': 'editSelectionContent' 5290 }, 5291 5292 toolbar: { 5293 'main-insert': 'mainInsertToolbar', 5294 'main-gallery': 'mainGalleryToolbar', 5295 'gallery-edit': 'galleryEditToolbar', 5296 'gallery-add': 'galleryAddToolbar', 5297 'main-playlist': 'mainPlaylistToolbar', 5298 'playlist-edit': 'playlistEditToolbar', 5299 'playlist-add': 'playlistAddToolbar', 5300 'main-video-playlist': 'mainVideoPlaylistToolbar', 5301 'video-playlist-edit': 'videoPlaylistEditToolbar', 5302 'video-playlist-add': 'videoPlaylistAddToolbar' 5303 } 5304 }; 5305 5306 _.each( handlers, function( regionHandlers, region ) { 5307 _.each( regionHandlers, function( callback, handler ) { 5308 this.on( region + ':render:' + handler, this[ callback ], this ); 5309 }, this ); 5310 }, this ); 5311 }, 5312 5313 activate: function() { 5314 // Hide menu items for states tied to particular media types if there are no items 5315 _.each( this.counts, function( type ) { 5316 if ( type.count < 1 ) { 5317 this.menuItemVisibility( type.state, 'hide' ); 5318 } 5319 }, this ); 5320 }, 5321 5322 mediaTypeCounts: function( model, attr ) { 5323 if ( typeof this.counts[ attr ] !== 'undefined' && this.counts[ attr ].count < 1 ) { 5324 this.counts[ attr ].count++; 5325 this.menuItemVisibility( this.counts[ attr ].state, 'show' ); 5326 } 5327 }, 5328 5329 // Menus 5330 /** 5331 * @param {wp.Backbone.View} view 5332 */ 5333 mainMenu: function( view ) { 5334 view.set({ 5335 'library-separator': new wp.media.View({ 5336 className: 'separator', 5337 priority: 100 5338 }) 5339 }); 5340 }, 5341 5342 menuItemVisibility: function( state, visibility ) { 5343 var menu = this.menu.get(); 5344 if ( visibility === 'hide' ) { 5345 menu.hide( state ); 5346 } else if ( visibility === 'show' ) { 5347 menu.show( state ); 5348 } 5349 }, 5350 /** 5351 * @param {wp.Backbone.View} view 5352 */ 5353 galleryMenu: function( view ) { 5354 var lastState = this.lastState(), 5355 previous = lastState && lastState.id, 5356 frame = this; 5357 5358 view.set({ 5359 cancel: { 5360 text: l10n.cancelGalleryTitle, 5361 priority: 20, 5362 click: function() { 5363 if ( previous ) { 5364 frame.setState( previous ); 5365 } else { 5366 frame.close(); 5367 } 5368 5369 // Keep focus inside media modal 5370 // after canceling a gallery 5371 this.controller.modal.focusManager.focus(); 5372 } 5373 }, 5374 separateCancel: new wp.media.View({ 5375 className: 'separator', 5376 priority: 40 5377 }) 5378 }); 5379 }, 5380 5381 playlistMenu: function( view ) { 5382 var lastState = this.lastState(), 5383 previous = lastState && lastState.id, 5384 frame = this; 5385 5386 view.set({ 5387 cancel: { 5388 text: l10n.cancelPlaylistTitle, 5389 priority: 20, 5390 click: function() { 5391 if ( previous ) { 5392 frame.setState( previous ); 5393 } else { 5394 frame.close(); 5395 } 5396 } 5397 }, 5398 separateCancel: new wp.media.View({ 5399 className: 'separator', 5400 priority: 40 5401 }) 5402 }); 5403 }, 5404 5405 videoPlaylistMenu: function( view ) { 5406 var lastState = this.lastState(), 5407 previous = lastState && lastState.id, 5408 frame = this; 5409 5410 view.set({ 5411 cancel: { 5412 text: l10n.cancelVideoPlaylistTitle, 5413 priority: 20, 5414 click: function() { 5415 if ( previous ) { 5416 frame.setState( previous ); 5417 } else { 5418 frame.close(); 5419 } 5420 } 5421 }, 5422 separateCancel: new wp.media.View({ 5423 className: 'separator', 5424 priority: 40 5425 }) 5426 }); 5427 }, 5428 5429 // Content 5430 embedContent: function() { 5431 var view = new wp.media.view.Embed({ 5432 controller: this, 5433 model: this.state() 5434 }).render(); 5435 5436 this.content.set( view ); 5437 5438 if ( ! wp.media.isTouchDevice ) { 5439 view.url.focus(); 5440 } 5441 }, 5442 5443 editSelectionContent: function() { 5444 var state = this.state(), 5445 selection = state.get('selection'), 5446 view; 5447 5448 view = new wp.media.view.AttachmentsBrowser({ 5449 controller: this, 5450 collection: selection, 5451 selection: selection, 5452 model: state, 5453 sortable: true, 5454 search: false, 5455 date: false, 5456 dragInfo: true, 5457 5458 AttachmentView: wp.media.view.Attachments.EditSelection 5459 }).render(); 5460 5461 view.toolbar.set( 'backToLibrary', { 5462 text: l10n.returnToLibrary, 5463 priority: -100, 5464 5465 click: function() { 5466 this.controller.content.mode('browse'); 5467 } 5468 }); 5469 5470 // Browse our library of attachments. 5471 this.content.set( view ); 5472 5473 // Trigger the controller to set focus 5474 this.trigger( 'edit:selection', this ); 5475 }, 5476 5477 editImageContent: function() { 5478 var image = this.state().get('image'), 5479 view = new wp.media.view.EditImage( { model: image, controller: this } ).render(); 5480 5481 this.content.set( view ); 5482 5483 // after creating the wrapper view, load the actual editor via an ajax call 5484 view.loadEditor(); 5485 5486 }, 5487 5488 // Toolbars 5489 5490 /** 5491 * @param {wp.Backbone.View} view 5492 */ 5493 selectionStatusToolbar: function( view ) { 5494 var editable = this.state().get('editable'); 5495 5496 view.set( 'selection', new wp.media.view.Selection({ 5497 controller: this, 5498 collection: this.state().get('selection'), 5499 priority: -40, 5500 5501 // If the selection is editable, pass the callback to 5502 // switch the content mode. 5503 editable: editable && function() { 5504 this.controller.content.mode('edit-selection'); 5505 } 5506 }).render() ); 5507 }, 5508 5509 /** 5510 * @param {wp.Backbone.View} view 5511 */ 5512 mainInsertToolbar: function( view ) { 5513 var controller = this; 5514 5515 this.selectionStatusToolbar( view ); 5516 5517 view.set( 'insert', { 5518 style: 'primary', 5519 priority: 80, 5520 text: l10n.insertIntoPost, 5521 requires: { selection: true }, 5522 5523 /** 5524 * @fires wp.media.controller.State#insert 5525 */ 5526 click: function() { 5527 var state = controller.state(), 5528 selection = state.get('selection'); 5529 5530 controller.close(); 5531 state.trigger( 'insert', selection ).reset(); 5532 } 5533 }); 5534 }, 5535 5536 /** 5537 * @param {wp.Backbone.View} view 5538 */ 5539 mainGalleryToolbar: function( view ) { 5540 var controller = this; 5541 5542 this.selectionStatusToolbar( view ); 5543 5544 view.set( 'gallery', { 5545 style: 'primary', 5546 text: l10n.createNewGallery, 5547 priority: 60, 5548 requires: { selection: true }, 5549 5550 click: function() { 5551 var selection = controller.state().get('selection'), 5552 edit = controller.state('gallery-edit'), 5553 models = selection.where({ type: 'image' }); 5554 5555 edit.set( 'library', new wp.media.model.Selection( models, { 5556 props: selection.props.toJSON(), 5557 multiple: true 5558 }) ); 5559 5560 this.controller.setState('gallery-edit'); 5561 5562 // Keep focus inside media modal 5563 // after jumping to gallery view 5564 this.controller.modal.focusManager.focus(); 5565 } 5566 }); 5567 }, 5568 5569 mainPlaylistToolbar: function( view ) { 5570 var controller = this; 5571 5572 this.selectionStatusToolbar( view ); 5573 5574 view.set( 'playlist', { 5575 style: 'primary', 5576 text: l10n.createNewPlaylist, 5577 priority: 100, 5578 requires: { selection: true }, 5579 5580 click: function() { 5581 var selection = controller.state().get('selection'), 5582 edit = controller.state('playlist-edit'), 5583 models = selection.where({ type: 'audio' }); 5584 5585 edit.set( 'library', new wp.media.model.Selection( models, { 5586 props: selection.props.toJSON(), 5587 multiple: true 5588 }) ); 5589 5590 this.controller.setState('playlist-edit'); 5591 5592 // Keep focus inside media modal 5593 // after jumping to playlist view 5594 this.controller.modal.focusManager.focus(); 5595 } 5596 }); 5597 }, 5598 5599 mainVideoPlaylistToolbar: function( view ) { 5600 var controller = this; 5601 5602 this.selectionStatusToolbar( view ); 5603 5604 view.set( 'video-playlist', { 5605 style: 'primary', 5606 text: l10n.createNewVideoPlaylist, 5607 priority: 100, 5608 requires: { selection: true }, 5609 5610 click: function() { 5611 var selection = controller.state().get('selection'), 5612 edit = controller.state('video-playlist-edit'), 5613 models = selection.where({ type: 'video' }); 5614 5615 edit.set( 'library', new wp.media.model.Selection( models, { 5616 props: selection.props.toJSON(), 5617 multiple: true 5618 }) ); 5619 5620 this.controller.setState('video-playlist-edit'); 5621 5622 // Keep focus inside media modal 5623 // after jumping to video playlist view 5624 this.controller.modal.focusManager.focus(); 5625 } 5626 }); 5627 }, 5628 5629 featuredImageToolbar: function( toolbar ) { 5630 this.createSelectToolbar( toolbar, { 5631 text: l10n.setFeaturedImage, 5632 state: this.options.state 5633 }); 5634 }, 5635 5636 mainEmbedToolbar: function( toolbar ) { 5637 toolbar.view = new wp.media.view.Toolbar.Embed({ 5638 controller: this 5639 }); 5640 }, 5641 5642 galleryEditToolbar: function() { 5643 var editing = this.state().get('editing'); 5644 this.toolbar.set( new wp.media.view.Toolbar({ 5645 controller: this, 5646 items: { 5647 insert: { 5648 style: 'primary', 5649 text: editing ? l10n.updateGallery : l10n.insertGallery, 5650 priority: 80, 5651 requires: { library: true }, 5652 5653 /** 5654 * @fires wp.media.controller.State#update 5655 */ 5656 click: function() { 5657 var controller = this.controller, 5658 state = controller.state(); 5659 5660 controller.close(); 5661 state.trigger( 'update', state.get('library') ); 5662 5663 // Restore and reset the default state. 5664 controller.setState( controller.options.state ); 5665 controller.reset(); 5666 } 5667 } 5668 } 5669 }) ); 5670 }, 5671 5672 galleryAddToolbar: function() { 5673 this.toolbar.set( new wp.media.view.Toolbar({ 5674 controller: this, 5675 items: { 5676 insert: { 5677 style: 'primary', 5678 text: l10n.addToGallery, 5679 priority: 80, 5680 requires: { selection: true }, 5681 5682 /** 5683 * @fires wp.media.controller.State#reset 5684 */ 5685 click: function() { 5686 var controller = this.controller, 5687 state = controller.state(), 5688 edit = controller.state('gallery-edit'); 5689 5690 edit.get('library').add( state.get('selection').models ); 5691 state.trigger('reset'); 5692 controller.setState('gallery-edit'); 5693 } 5694 } 5695 } 5696 }) ); 5697 }, 5698 5699 playlistEditToolbar: function() { 5700 var editing = this.state().get('editing'); 5701 this.toolbar.set( new wp.media.view.Toolbar({ 5702 controller: this, 5703 items: { 5704 insert: { 5705 style: 'primary', 5706 text: editing ? l10n.updatePlaylist : l10n.insertPlaylist, 5707 priority: 80, 5708 requires: { library: true }, 5709 5710 /** 5711 * @fires wp.media.controller.State#update 5712 */ 5713 click: function() { 5714 var controller = this.controller, 5715 state = controller.state(); 5716 5717 controller.close(); 5718 state.trigger( 'update', state.get('library') ); 5719 5720 // Restore and reset the default state. 5721 controller.setState( controller.options.state ); 5722 controller.reset(); 5723 } 5724 } 5725 } 5726 }) ); 5727 }, 5728 5729 playlistAddToolbar: function() { 5730 this.toolbar.set( new wp.media.view.Toolbar({ 5731 controller: this, 5732 items: { 5733 insert: { 5734 style: 'primary', 5735 text: l10n.addToPlaylist, 5736 priority: 80, 5737 requires: { selection: true }, 5738 5739 /** 5740 * @fires wp.media.controller.State#reset 5741 */ 5742 click: function() { 5743 var controller = this.controller, 5744 state = controller.state(), 5745 edit = controller.state('playlist-edit'); 5746 5747 edit.get('library').add( state.get('selection').models ); 5748 state.trigger('reset'); 5749 controller.setState('playlist-edit'); 5750 } 5751 } 5752 } 5753 }) ); 5754 }, 5755 5756 videoPlaylistEditToolbar: function() { 5757 var editing = this.state().get('editing'); 5758 this.toolbar.set( new wp.media.view.Toolbar({ 5759 controller: this, 5760 items: { 5761 insert: { 5762 style: 'primary', 5763 text: editing ? l10n.updateVideoPlaylist : l10n.insertVideoPlaylist, 5764 priority: 140, 5765 requires: { library: true }, 5766 5767 click: function() { 5768 var controller = this.controller, 5769 state = controller.state(), 5770 library = state.get('library'); 5771 5772 library.type = 'video'; 5773 5774 controller.close(); 5775 state.trigger( 'update', library ); 5776 5777 // Restore and reset the default state. 5778 controller.setState( controller.options.state ); 5779 controller.reset(); 5780 } 5781 } 5782 } 5783 }) ); 5784 }, 5785 5786 videoPlaylistAddToolbar: function() { 5787 this.toolbar.set( new wp.media.view.Toolbar({ 5788 controller: this, 5789 items: { 5790 insert: { 5791 style: 'primary', 5792 text: l10n.addToVideoPlaylist, 5793 priority: 140, 5794 requires: { selection: true }, 5795 5796 click: function() { 5797 var controller = this.controller, 5798 state = controller.state(), 5799 edit = controller.state('video-playlist-edit'); 5800 5801 edit.get('library').add( state.get('selection').models ); 5802 state.trigger('reset'); 5803 controller.setState('video-playlist-edit'); 5804 } 5805 } 5806 } 5807 }) ); 5808 } 5809 }); 5810 5811 module.exports = Post; 5812 5813 },{}],46:[function(require,module,exports){ 5814 /** 5815 * wp.media.view.MediaFrame.Select 5816 * 5817 * A frame for selecting an item or items from the media library. 5818 * 5819 * @class 5820 * @augments wp.media.view.MediaFrame 5821 * @augments wp.media.view.Frame 5822 * @augments wp.media.View 5823 * @augments wp.Backbone.View 5824 * @augments Backbone.View 5825 * @mixes wp.media.controller.StateMachine 5826 */ 5827 5828 var MediaFrame = wp.media.view.MediaFrame, 5829 l10n = wp.media.view.l10n, 5830 Select; 5831 5832 Select = MediaFrame.extend({ 5833 initialize: function() { 5834 // Call 'initialize' directly on the parent class. 5835 MediaFrame.prototype.initialize.apply( this, arguments ); 5836 5837 _.defaults( this.options, { 5838 selection: [], 5839 library: {}, 5840 multiple: false, 5841 state: 'library' 5842 }); 5843 5844 this.createSelection(); 5845 this.createStates(); 5846 this.bindHandlers(); 5847 }, 5848 5849 /** 5850 * Attach a selection collection to the frame. 5851 * 5852 * A selection is a collection of attachments used for a specific purpose 5853 * by a media frame. e.g. Selecting an attachment (or many) to insert into 5854 * post content. 5855 * 5856 * @see media.model.Selection 5857 */ 5858 createSelection: function() { 5859 var selection = this.options.selection; 5860 5861 if ( ! (selection instanceof wp.media.model.Selection) ) { 5862 this.options.selection = new wp.media.model.Selection( selection, { 5863 multiple: this.options.multiple 5864 }); 5865 } 5866 5867 this._selection = { 5868 attachments: new wp.media.model.Attachments(), 5869 difference: [] 5870 }; 5871 }, 5872 5873 /** 5874 * Create the default states on the frame. 5875 */ 5876 createStates: function() { 5877 var options = this.options; 5878 5879 if ( this.options.states ) { 5880 return; 5881 } 5882 5883 // Add the default states. 5884 this.states.add([ 5885 // Main states. 5886 new wp.media.controller.Library({ 5887 library: wp.media.query( options.library ), 5888 multiple: options.multiple, 5889 title: options.title, 5890 priority: 20 5891 }) 5892 ]); 5893 }, 5894 5895 /** 5896 * Bind region mode event callbacks. 5897 * 5898 * @see media.controller.Region.render 5899 */ 5900 bindHandlers: function() { 5901 this.on( 'router:create:browse', this.createRouter, this ); 5902 this.on( 'router:render:browse', this.browseRouter, this ); 5903 this.on( 'content:create:browse', this.browseContent, this ); 5904 this.on( 'content:render:upload', this.uploadContent, this ); 5905 this.on( 'toolbar:create:select', this.createSelectToolbar, this ); 5906 }, 5907 5908 /** 5909 * Render callback for the router region in the `browse` mode. 5910 * 5911 * @param {wp.media.view.Router} routerView 5912 */ 5913 browseRouter: function( routerView ) { 5914 routerView.set({ 5915 upload: { 5916 text: l10n.uploadFilesTitle, 5917 priority: 20 5918 }, 5919 browse: { 5920 text: l10n.mediaLibraryTitle, 5921 priority: 40 5922 } 5923 }); 5924 }, 5925 5926 /** 5927 * Render callback for the content region in the `browse` mode. 5928 * 5929 * @param {wp.media.controller.Region} contentRegion 5930 */ 5931 browseContent: function( contentRegion ) { 5932 var state = this.state(); 5933 5934 this.$el.removeClass('hide-toolbar'); 5935 5936 // Browse our library of attachments. 5937 contentRegion.view = new wp.media.view.AttachmentsBrowser({ 5938 controller: this, 5939 collection: state.get('library'), 5940 selection: state.get('selection'), 5941 model: state, 5942 sortable: state.get('sortable'), 5943 search: state.get('searchable'), 5944 filters: state.get('filterable'), 5945 date: state.get('date'), 5946 display: state.has('display') ? state.get('display') : state.get('displaySettings'), 5947 dragInfo: state.get('dragInfo'), 5948 5949 idealColumnWidth: state.get('idealColumnWidth'), 5950 suggestedWidth: state.get('suggestedWidth'), 5951 suggestedHeight: state.get('suggestedHeight'), 5952 5953 AttachmentView: state.get('AttachmentView') 5954 }); 5955 }, 5956 5957 /** 5958 * Render callback for the content region in the `upload` mode. 5959 */ 5960 uploadContent: function() { 5961 this.$el.removeClass( 'hide-toolbar' ); 5962 this.content.set( new wp.media.view.UploaderInline({ 5963 controller: this 5964 }) ); 5965 }, 5966 5967 /** 5968 * Toolbars 5969 * 5970 * @param {Object} toolbar 5971 * @param {Object} [options={}] 5972 * @this wp.media.controller.Region 5973 */ 5974 createSelectToolbar: function( toolbar, options ) { 5975 options = options || this.options.button || {}; 5976 options.controller = this; 5977 5978 toolbar.view = new wp.media.view.Toolbar.Select( options ); 5979 } 5980 }); 5981 5982 module.exports = Select; 5983 5984 },{}],47:[function(require,module,exports){ 5985 /** 5986 * wp.media.view.Iframe 5987 * 5988 * @class 5989 * @augments wp.media.View 5990 * @augments wp.Backbone.View 5991 * @augments Backbone.View 5992 */ 5993 var Iframe = wp.media.View.extend({ 5994 className: 'media-iframe', 5995 /** 5996 * @returns {wp.media.view.Iframe} Returns itself to allow chaining 5997 */ 5998 render: function() { 5999 this.views.detach(); 6000 this.$el.html( '<iframe src="' + this.controller.state().get('src') + '" />' ); 6001 this.views.render(); 6002 return this; 6003 } 6004 }); 6005 6006 module.exports = Iframe; 6007 6008 },{}],48:[function(require,module,exports){ 8409 6009 /** 8410 6010 * wp.media.view.ImageDetails … … 8574 6174 module.exports = ImageDetails; 8575 6175 8576 8577 /***/ }), 8578 /* 96 */ 8579 /***/ (function(module, exports) { 8580 6176 },{}],49:[function(require,module,exports){ 8581 6177 /** 8582 * wp.media.view.Cropper 8583 * 8584 * Uses the imgAreaSelect plugin to allow a user to crop an image. 8585 * 8586 * Takes imgAreaSelect options from 8587 * wp.customize.HeaderControl.calculateImageSelectOptions via 8588 * wp.customize.HeaderControl.openMM. 6178 * wp.media.view.Label 6179 * 6180 * @class 6181 * @augments wp.media.View 6182 * @augments wp.Backbone.View 6183 * @augments Backbone.View 6184 */ 6185 var Label = wp.media.View.extend({ 6186 tagName: 'label', 6187 className: 'screen-reader-text', 6188 6189 initialize: function() { 6190 this.value = this.options.value; 6191 }, 6192 6193 render: function() { 6194 this.$el.html( this.value ); 6195 6196 return this; 6197 } 6198 }); 6199 6200 module.exports = Label; 6201 6202 },{}],50:[function(require,module,exports){ 6203 /** 6204 * wp.media.view.MediaFrame 6205 * 6206 * The frame used to create the media modal. 6207 * 6208 * @class 6209 * @augments wp.media.view.Frame 6210 * @augments wp.media.View 6211 * @augments wp.Backbone.View 6212 * @augments Backbone.View 6213 * @mixes wp.media.controller.StateMachine 6214 */ 6215 var Frame = wp.media.view.Frame, 6216 $ = jQuery, 6217 MediaFrame; 6218 6219 MediaFrame = Frame.extend({ 6220 className: 'media-frame', 6221 template: wp.template('media-frame'), 6222 regions: ['menu','title','content','toolbar','router'], 6223 6224 events: { 6225 'click div.media-frame-title h1': 'toggleMenu' 6226 }, 6227 6228 /** 6229 * @global wp.Uploader 6230 */ 6231 initialize: function() { 6232 Frame.prototype.initialize.apply( this, arguments ); 6233 6234 _.defaults( this.options, { 6235 title: '', 6236 modal: true, 6237 uploader: true 6238 }); 6239 6240 // Ensure core UI is enabled. 6241 this.$el.addClass('wp-core-ui'); 6242 6243 // Initialize modal container view. 6244 if ( this.options.modal ) { 6245 this.modal = new wp.media.view.Modal({ 6246 controller: this, 6247 title: this.options.title 6248 }); 6249 6250 this.modal.content( this ); 6251 } 6252 6253 // Force the uploader off if the upload limit has been exceeded or 6254 // if the browser isn't supported. 6255 if ( wp.Uploader.limitExceeded || ! wp.Uploader.browser.supported ) { 6256 this.options.uploader = false; 6257 } 6258 6259 // Initialize window-wide uploader. 6260 if ( this.options.uploader ) { 6261 this.uploader = new wp.media.view.UploaderWindow({ 6262 controller: this, 6263 uploader: { 6264 dropzone: this.modal ? this.modal.$el : this.$el, 6265 container: this.$el 6266 } 6267 }); 6268 this.views.set( '.media-frame-uploader', this.uploader ); 6269 } 6270 6271 this.on( 'attach', _.bind( this.views.ready, this.views ), this ); 6272 6273 // Bind default title creation. 6274 this.on( 'title:create:default', this.createTitle, this ); 6275 this.title.mode('default'); 6276 6277 this.on( 'title:render', function( view ) { 6278 view.$el.append( '<span class="dashicons dashicons-arrow-down"></span>' ); 6279 }); 6280 6281 // Bind default menu. 6282 this.on( 'menu:create:default', this.createMenu, this ); 6283 }, 6284 /** 6285 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 6286 */ 6287 render: function() { 6288 // Activate the default state if no active state exists. 6289 if ( ! this.state() && this.options.state ) { 6290 this.setState( this.options.state ); 6291 } 6292 /** 6293 * call 'render' directly on the parent class 6294 */ 6295 return Frame.prototype.render.apply( this, arguments ); 6296 }, 6297 /** 6298 * @param {Object} title 6299 * @this wp.media.controller.Region 6300 */ 6301 createTitle: function( title ) { 6302 title.view = new wp.media.View({ 6303 controller: this, 6304 tagName: 'h1' 6305 }); 6306 }, 6307 /** 6308 * @param {Object} menu 6309 * @this wp.media.controller.Region 6310 */ 6311 createMenu: function( menu ) { 6312 menu.view = new wp.media.view.Menu({ 6313 controller: this 6314 }); 6315 }, 6316 6317 toggleMenu: function() { 6318 this.$el.find( '.media-menu' ).toggleClass( 'visible' ); 6319 }, 6320 6321 /** 6322 * @param {Object} toolbar 6323 * @this wp.media.controller.Region 6324 */ 6325 createToolbar: function( toolbar ) { 6326 toolbar.view = new wp.media.view.Toolbar({ 6327 controller: this 6328 }); 6329 }, 6330 /** 6331 * @param {Object} router 6332 * @this wp.media.controller.Region 6333 */ 6334 createRouter: function( router ) { 6335 router.view = new wp.media.view.Router({ 6336 controller: this 6337 }); 6338 }, 6339 /** 6340 * @param {Object} options 6341 */ 6342 createIframeStates: function( options ) { 6343 var settings = wp.media.view.settings, 6344 tabs = settings.tabs, 6345 tabUrl = settings.tabUrl, 6346 $postId; 6347 6348 if ( ! tabs || ! tabUrl ) { 6349 return; 6350 } 6351 6352 // Add the post ID to the tab URL if it exists. 6353 $postId = $('#post_ID'); 6354 if ( $postId.length ) { 6355 tabUrl += '&post_id=' + $postId.val(); 6356 } 6357 6358 // Generate the tab states. 6359 _.each( tabs, function( title, id ) { 6360 this.state( 'iframe:' + id ).set( _.defaults({ 6361 tab: id, 6362 src: tabUrl + '&tab=' + id, 6363 title: title, 6364 content: 'iframe', 6365 menu: 'default' 6366 }, options ) ); 6367 }, this ); 6368 6369 this.on( 'content:create:iframe', this.iframeContent, this ); 6370 this.on( 'content:deactivate:iframe', this.iframeContentCleanup, this ); 6371 this.on( 'menu:render:default', this.iframeMenu, this ); 6372 this.on( 'open', this.hijackThickbox, this ); 6373 this.on( 'close', this.restoreThickbox, this ); 6374 }, 6375 6376 /** 6377 * @param {Object} content 6378 * @this wp.media.controller.Region 6379 */ 6380 iframeContent: function( content ) { 6381 this.$el.addClass('hide-toolbar'); 6382 content.view = new wp.media.view.Iframe({ 6383 controller: this 6384 }); 6385 }, 6386 6387 iframeContentCleanup: function() { 6388 this.$el.removeClass('hide-toolbar'); 6389 }, 6390 6391 iframeMenu: function( view ) { 6392 var views = {}; 6393 6394 if ( ! view ) { 6395 return; 6396 } 6397 6398 _.each( wp.media.view.settings.tabs, function( title, id ) { 6399 views[ 'iframe:' + id ] = { 6400 text: this.state( 'iframe:' + id ).get('title'), 6401 priority: 200 6402 }; 6403 }, this ); 6404 6405 view.set( views ); 6406 }, 6407 6408 hijackThickbox: function() { 6409 var frame = this; 6410 6411 if ( ! window.tb_remove || this._tb_remove ) { 6412 return; 6413 } 6414 6415 this._tb_remove = window.tb_remove; 6416 window.tb_remove = function() { 6417 frame.close(); 6418 frame.reset(); 6419 frame.setState( frame.options.state ); 6420 frame._tb_remove.call( window ); 6421 }; 6422 }, 6423 6424 restoreThickbox: function() { 6425 if ( ! this._tb_remove ) { 6426 return; 6427 } 6428 6429 window.tb_remove = this._tb_remove; 6430 delete this._tb_remove; 6431 } 6432 }); 6433 6434 // Map some of the modal's methods to the frame. 6435 _.each(['open','close','attach','detach','escape'], function( method ) { 6436 /** 6437 * @returns {wp.media.view.MediaFrame} Returns itself to allow chaining 6438 */ 6439 MediaFrame.prototype[ method ] = function() { 6440 if ( this.modal ) { 6441 this.modal[ method ].apply( this.modal, arguments ); 6442 } 6443 return this; 6444 }; 6445 }); 6446 6447 module.exports = MediaFrame; 6448 6449 },{}],51:[function(require,module,exports){ 6450 /** 6451 * wp.media.view.MenuItem 6452 * 6453 * @class 6454 * @augments wp.media.View 6455 * @augments wp.Backbone.View 6456 * @augments Backbone.View 6457 */ 6458 var $ = jQuery, 6459 MenuItem; 6460 6461 MenuItem = wp.media.View.extend({ 6462 tagName: 'a', 6463 className: 'media-menu-item', 6464 6465 attributes: { 6466 href: '#' 6467 }, 6468 6469 events: { 6470 'click': '_click' 6471 }, 6472 /** 6473 * @param {Object} event 6474 */ 6475 _click: function( event ) { 6476 var clickOverride = this.options.click; 6477 6478 if ( event ) { 6479 event.preventDefault(); 6480 } 6481 6482 if ( clickOverride ) { 6483 clickOverride.call( this ); 6484 } else { 6485 this.click(); 6486 } 6487 6488 // When selecting a tab along the left side, 6489 // focus should be transferred into the main panel 6490 if ( ! wp.media.isTouchDevice ) { 6491 $('.media-frame-content input').first().focus(); 6492 } 6493 }, 6494 6495 click: function() { 6496 var state = this.options.state; 6497 6498 if ( state ) { 6499 this.controller.setState( state ); 6500 this.views.parent.$el.removeClass( 'visible' ); // TODO: or hide on any click, see below 6501 } 6502 }, 6503 /** 6504 * @returns {wp.media.view.MenuItem} returns itself to allow chaining 6505 */ 6506 render: function() { 6507 var options = this.options; 6508 6509 if ( options.text ) { 6510 this.$el.text( options.text ); 6511 } else if ( options.html ) { 6512 this.$el.html( options.html ); 6513 } 6514 6515 return this; 6516 } 6517 }); 6518 6519 module.exports = MenuItem; 6520 6521 },{}],52:[function(require,module,exports){ 6522 /** 6523 * wp.media.view.Menu 6524 * 6525 * @class 6526 * @augments wp.media.view.PriorityList 6527 * @augments wp.media.View 6528 * @augments wp.Backbone.View 6529 * @augments Backbone.View 6530 */ 6531 var MenuItem = wp.media.view.MenuItem, 6532 PriorityList = wp.media.view.PriorityList, 6533 Menu; 6534 6535 Menu = PriorityList.extend({ 6536 tagName: 'div', 6537 className: 'media-menu', 6538 property: 'state', 6539 ItemView: MenuItem, 6540 region: 'menu', 6541 6542 /* TODO: alternatively hide on any click anywhere 6543 events: { 6544 'click': 'click' 6545 }, 6546 6547 click: function() { 6548 this.$el.removeClass( 'visible' ); 6549 }, 6550 */ 6551 6552 /** 6553 * @param {Object} options 6554 * @param {string} id 6555 * @returns {wp.media.View} 6556 */ 6557 toView: function( options, id ) { 6558 options = options || {}; 6559 options[ this.property ] = options[ this.property ] || id; 6560 return new this.ItemView( options ).render(); 6561 }, 6562 6563 ready: function() { 6564 /** 6565 * call 'ready' directly on the parent class 6566 */ 6567 PriorityList.prototype.ready.apply( this, arguments ); 6568 this.visibility(); 6569 }, 6570 6571 set: function() { 6572 /** 6573 * call 'set' directly on the parent class 6574 */ 6575 PriorityList.prototype.set.apply( this, arguments ); 6576 this.visibility(); 6577 }, 6578 6579 unset: function() { 6580 /** 6581 * call 'unset' directly on the parent class 6582 */ 6583 PriorityList.prototype.unset.apply( this, arguments ); 6584 this.visibility(); 6585 }, 6586 6587 visibility: function() { 6588 var region = this.region, 6589 view = this.controller[ region ].get(), 6590 views = this.views.get(), 6591 hide = ! views || views.length < 2; 6592 6593 if ( this === view ) { 6594 this.controller.$el.toggleClass( 'hide-' + region, hide ); 6595 } 6596 }, 6597 /** 6598 * @param {string} id 6599 */ 6600 select: function( id ) { 6601 var view = this.get( id ); 6602 6603 if ( ! view ) { 6604 return; 6605 } 6606 6607 this.deselect(); 6608 view.$el.addClass('active'); 6609 }, 6610 6611 deselect: function() { 6612 this.$el.children().removeClass('active'); 6613 }, 6614 6615 hide: function( id ) { 6616 var view = this.get( id ); 6617 6618 if ( ! view ) { 6619 return; 6620 } 6621 6622 view.$el.addClass('hidden'); 6623 }, 6624 6625 show: function( id ) { 6626 var view = this.get( id ); 6627 6628 if ( ! view ) { 6629 return; 6630 } 6631 6632 view.$el.removeClass('hidden'); 6633 } 6634 }); 6635 6636 module.exports = Menu; 6637 6638 },{}],53:[function(require,module,exports){ 6639 /** 6640 * wp.media.view.Modal 6641 * 6642 * A modal view, which the media modal uses as its default container. 6643 * 6644 * @class 6645 * @augments wp.media.View 6646 * @augments wp.Backbone.View 6647 * @augments Backbone.View 6648 */ 6649 var $ = jQuery, 6650 Modal; 6651 6652 Modal = wp.media.View.extend({ 6653 tagName: 'div', 6654 template: wp.template('media-modal'), 6655 6656 attributes: { 6657 tabindex: 0 6658 }, 6659 6660 events: { 6661 'click .media-modal-backdrop, .media-modal-close': 'escapeHandler', 6662 'keydown': 'keydown' 6663 }, 6664 6665 initialize: function() { 6666 _.defaults( this.options, { 6667 container: document.body, 6668 title: '', 6669 propagate: true, 6670 freeze: true 6671 }); 6672 6673 this.focusManager = new wp.media.view.FocusManager({ 6674 el: this.el 6675 }); 6676 }, 6677 /** 6678 * @returns {Object} 6679 */ 6680 prepare: function() { 6681 return { 6682 title: this.options.title 6683 }; 6684 }, 6685 6686 /** 6687 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6688 */ 6689 attach: function() { 6690 if ( this.views.attached ) { 6691 return this; 6692 } 6693 6694 if ( ! this.views.rendered ) { 6695 this.render(); 6696 } 6697 6698 this.$el.appendTo( this.options.container ); 6699 6700 // Manually mark the view as attached and trigger ready. 6701 this.views.attached = true; 6702 this.views.ready(); 6703 6704 return this.propagate('attach'); 6705 }, 6706 6707 /** 6708 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6709 */ 6710 detach: function() { 6711 if ( this.$el.is(':visible') ) { 6712 this.close(); 6713 } 6714 6715 this.$el.detach(); 6716 this.views.attached = false; 6717 return this.propagate('detach'); 6718 }, 6719 6720 /** 6721 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6722 */ 6723 open: function() { 6724 var $el = this.$el, 6725 options = this.options, 6726 mceEditor; 6727 6728 if ( $el.is(':visible') ) { 6729 return this; 6730 } 6731 6732 if ( ! this.views.attached ) { 6733 this.attach(); 6734 } 6735 6736 // If the `freeze` option is set, record the window's scroll position. 6737 if ( options.freeze ) { 6738 this._freeze = { 6739 scrollTop: $( window ).scrollTop() 6740 }; 6741 } 6742 6743 // Disable page scrolling. 6744 $( 'body' ).addClass( 'modal-open' ); 6745 6746 $el.show(); 6747 6748 // Try to close the onscreen keyboard 6749 if ( 'ontouchend' in document ) { 6750 if ( ( mceEditor = window.tinymce && window.tinymce.activeEditor ) && ! mceEditor.isHidden() && mceEditor.iframeElement ) { 6751 mceEditor.iframeElement.focus(); 6752 mceEditor.iframeElement.blur(); 6753 6754 setTimeout( function() { 6755 mceEditor.iframeElement.blur(); 6756 }, 100 ); 6757 } 6758 } 6759 6760 this.$el.focus(); 6761 6762 return this.propagate('open'); 6763 }, 6764 6765 /** 6766 * @param {Object} options 6767 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6768 */ 6769 close: function( options ) { 6770 var freeze = this._freeze; 6771 6772 if ( ! this.views.attached || ! this.$el.is(':visible') ) { 6773 return this; 6774 } 6775 6776 // Enable page scrolling. 6777 $( 'body' ).removeClass( 'modal-open' ); 6778 6779 // Hide modal and remove restricted media modal tab focus once it's closed 6780 this.$el.hide().undelegate( 'keydown' ); 6781 6782 // Put focus back in useful location once modal is closed 6783 $('#wpbody-content').focus(); 6784 6785 this.propagate('close'); 6786 6787 // If the `freeze` option is set, restore the container's scroll position. 6788 if ( freeze ) { 6789 $( window ).scrollTop( freeze.scrollTop ); 6790 } 6791 6792 if ( options && options.escape ) { 6793 this.propagate('escape'); 6794 } 6795 6796 return this; 6797 }, 6798 /** 6799 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6800 */ 6801 escape: function() { 6802 return this.close({ escape: true }); 6803 }, 6804 /** 6805 * @param {Object} event 6806 */ 6807 escapeHandler: function( event ) { 6808 event.preventDefault(); 6809 this.escape(); 6810 }, 6811 6812 /** 6813 * @param {Array|Object} content Views to register to '.media-modal-content' 6814 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6815 */ 6816 content: function( content ) { 6817 this.views.set( '.media-modal-content', content ); 6818 return this; 6819 }, 6820 6821 /** 6822 * Triggers a modal event and if the `propagate` option is set, 6823 * forwards events to the modal's controller. 6824 * 6825 * @param {string} id 6826 * @returns {wp.media.view.Modal} Returns itself to allow chaining 6827 */ 6828 propagate: function( id ) { 6829 this.trigger( id ); 6830 6831 if ( this.options.propagate ) { 6832 this.controller.trigger( id ); 6833 } 6834 6835 return this; 6836 }, 6837 /** 6838 * @param {Object} event 6839 */ 6840 keydown: function( event ) { 6841 // Close the modal when escape is pressed. 6842 if ( 27 === event.which && this.$el.is(':visible') ) { 6843 this.escape(); 6844 event.stopImmediatePropagation(); 6845 } 6846 } 6847 }); 6848 6849 module.exports = Modal; 6850 6851 },{}],54:[function(require,module,exports){ 6852 /** 6853 * wp.media.view.PriorityList 6854 * 6855 * @class 6856 * @augments wp.media.View 6857 * @augments wp.Backbone.View 6858 * @augments Backbone.View 6859 */ 6860 var PriorityList = wp.media.View.extend({ 6861 tagName: 'div', 6862 6863 initialize: function() { 6864 this._views = {}; 6865 6866 this.set( _.extend( {}, this._views, this.options.views ), { silent: true }); 6867 delete this.options.views; 6868 6869 if ( ! this.options.silent ) { 6870 this.render(); 6871 } 6872 }, 6873 /** 6874 * @param {string} id 6875 * @param {wp.media.View|Object} view 6876 * @param {Object} options 6877 * @returns {wp.media.view.PriorityList} Returns itself to allow chaining 6878 */ 6879 set: function( id, view, options ) { 6880 var priority, views, index; 6881 6882 options = options || {}; 6883 6884 // Accept an object with an `id` : `view` mapping. 6885 if ( _.isObject( id ) ) { 6886 _.each( id, function( view, id ) { 6887 this.set( id, view ); 6888 }, this ); 6889 return this; 6890 } 6891 6892 if ( ! (view instanceof Backbone.View) ) { 6893 view = this.toView( view, id, options ); 6894 } 6895 view.controller = view.controller || this.controller; 6896 6897 this.unset( id ); 6898 6899 priority = view.options.priority || 10; 6900 views = this.views.get() || []; 6901 6902 _.find( views, function( existing, i ) { 6903 if ( existing.options.priority > priority ) { 6904 index = i; 6905 return true; 6906 } 6907 }); 6908 6909 this._views[ id ] = view; 6910 this.views.add( view, { 6911 at: _.isNumber( index ) ? index : views.length || 0 6912 }); 6913 6914 return this; 6915 }, 6916 /** 6917 * @param {string} id 6918 * @returns {wp.media.View} 6919 */ 6920 get: function( id ) { 6921 return this._views[ id ]; 6922 }, 6923 /** 6924 * @param {string} id 6925 * @returns {wp.media.view.PriorityList} 6926 */ 6927 unset: function( id ) { 6928 var view = this.get( id ); 6929 6930 if ( view ) { 6931 view.remove(); 6932 } 6933 6934 delete this._views[ id ]; 6935 return this; 6936 }, 6937 /** 6938 * @param {Object} options 6939 * @returns {wp.media.View} 6940 */ 6941 toView: function( options ) { 6942 return new wp.media.View( options ); 6943 } 6944 }); 6945 6946 module.exports = PriorityList; 6947 6948 },{}],55:[function(require,module,exports){ 6949 /** 6950 * wp.media.view.RouterItem 6951 * 6952 * @class 6953 * @augments wp.media.view.MenuItem 6954 * @augments wp.media.View 6955 * @augments wp.Backbone.View 6956 * @augments Backbone.View 6957 */ 6958 var RouterItem = wp.media.view.MenuItem.extend({ 6959 /** 6960 * On click handler to activate the content region's corresponding mode. 6961 */ 6962 click: function() { 6963 var contentMode = this.options.contentMode; 6964 if ( contentMode ) { 6965 this.controller.content.mode( contentMode ); 6966 } 6967 } 6968 }); 6969 6970 module.exports = RouterItem; 6971 6972 },{}],56:[function(require,module,exports){ 6973 /** 6974 * wp.media.view.Router 6975 * 6976 * @class 6977 * @augments wp.media.view.Menu 6978 * @augments wp.media.view.PriorityList 6979 * @augments wp.media.View 6980 * @augments wp.Backbone.View 6981 * @augments Backbone.View 6982 */ 6983 var Menu = wp.media.view.Menu, 6984 Router; 6985 6986 Router = Menu.extend({ 6987 tagName: 'div', 6988 className: 'media-router', 6989 property: 'contentMode', 6990 ItemView: wp.media.view.RouterItem, 6991 region: 'router', 6992 6993 initialize: function() { 6994 this.controller.on( 'content:render', this.update, this ); 6995 // Call 'initialize' directly on the parent class. 6996 Menu.prototype.initialize.apply( this, arguments ); 6997 }, 6998 6999 update: function() { 7000 var mode = this.controller.content.mode(); 7001 if ( mode ) { 7002 this.select( mode ); 7003 } 7004 } 7005 }); 7006 7007 module.exports = Router; 7008 7009 },{}],57:[function(require,module,exports){ 7010 /** 7011 * wp.media.view.Search 7012 * 7013 * @class 7014 * @augments wp.media.View 7015 * @augments wp.Backbone.View 7016 * @augments Backbone.View 7017 */ 7018 var l10n = wp.media.view.l10n, 7019 Search; 7020 7021 Search = wp.media.View.extend({ 7022 tagName: 'input', 7023 className: 'search', 7024 id: 'media-search-input', 7025 7026 attributes: { 7027 type: 'search', 7028 placeholder: l10n.search 7029 }, 7030 7031 events: { 7032 'input': 'search', 7033 'keyup': 'search', 7034 'change': 'search', 7035 'search': 'search' 7036 }, 7037 7038 /** 7039 * @returns {wp.media.view.Search} Returns itself to allow chaining 7040 */ 7041 render: function() { 7042 this.el.value = this.model.escape('search'); 7043 return this; 7044 }, 7045 7046 search: function( event ) { 7047 if ( event.target.value ) { 7048 this.model.set( 'search', event.target.value ); 7049 } else { 7050 this.model.unset('search'); 7051 } 7052 } 7053 }); 7054 7055 module.exports = Search; 7056 7057 },{}],58:[function(require,module,exports){ 7058 /** 7059 * wp.media.view.Selection 7060 * 7061 * @class 7062 * @augments wp.media.View 7063 * @augments wp.Backbone.View 7064 * @augments Backbone.View 7065 */ 7066 var l10n = wp.media.view.l10n, 7067 Selection; 7068 7069 Selection = wp.media.View.extend({ 7070 tagName: 'div', 7071 className: 'media-selection', 7072 template: wp.template('media-selection'), 7073 7074 events: { 7075 'click .edit-selection': 'edit', 7076 'click .clear-selection': 'clear' 7077 }, 7078 7079 initialize: function() { 7080 _.defaults( this.options, { 7081 editable: false, 7082 clearable: true 7083 }); 7084 7085 /** 7086 * @member {wp.media.view.Attachments.Selection} 7087 */ 7088 this.attachments = new wp.media.view.Attachments.Selection({ 7089 controller: this.controller, 7090 collection: this.collection, 7091 selection: this.collection, 7092 model: new Backbone.Model() 7093 }); 7094 7095 this.views.set( '.selection-view', this.attachments ); 7096 this.collection.on( 'add remove reset', this.refresh, this ); 7097 this.controller.on( 'content:activate', this.refresh, this ); 7098 }, 7099 7100 ready: function() { 7101 this.refresh(); 7102 }, 7103 7104 refresh: function() { 7105 // If the selection hasn't been rendered, bail. 7106 if ( ! this.$el.children().length ) { 7107 return; 7108 } 7109 7110 var collection = this.collection, 7111 editing = 'edit-selection' === this.controller.content.mode(); 7112 7113 // If nothing is selected, display nothing. 7114 this.$el.toggleClass( 'empty', ! collection.length ); 7115 this.$el.toggleClass( 'one', 1 === collection.length ); 7116 this.$el.toggleClass( 'editing', editing ); 7117 7118 this.$('.count').text( l10n.selected.replace('%d', collection.length) ); 7119 }, 7120 7121 edit: function( event ) { 7122 event.preventDefault(); 7123 if ( this.options.editable ) { 7124 this.options.editable.call( this, this.collection ); 7125 } 7126 }, 7127 7128 clear: function( event ) { 7129 event.preventDefault(); 7130 this.collection.reset(); 7131 7132 // Keep focus inside media modal 7133 // after clear link is selected 7134 this.controller.modal.focusManager.focus(); 7135 } 7136 }); 7137 7138 module.exports = Selection; 7139 7140 },{}],59:[function(require,module,exports){ 7141 /** 7142 * wp.media.view.Settings 8589 7143 * 8590 7144 * @class … … 8594 7148 */ 8595 7149 var View = wp.media.View, 8596 UploaderStatus = wp.media.view.UploaderStatus, 8597 l10n = wp.media.view.l10n, 8598 $ = jQuery, 8599 Cropper; 8600 8601 Cropper = View.extend({ 8602 className: 'crop-content', 8603 template: wp.template('crop-content'), 7150 $ = Backbone.$, 7151 Settings; 7152 7153 Settings = View.extend({ 7154 events: { 7155 'click button': 'updateHandler', 7156 'change input': 'updateHandler', 7157 'change select': 'updateHandler', 7158 'change textarea': 'updateHandler' 7159 }, 7160 8604 7161 initialize: function() { 8605 _.bindAll(this, 'onImageLoad'); 8606 }, 8607 ready: function() { 8608 this.controller.frame.on('content:error:crop', this.onError, this); 8609 this.$image = this.$el.find('.crop-image'); 8610 this.$image.on('load', this.onImageLoad); 8611 $(window).on('resize.cropper', _.debounce(this.onImageLoad, 250)); 8612 }, 8613 remove: function() { 8614 $(window).off('resize.cropper'); 8615 this.$el.remove(); 8616 this.$el.off(); 8617 View.prototype.remove.apply(this, arguments); 8618 }, 7162 this.model = this.model || new Backbone.Model(); 7163 this.listenTo( this.model, 'change', this.updateChanges ); 7164 }, 7165 8619 7166 prepare: function() { 8620 return { 8621 title: l10n.cropYourImage, 8622 url: this.options.attachment.get('url') 8623 }; 8624 }, 8625 onImageLoad: function() { 8626 var imgOptions = this.controller.get('imgSelectOptions'); 8627 if (typeof imgOptions === 'function') { 8628 imgOptions = imgOptions(this.options.attachment, this.controller); 8629 } 8630 8631 imgOptions = _.extend(imgOptions, {parent: this.$el}); 8632 this.trigger('image-loaded'); 8633 this.controller.imgSelect = this.$image.imgAreaSelect(imgOptions); 8634 }, 8635 onError: function() { 8636 var filename = this.options.attachment.get('filename'); 8637 8638 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({ 8639 filename: UploaderStatus.prototype.filename(filename), 8640 message: window._wpMediaViewsL10n.cropError 8641 }), { at: 0 }); 7167 return _.defaults({ 7168 model: this.model.toJSON() 7169 }, this.options ); 7170 }, 7171 /** 7172 * @returns {wp.media.view.Settings} Returns itself to allow chaining 7173 */ 7174 render: function() { 7175 View.prototype.render.apply( this, arguments ); 7176 // Select the correct values. 7177 _( this.model.attributes ).chain().keys().each( this.update, this ); 7178 return this; 7179 }, 7180 /** 7181 * @param {string} key 7182 */ 7183 update: function( key ) { 7184 var value = this.model.get( key ), 7185 $setting = this.$('[data-setting="' + key + '"]'), 7186 $buttons, $value; 7187 7188 // Bail if we didn't find a matching setting. 7189 if ( ! $setting.length ) { 7190 return; 7191 } 7192 7193 // Attempt to determine how the setting is rendered and update 7194 // the selected value. 7195 7196 // Handle dropdowns. 7197 if ( $setting.is('select') ) { 7198 $value = $setting.find('[value="' + value + '"]'); 7199 7200 if ( $value.length ) { 7201 $setting.find('option').prop( 'selected', false ); 7202 $value.prop( 'selected', true ); 7203 } else { 7204 // If we can't find the desired value, record what *is* selected. 7205 this.model.set( key, $setting.find(':selected').val() ); 7206 } 7207 7208 // Handle button groups. 7209 } else if ( $setting.hasClass('button-group') ) { 7210 $buttons = $setting.find('button').removeClass('active'); 7211 $buttons.filter( '[value="' + value + '"]' ).addClass('active'); 7212 7213 // Handle text inputs and textareas. 7214 } else if ( $setting.is('input[type="text"], textarea') ) { 7215 if ( ! $setting.is(':focus') ) { 7216 $setting.val( value ); 7217 } 7218 // Handle checkboxes. 7219 } else if ( $setting.is('input[type="checkbox"]') ) { 7220 $setting.prop( 'checked', !! value && 'false' !== value ); 7221 } 7222 }, 7223 /** 7224 * @param {Object} event 7225 */ 7226 updateHandler: function( event ) { 7227 var $setting = $( event.target ).closest('[data-setting]'), 7228 value = event.target.value, 7229 userSetting; 7230 7231 event.preventDefault(); 7232 7233 if ( ! $setting.length ) { 7234 return; 7235 } 7236 7237 // Use the correct value for checkboxes. 7238 if ( $setting.is('input[type="checkbox"]') ) { 7239 value = $setting[0].checked; 7240 } 7241 7242 // Update the corresponding setting. 7243 this.model.set( $setting.data('setting'), value ); 7244 7245 // If the setting has a corresponding user setting, 7246 // update that as well. 7247 if ( userSetting = $setting.data('userSetting') ) { 7248 window.setUserSetting( userSetting, value ); 7249 } 7250 }, 7251 7252 updateChanges: function( model ) { 7253 if ( model.hasChanged() ) { 7254 _( model.changed ).chain().keys().each( this.update, this ); 7255 } 8642 7256 } 8643 7257 }); 8644 7258 8645 module.exports = Cropper; 8646 8647 8648 /***/ }), 8649 /* 97 */ 8650 /***/ (function(module, exports) { 8651 7259 module.exports = Settings; 7260 7261 },{}],60:[function(require,module,exports){ 7262 /** 7263 * wp.media.view.Settings.AttachmentDisplay 7264 * 7265 * @class 7266 * @augments wp.media.view.Settings 7267 * @augments wp.media.View 7268 * @augments wp.Backbone.View 7269 * @augments Backbone.View 7270 */ 7271 var Settings = wp.media.view.Settings, 7272 AttachmentDisplay; 7273 7274 AttachmentDisplay = Settings.extend({ 7275 className: 'attachment-display-settings', 7276 template: wp.template('attachment-display-settings'), 7277 7278 initialize: function() { 7279 var attachment = this.options.attachment; 7280 7281 _.defaults( this.options, { 7282 userSettings: false 7283 }); 7284 // Call 'initialize' directly on the parent class. 7285 Settings.prototype.initialize.apply( this, arguments ); 7286 this.listenTo( this.model, 'change:link', this.updateLinkTo ); 7287 7288 if ( attachment ) { 7289 attachment.on( 'change:uploading', this.render, this ); 7290 } 7291 }, 7292 7293 dispose: function() { 7294 var attachment = this.options.attachment; 7295 if ( attachment ) { 7296 attachment.off( null, null, this ); 7297 } 7298 /** 7299 * call 'dispose' directly on the parent class 7300 */ 7301 Settings.prototype.dispose.apply( this, arguments ); 7302 }, 7303 /** 7304 * @returns {wp.media.view.AttachmentDisplay} Returns itself to allow chaining 7305 */ 7306 render: function() { 7307 var attachment = this.options.attachment; 7308 if ( attachment ) { 7309 _.extend( this.options, { 7310 sizes: attachment.get('sizes'), 7311 type: attachment.get('type') 7312 }); 7313 } 7314 /** 7315 * call 'render' directly on the parent class 7316 */ 7317 Settings.prototype.render.call( this ); 7318 this.updateLinkTo(); 7319 return this; 7320 }, 7321 7322 updateLinkTo: function() { 7323 var linkTo = this.model.get('link'), 7324 $input = this.$('.link-to-custom'), 7325 attachment = this.options.attachment; 7326 7327 if ( 'none' === linkTo || 'embed' === linkTo || ( ! attachment && 'custom' !== linkTo ) ) { 7328 $input.addClass( 'hidden' ); 7329 return; 7330 } 7331 7332 if ( attachment ) { 7333 if ( 'post' === linkTo ) { 7334 $input.val( attachment.get('link') ); 7335 } else if ( 'file' === linkTo ) { 7336 $input.val( attachment.get('url') ); 7337 } else if ( ! this.model.get('linkUrl') ) { 7338 $input.val('http://'); 7339 } 7340 7341 $input.prop( 'readonly', 'custom' !== linkTo ); 7342 } 7343 7344 $input.removeClass( 'hidden' ); 7345 7346 // If the input is visible, focus and select its contents. 7347 if ( ! wp.media.isTouchDevice && $input.is(':visible') ) { 7348 $input.focus()[0].select(); 7349 } 7350 } 7351 }); 7352 7353 module.exports = AttachmentDisplay; 7354 7355 },{}],61:[function(require,module,exports){ 7356 /** 7357 * wp.media.view.Settings.Gallery 7358 * 7359 * @class 7360 * @augments wp.media.view.Settings 7361 * @augments wp.media.View 7362 * @augments wp.Backbone.View 7363 * @augments Backbone.View 7364 */ 7365 var Gallery = wp.media.view.Settings.extend({ 7366 className: 'collection-settings gallery-settings', 7367 template: wp.template('gallery-settings') 7368 }); 7369 7370 module.exports = Gallery; 7371 7372 },{}],62:[function(require,module,exports){ 7373 /** 7374 * wp.media.view.Settings.Playlist 7375 * 7376 * @class 7377 * @augments wp.media.view.Settings 7378 * @augments wp.media.View 7379 * @augments wp.Backbone.View 7380 * @augments Backbone.View 7381 */ 7382 var Playlist = wp.media.view.Settings.extend({ 7383 className: 'collection-settings playlist-settings', 7384 template: wp.template('playlist-settings') 7385 }); 7386 7387 module.exports = Playlist; 7388 7389 },{}],63:[function(require,module,exports){ 7390 /** 7391 * wp.media.view.Sidebar 7392 * 7393 * @class 7394 * @augments wp.media.view.PriorityList 7395 * @augments wp.media.View 7396 * @augments wp.Backbone.View 7397 * @augments Backbone.View 7398 */ 7399 var Sidebar = wp.media.view.PriorityList.extend({ 7400 className: 'media-sidebar' 7401 }); 7402 7403 module.exports = Sidebar; 7404 7405 },{}],64:[function(require,module,exports){ 8652 7406 /** 8653 7407 * wp.media.view.SiteIconCropper … … 8692 7446 module.exports = SiteIconCropper; 8693 7447 8694 8695 /***/ }), 8696 /* 98 */ 8697 /***/ (function(module, exports) { 8698 7448 },{}],65:[function(require,module,exports){ 8699 7449 /** 8700 7450 * wp.media.view.SiteIconPreview … … 8752 7502 module.exports = SiteIconPreview; 8753 7503 8754 8755 /***/ }), 8756 /* 99 */ 8757 /***/ (function(module, exports) { 8758 8759 /** 8760 * wp.media.view.EditImage 8761 * 8762 * @class 8763 * @augments wp.media.View 8764 * @augments wp.Backbone.View 8765 * @augments Backbone.View 8766 */ 8767 var View = wp.media.View, 8768 EditImage; 8769 8770 EditImage = View.extend({ 8771 className: 'image-editor', 8772 template: wp.template('image-editor'), 8773 8774 initialize: function( options ) { 8775 this.editor = window.imageEdit; 8776 this.controller = options.controller; 8777 View.prototype.initialize.apply( this, arguments ); 8778 }, 8779 8780 prepare: function() { 8781 return this.model.toJSON(); 8782 }, 8783 8784 loadEditor: function() { 8785 var dfd = this.editor.open( this.model.get('id'), this.model.get('nonces').edit, this ); 8786 dfd.done( _.bind( this.focus, this ) ); 8787 }, 8788 8789 focus: function() { 8790 this.$( '.imgedit-submit .button' ).eq( 0 ).focus(); 8791 }, 8792 8793 back: function() { 8794 var lastState = this.controller.lastState(); 8795 this.controller.setState( lastState ); 8796 }, 8797 8798 refresh: function() { 8799 this.model.fetch(); 8800 }, 8801 8802 save: function() { 8803 var lastState = this.controller.lastState(); 8804 8805 this.model.fetch().done( _.bind( function() { 8806 this.controller.setState( lastState ); 8807 }, this ) ); 8808 } 8809 8810 }); 8811 8812 module.exports = EditImage; 8813 8814 8815 /***/ }), 8816 /* 100 */ 8817 /***/ (function(module, exports) { 8818 7504 },{}],66:[function(require,module,exports){ 8819 7505 /** 8820 7506 * wp.media.view.Spinner … … 8851 7537 module.exports = Spinner; 8852 7538 8853 8854 /***/ }) 8855 /******/ ])); 7539 },{}],67:[function(require,module,exports){ 7540 /** 7541 * wp.media.view.Toolbar 7542 * 7543 * A toolbar which consists of a primary and a secondary section. Each sections 7544 * can be filled with views. 7545 * 7546 * @class 7547 * @augments wp.media.View 7548 * @augments wp.Backbone.View 7549 * @augments Backbone.View 7550 */ 7551 var View = wp.media.View, 7552 Toolbar; 7553 7554 Toolbar = View.extend({ 7555 tagName: 'div', 7556 className: 'media-toolbar', 7557 7558 initialize: function() { 7559 var state = this.controller.state(), 7560 selection = this.selection = state.get('selection'), 7561 library = this.library = state.get('library'); 7562 7563 this._views = {}; 7564 7565 // The toolbar is composed of two `PriorityList` views. 7566 this.primary = new wp.media.view.PriorityList(); 7567 this.secondary = new wp.media.view.PriorityList(); 7568 this.primary.$el.addClass('media-toolbar-primary search-form'); 7569 this.secondary.$el.addClass('media-toolbar-secondary'); 7570 7571 this.views.set([ this.secondary, this.primary ]); 7572 7573 if ( this.options.items ) { 7574 this.set( this.options.items, { silent: true }); 7575 } 7576 7577 if ( ! this.options.silent ) { 7578 this.render(); 7579 } 7580 7581 if ( selection ) { 7582 selection.on( 'add remove reset', this.refresh, this ); 7583 } 7584 7585 if ( library ) { 7586 library.on( 'add remove reset', this.refresh, this ); 7587 } 7588 }, 7589 /** 7590 * @returns {wp.media.view.Toolbar} Returns itsef to allow chaining 7591 */ 7592 dispose: function() { 7593 if ( this.selection ) { 7594 this.selection.off( null, null, this ); 7595 } 7596 7597 if ( this.library ) { 7598 this.library.off( null, null, this ); 7599 } 7600 /** 7601 * call 'dispose' directly on the parent class 7602 */ 7603 return View.prototype.dispose.apply( this, arguments ); 7604 }, 7605 7606 ready: function() { 7607 this.refresh(); 7608 }, 7609 7610 /** 7611 * @param {string} id 7612 * @param {Backbone.View|Object} view 7613 * @param {Object} [options={}] 7614 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining 7615 */ 7616 set: function( id, view, options ) { 7617 var list; 7618 options = options || {}; 7619 7620 // Accept an object with an `id` : `view` mapping. 7621 if ( _.isObject( id ) ) { 7622 _.each( id, function( view, id ) { 7623 this.set( id, view, { silent: true }); 7624 }, this ); 7625 7626 } else { 7627 if ( ! ( view instanceof Backbone.View ) ) { 7628 view.classes = [ 'media-button-' + id ].concat( view.classes || [] ); 7629 view = new wp.media.view.Button( view ).render(); 7630 } 7631 7632 view.controller = view.controller || this.controller; 7633 7634 this._views[ id ] = view; 7635 7636 list = view.options.priority < 0 ? 'secondary' : 'primary'; 7637 this[ list ].set( id, view, options ); 7638 } 7639 7640 if ( ! options.silent ) { 7641 this.refresh(); 7642 } 7643 7644 return this; 7645 }, 7646 /** 7647 * @param {string} id 7648 * @returns {wp.media.view.Button} 7649 */ 7650 get: function( id ) { 7651 return this._views[ id ]; 7652 }, 7653 /** 7654 * @param {string} id 7655 * @param {Object} options 7656 * @returns {wp.media.view.Toolbar} Returns itself to allow chaining 7657 */ 7658 unset: function( id, options ) { 7659 delete this._views[ id ]; 7660 this.primary.unset( id, options ); 7661 this.secondary.unset( id, options ); 7662 7663 if ( ! options || ! options.silent ) { 7664 this.refresh(); 7665 } 7666 return this; 7667 }, 7668 7669 refresh: function() { 7670 var state = this.controller.state(), 7671 library = state.get('library'), 7672 selection = state.get('selection'); 7673 7674 _.each( this._views, function( button ) { 7675 if ( ! button.model || ! button.options || ! button.options.requires ) { 7676 return; 7677 } 7678 7679 var requires = button.options.requires, 7680 disabled = false; 7681 7682 // Prevent insertion of attachments if any of them are still uploading 7683 disabled = _.some( selection.models, function( attachment ) { 7684 return attachment.get('uploading') === true; 7685 }); 7686 7687 if ( requires.selection && selection && ! selection.length ) { 7688 disabled = true; 7689 } else if ( requires.library && library && ! library.length ) { 7690 disabled = true; 7691 } 7692 button.model.set( 'disabled', disabled ); 7693 }); 7694 } 7695 }); 7696 7697 module.exports = Toolbar; 7698 7699 },{}],68:[function(require,module,exports){ 7700 /** 7701 * wp.media.view.Toolbar.Embed 7702 * 7703 * @class 7704 * @augments wp.media.view.Toolbar.Select 7705 * @augments wp.media.view.Toolbar 7706 * @augments wp.media.View 7707 * @augments wp.Backbone.View 7708 * @augments Backbone.View 7709 */ 7710 var Select = wp.media.view.Toolbar.Select, 7711 l10n = wp.media.view.l10n, 7712 Embed; 7713 7714 Embed = Select.extend({ 7715 initialize: function() { 7716 _.defaults( this.options, { 7717 text: l10n.insertIntoPost, 7718 requires: false 7719 }); 7720 // Call 'initialize' directly on the parent class. 7721 Select.prototype.initialize.apply( this, arguments ); 7722 }, 7723 7724 refresh: function() { 7725 var url = this.controller.state().props.get('url'); 7726 this.get('select').model.set( 'disabled', ! url || url === 'http://' ); 7727 /** 7728 * call 'refresh' directly on the parent class 7729 */ 7730 Select.prototype.refresh.apply( this, arguments ); 7731 } 7732 }); 7733 7734 module.exports = Embed; 7735 7736 },{}],69:[function(require,module,exports){ 7737 /** 7738 * wp.media.view.Toolbar.Select 7739 * 7740 * @class 7741 * @augments wp.media.view.Toolbar 7742 * @augments wp.media.View 7743 * @augments wp.Backbone.View 7744 * @augments Backbone.View 7745 */ 7746 var Toolbar = wp.media.view.Toolbar, 7747 l10n = wp.media.view.l10n, 7748 Select; 7749 7750 Select = Toolbar.extend({ 7751 initialize: function() { 7752 var options = this.options; 7753 7754 _.bindAll( this, 'clickSelect' ); 7755 7756 _.defaults( options, { 7757 event: 'select', 7758 state: false, 7759 reset: true, 7760 close: true, 7761 text: l10n.select, 7762 7763 // Does the button rely on the selection? 7764 requires: { 7765 selection: true 7766 } 7767 }); 7768 7769 options.items = _.defaults( options.items || {}, { 7770 select: { 7771 style: 'primary', 7772 text: options.text, 7773 priority: 80, 7774 click: this.clickSelect, 7775 requires: options.requires 7776 } 7777 }); 7778 // Call 'initialize' directly on the parent class. 7779 Toolbar.prototype.initialize.apply( this, arguments ); 7780 }, 7781 7782 clickSelect: function() { 7783 var options = this.options, 7784 controller = this.controller; 7785 7786 if ( options.close ) { 7787 controller.close(); 7788 } 7789 7790 if ( options.event ) { 7791 controller.state().trigger( options.event ); 7792 } 7793 7794 if ( options.state ) { 7795 controller.setState( options.state ); 7796 } 7797 7798 if ( options.reset ) { 7799 controller.reset(); 7800 } 7801 } 7802 }); 7803 7804 module.exports = Select; 7805 7806 },{}],70:[function(require,module,exports){ 7807 /** 7808 * Creates a dropzone on WP editor instances (elements with .wp-editor-wrap) 7809 * and relays drag'n'dropped files to a media workflow. 7810 * 7811 * wp.media.view.EditorUploader 7812 * 7813 * @class 7814 * @augments wp.media.View 7815 * @augments wp.Backbone.View 7816 * @augments Backbone.View 7817 */ 7818 var View = wp.media.View, 7819 l10n = wp.media.view.l10n, 7820 $ = jQuery, 7821 EditorUploader; 7822 7823 EditorUploader = View.extend({ 7824 tagName: 'div', 7825 className: 'uploader-editor', 7826 template: wp.template( 'uploader-editor' ), 7827 7828 localDrag: false, 7829 overContainer: false, 7830 overDropzone: false, 7831 draggingFile: null, 7832 7833 /** 7834 * Bind drag'n'drop events to callbacks. 7835 */ 7836 initialize: function() { 7837 this.initialized = false; 7838 7839 // Bail if not enabled or UA does not support drag'n'drop or File API. 7840 if ( ! window.tinyMCEPreInit || ! window.tinyMCEPreInit.dragDropUpload || ! this.browserSupport() ) { 7841 return this; 7842 } 7843 7844 this.$document = $(document); 7845 this.dropzones = []; 7846 this.files = []; 7847 7848 this.$document.on( 'drop', '.uploader-editor', _.bind( this.drop, this ) ); 7849 this.$document.on( 'dragover', '.uploader-editor', _.bind( this.dropzoneDragover, this ) ); 7850 this.$document.on( 'dragleave', '.uploader-editor', _.bind( this.dropzoneDragleave, this ) ); 7851 this.$document.on( 'click', '.uploader-editor', _.bind( this.click, this ) ); 7852 7853 this.$document.on( 'dragover', _.bind( this.containerDragover, this ) ); 7854 this.$document.on( 'dragleave', _.bind( this.containerDragleave, this ) ); 7855 7856 this.$document.on( 'dragstart dragend drop', _.bind( function( event ) { 7857 this.localDrag = event.type === 'dragstart'; 7858 }, this ) ); 7859 7860 this.initialized = true; 7861 return this; 7862 }, 7863 7864 /** 7865 * Check browser support for drag'n'drop. 7866 * 7867 * @return Boolean 7868 */ 7869 browserSupport: function() { 7870 var supports = false, div = document.createElement('div'); 7871 7872 supports = ( 'draggable' in div ) || ( 'ondragstart' in div && 'ondrop' in div ); 7873 supports = supports && !! ( window.File && window.FileList && window.FileReader ); 7874 return supports; 7875 }, 7876 7877 isDraggingFile: function( event ) { 7878 if ( this.draggingFile !== null ) { 7879 return this.draggingFile; 7880 } 7881 7882 if ( _.isUndefined( event.originalEvent ) || _.isUndefined( event.originalEvent.dataTransfer ) ) { 7883 return false; 7884 } 7885 7886 this.draggingFile = _.indexOf( event.originalEvent.dataTransfer.types, 'Files' ) > -1 && 7887 _.indexOf( event.originalEvent.dataTransfer.types, 'text/plain' ) === -1; 7888 7889 return this.draggingFile; 7890 }, 7891 7892 refresh: function( e ) { 7893 var dropzone_id; 7894 for ( dropzone_id in this.dropzones ) { 7895 // Hide the dropzones only if dragging has left the screen. 7896 this.dropzones[ dropzone_id ].toggle( this.overContainer || this.overDropzone ); 7897 } 7898 7899 if ( ! _.isUndefined( e ) ) { 7900 $( e.target ).closest( '.uploader-editor' ).toggleClass( 'droppable', this.overDropzone ); 7901 } 7902 7903 if ( ! this.overContainer && ! this.overDropzone ) { 7904 this.draggingFile = null; 7905 } 7906 7907 return this; 7908 }, 7909 7910 render: function() { 7911 if ( ! this.initialized ) { 7912 return this; 7913 } 7914 7915 View.prototype.render.apply( this, arguments ); 7916 $( '.wp-editor-wrap' ).each( _.bind( this.attach, this ) ); 7917 return this; 7918 }, 7919 7920 attach: function( index, editor ) { 7921 // Attach a dropzone to an editor. 7922 var dropzone = this.$el.clone(); 7923 this.dropzones.push( dropzone ); 7924 $( editor ).append( dropzone ); 7925 return this; 7926 }, 7927 7928 /** 7929 * When a file is dropped on the editor uploader, open up an editor media workflow 7930 * and upload the file immediately. 7931 * 7932 * @param {jQuery.Event} event The 'drop' event. 7933 */ 7934 drop: function( event ) { 7935 var $wrap, uploadView; 7936 7937 this.containerDragleave( event ); 7938 this.dropzoneDragleave( event ); 7939 7940 this.files = event.originalEvent.dataTransfer.files; 7941 if ( this.files.length < 1 ) { 7942 return; 7943 } 7944 7945 // Set the active editor to the drop target. 7946 $wrap = $( event.target ).parents( '.wp-editor-wrap' ); 7947 if ( $wrap.length > 0 && $wrap[0].id ) { 7948 window.wpActiveEditor = $wrap[0].id.slice( 3, -5 ); 7949 } 7950 7951 if ( ! this.workflow ) { 7952 this.workflow = wp.media.editor.open( window.wpActiveEditor, { 7953 frame: 'post', 7954 state: 'insert', 7955 title: l10n.addMedia, 7956 multiple: true 7957 }); 7958 7959 uploadView = this.workflow.uploader; 7960 7961 if ( uploadView.uploader && uploadView.uploader.ready ) { 7962 this.addFiles.apply( this ); 7963 } else { 7964 this.workflow.on( 'uploader:ready', this.addFiles, this ); 7965 } 7966 } else { 7967 this.workflow.state().reset(); 7968 this.addFiles.apply( this ); 7969 this.workflow.open(); 7970 } 7971 7972 return false; 7973 }, 7974 7975 /** 7976 * Add the files to the uploader. 7977 */ 7978 addFiles: function() { 7979 if ( this.files.length ) { 7980 this.workflow.uploader.uploader.uploader.addFile( _.toArray( this.files ) ); 7981 this.files = []; 7982 } 7983 return this; 7984 }, 7985 7986 containerDragover: function( event ) { 7987 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 7988 return; 7989 } 7990 7991 this.overContainer = true; 7992 this.refresh(); 7993 }, 7994 7995 containerDragleave: function() { 7996 this.overContainer = false; 7997 7998 // Throttle dragleave because it's called when bouncing from some elements to others. 7999 _.delay( _.bind( this.refresh, this ), 50 ); 8000 }, 8001 8002 dropzoneDragover: function( event ) { 8003 if ( this.localDrag || ! this.isDraggingFile( event ) ) { 8004 return; 8005 } 8006 8007 this.overDropzone = true; 8008 this.refresh( event ); 8009 return false; 8010 }, 8011 8012 dropzoneDragleave: function( e ) { 8013 this.overDropzone = false; 8014 _.delay( _.bind( this.refresh, this, e ), 50 ); 8015 }, 8016 8017 click: function( e ) { 8018 // In the rare case where the dropzone gets stuck, hide it on click. 8019 this.containerDragleave( e ); 8020 this.dropzoneDragleave( e ); 8021 this.localDrag = false; 8022 } 8023 }); 8024 8025 module.exports = EditorUploader; 8026 8027 },{}],71:[function(require,module,exports){ 8028 /** 8029 * wp.media.view.UploaderInline 8030 * 8031 * The inline uploader that shows up in the 'Upload Files' tab. 8032 * 8033 * @class 8034 * @augments wp.media.View 8035 * @augments wp.Backbone.View 8036 * @augments Backbone.View 8037 */ 8038 var View = wp.media.View, 8039 UploaderInline; 8040 8041 UploaderInline = View.extend({ 8042 tagName: 'div', 8043 className: 'uploader-inline', 8044 template: wp.template('uploader-inline'), 8045 8046 events: { 8047 'click .close': 'hide' 8048 }, 8049 8050 initialize: function() { 8051 _.defaults( this.options, { 8052 message: '', 8053 status: true, 8054 canClose: false 8055 }); 8056 8057 if ( ! this.options.$browser && this.controller.uploader ) { 8058 this.options.$browser = this.controller.uploader.$browser; 8059 } 8060 8061 if ( _.isUndefined( this.options.postId ) ) { 8062 this.options.postId = wp.media.view.settings.post.id; 8063 } 8064 8065 if ( this.options.status ) { 8066 this.views.set( '.upload-inline-status', new wp.media.view.UploaderStatus({ 8067 controller: this.controller 8068 }) ); 8069 } 8070 }, 8071 8072 prepare: function() { 8073 var suggestedWidth = this.controller.state().get('suggestedWidth'), 8074 suggestedHeight = this.controller.state().get('suggestedHeight'), 8075 data = {}; 8076 8077 data.message = this.options.message; 8078 data.canClose = this.options.canClose; 8079 8080 if ( suggestedWidth && suggestedHeight ) { 8081 data.suggestedWidth = suggestedWidth; 8082 data.suggestedHeight = suggestedHeight; 8083 } 8084 8085 return data; 8086 }, 8087 /** 8088 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining 8089 */ 8090 dispose: function() { 8091 if ( this.disposing ) { 8092 /** 8093 * call 'dispose' directly on the parent class 8094 */ 8095 return View.prototype.dispose.apply( this, arguments ); 8096 } 8097 8098 // Run remove on `dispose`, so we can be sure to refresh the 8099 // uploader with a view-less DOM. Track whether we're disposing 8100 // so we don't trigger an infinite loop. 8101 this.disposing = true; 8102 return this.remove(); 8103 }, 8104 /** 8105 * @returns {wp.media.view.UploaderInline} Returns itself to allow chaining 8106 */ 8107 remove: function() { 8108 /** 8109 * call 'remove' directly on the parent class 8110 */ 8111 var result = View.prototype.remove.apply( this, arguments ); 8112 8113 _.defer( _.bind( this.refresh, this ) ); 8114 return result; 8115 }, 8116 8117 refresh: function() { 8118 var uploader = this.controller.uploader; 8119 8120 if ( uploader ) { 8121 uploader.refresh(); 8122 } 8123 }, 8124 /** 8125 * @returns {wp.media.view.UploaderInline} 8126 */ 8127 ready: function() { 8128 var $browser = this.options.$browser, 8129 $placeholder; 8130 8131 if ( this.controller.uploader ) { 8132 $placeholder = this.$('.browser'); 8133 8134 // Check if we've already replaced the placeholder. 8135 if ( $placeholder[0] === $browser[0] ) { 8136 return; 8137 } 8138 8139 $browser.detach().text( $placeholder.text() ); 8140 $browser[0].className = $placeholder[0].className; 8141 $placeholder.replaceWith( $browser.show() ); 8142 } 8143 8144 this.refresh(); 8145 return this; 8146 }, 8147 show: function() { 8148 this.$el.removeClass( 'hidden' ); 8149 }, 8150 hide: function() { 8151 this.$el.addClass( 'hidden' ); 8152 } 8153 8154 }); 8155 8156 module.exports = UploaderInline; 8157 8158 },{}],72:[function(require,module,exports){ 8159 /** 8160 * wp.media.view.UploaderStatusError 8161 * 8162 * @class 8163 * @augments wp.media.View 8164 * @augments wp.Backbone.View 8165 * @augments Backbone.View 8166 */ 8167 var UploaderStatusError = wp.media.View.extend({ 8168 className: 'upload-error', 8169 template: wp.template('uploader-status-error') 8170 }); 8171 8172 module.exports = UploaderStatusError; 8173 8174 },{}],73:[function(require,module,exports){ 8175 /** 8176 * wp.media.view.UploaderStatus 8177 * 8178 * An uploader status for on-going uploads. 8179 * 8180 * @class 8181 * @augments wp.media.View 8182 * @augments wp.Backbone.View 8183 * @augments Backbone.View 8184 */ 8185 var View = wp.media.View, 8186 UploaderStatus; 8187 8188 UploaderStatus = View.extend({ 8189 className: 'media-uploader-status', 8190 template: wp.template('uploader-status'), 8191 8192 events: { 8193 'click .upload-dismiss-errors': 'dismiss' 8194 }, 8195 8196 initialize: function() { 8197 this.queue = wp.Uploader.queue; 8198 this.queue.on( 'add remove reset', this.visibility, this ); 8199 this.queue.on( 'add remove reset change:percent', this.progress, this ); 8200 this.queue.on( 'add remove reset change:uploading', this.info, this ); 8201 8202 this.errors = wp.Uploader.errors; 8203 this.errors.reset(); 8204 this.errors.on( 'add remove reset', this.visibility, this ); 8205 this.errors.on( 'add', this.error, this ); 8206 }, 8207 /** 8208 * @global wp.Uploader 8209 * @returns {wp.media.view.UploaderStatus} 8210 */ 8211 dispose: function() { 8212 wp.Uploader.queue.off( null, null, this ); 8213 /** 8214 * call 'dispose' directly on the parent class 8215 */ 8216 View.prototype.dispose.apply( this, arguments ); 8217 return this; 8218 }, 8219 8220 visibility: function() { 8221 this.$el.toggleClass( 'uploading', !! this.queue.length ); 8222 this.$el.toggleClass( 'errors', !! this.errors.length ); 8223 this.$el.toggle( !! this.queue.length || !! this.errors.length ); 8224 }, 8225 8226 ready: function() { 8227 _.each({ 8228 '$bar': '.media-progress-bar div', 8229 '$index': '.upload-index', 8230 '$total': '.upload-total', 8231 '$filename': '.upload-filename' 8232 }, function( selector, key ) { 8233 this[ key ] = this.$( selector ); 8234 }, this ); 8235 8236 this.visibility(); 8237 this.progress(); 8238 this.info(); 8239 }, 8240 8241 progress: function() { 8242 var queue = this.queue, 8243 $bar = this.$bar; 8244 8245 if ( ! $bar || ! queue.length ) { 8246 return; 8247 } 8248 8249 $bar.width( ( queue.reduce( function( memo, attachment ) { 8250 if ( ! attachment.get('uploading') ) { 8251 return memo + 100; 8252 } 8253 8254 var percent = attachment.get('percent'); 8255 return memo + ( _.isNumber( percent ) ? percent : 100 ); 8256 }, 0 ) / queue.length ) + '%' ); 8257 }, 8258 8259 info: function() { 8260 var queue = this.queue, 8261 index = 0, active; 8262 8263 if ( ! queue.length ) { 8264 return; 8265 } 8266 8267 active = this.queue.find( function( attachment, i ) { 8268 index = i; 8269 return attachment.get('uploading'); 8270 }); 8271 8272 this.$index.text( index + 1 ); 8273 this.$total.text( queue.length ); 8274 this.$filename.html( active ? this.filename( active.get('filename') ) : '' ); 8275 }, 8276 /** 8277 * @param {string} filename 8278 * @returns {string} 8279 */ 8280 filename: function( filename ) { 8281 return _.escape( filename ); 8282 }, 8283 /** 8284 * @param {Backbone.Model} error 8285 */ 8286 error: function( error ) { 8287 this.views.add( '.upload-errors', new wp.media.view.UploaderStatusError({ 8288 filename: this.filename( error.get('file').name ), 8289 message: error.get('message') 8290 }), { at: 0 }); 8291 }, 8292 8293 /** 8294 * @global wp.Uploader 8295 * 8296 * @param {Object} event 8297 */ 8298 dismiss: function( event ) { 8299 var errors = this.views.get('.upload-errors'); 8300 8301 event.preventDefault(); 8302 8303 if ( errors ) { 8304 _.invoke( errors, 'remove' ); 8305 } 8306 wp.Uploader.errors.reset(); 8307 } 8308 }); 8309 8310 module.exports = UploaderStatus; 8311 8312 },{}],74:[function(require,module,exports){ 8313 /** 8314 * wp.media.view.UploaderWindow 8315 * 8316 * An uploader window that allows for dragging and dropping media. 8317 * 8318 * @class 8319 * @augments wp.media.View 8320 * @augments wp.Backbone.View 8321 * @augments Backbone.View 8322 * 8323 * @param {object} [options] Options hash passed to the view. 8324 * @param {object} [options.uploader] Uploader properties. 8325 * @param {jQuery} [options.uploader.browser] 8326 * @param {jQuery} [options.uploader.dropzone] jQuery collection of the dropzone. 8327 * @param {object} [options.uploader.params] 8328 */ 8329 var $ = jQuery, 8330 UploaderWindow; 8331 8332 UploaderWindow = wp.media.View.extend({ 8333 tagName: 'div', 8334 className: 'uploader-window', 8335 template: wp.template('uploader-window'), 8336 8337 initialize: function() { 8338 var uploader; 8339 8340 this.$browser = $('<a href="#" class="browser" />').hide().appendTo('body'); 8341 8342 uploader = this.options.uploader = _.defaults( this.options.uploader || {}, { 8343 dropzone: this.$el, 8344 browser: this.$browser, 8345 params: {} 8346 }); 8347 8348 // Ensure the dropzone is a jQuery collection. 8349 if ( uploader.dropzone && ! (uploader.dropzone instanceof $) ) { 8350 uploader.dropzone = $( uploader.dropzone ); 8351 } 8352 8353 this.controller.on( 'activate', this.refresh, this ); 8354 8355 this.controller.on( 'detach', function() { 8356 this.$browser.remove(); 8357 }, this ); 8358 }, 8359 8360 refresh: function() { 8361 if ( this.uploader ) { 8362 this.uploader.refresh(); 8363 } 8364 }, 8365 8366 ready: function() { 8367 var postId = wp.media.view.settings.post.id, 8368 dropzone; 8369 8370 // If the uploader already exists, bail. 8371 if ( this.uploader ) { 8372 return; 8373 } 8374 8375 if ( postId ) { 8376 this.options.uploader.params.post_id = postId; 8377 } 8378 this.uploader = new wp.Uploader( this.options.uploader ); 8379 8380 dropzone = this.uploader.dropzone; 8381 dropzone.on( 'dropzone:enter', _.bind( this.show, this ) ); 8382 dropzone.on( 'dropzone:leave', _.bind( this.hide, this ) ); 8383 8384 $( this.uploader ).on( 'uploader:ready', _.bind( this._ready, this ) ); 8385 }, 8386 8387 _ready: function() { 8388 this.controller.trigger( 'uploader:ready' ); 8389 }, 8390 8391 show: function() { 8392 var $el = this.$el.show(); 8393 8394 // Ensure that the animation is triggered by waiting until 8395 // the transparent element is painted into the DOM. 8396 _.defer( function() { 8397 $el.css({ opacity: 1 }); 8398 }); 8399 }, 8400 8401 hide: function() { 8402 var $el = this.$el.css({ opacity: 0 }); 8403 8404 wp.media.transition( $el ).done( function() { 8405 // Transition end events are subject to race conditions. 8406 // Make sure that the value is set as intended. 8407 if ( '0' === $el.css('opacity') ) { 8408 $el.hide(); 8409 } 8410 }); 8411 8412 // https://core.trac.wordpress.org/ticket/27341 8413 _.delay( function() { 8414 if ( '0' === $el.css('opacity') && $el.is(':visible') ) { 8415 $el.hide(); 8416 } 8417 }, 500 ); 8418 } 8419 }); 8420 8421 module.exports = UploaderWindow; 8422 8423 },{}],75:[function(require,module,exports){ 8424 /** 8425 * wp.media.View 8426 * 8427 * The base view class for media. 8428 * 8429 * Undelegating events, removing events from the model, and 8430 * removing events from the controller mirror the code for 8431 * `Backbone.View.dispose` in Backbone 0.9.8 development. 8432 * 8433 * This behavior has since been removed, and should not be used 8434 * outside of the media manager. 8435 * 8436 * @class 8437 * @augments wp.Backbone.View 8438 * @augments Backbone.View 8439 */ 8440 var View = wp.Backbone.View.extend({ 8441 constructor: function( options ) { 8442 if ( options && options.controller ) { 8443 this.controller = options.controller; 8444 } 8445 wp.Backbone.View.apply( this, arguments ); 8446 }, 8447 /** 8448 * @todo The internal comment mentions this might have been a stop-gap 8449 * before Backbone 0.9.8 came out. Figure out if Backbone core takes 8450 * care of this in Backbone.View now. 8451 * 8452 * @returns {wp.media.View} Returns itself to allow chaining 8453 */ 8454 dispose: function() { 8455 // Undelegating events, removing events from the model, and 8456 // removing events from the controller mirror the code for 8457 // `Backbone.View.dispose` in Backbone 0.9.8 development. 8458 this.undelegateEvents(); 8459 8460 if ( this.model && this.model.off ) { 8461 this.model.off( null, null, this ); 8462 } 8463 8464 if ( this.collection && this.collection.off ) { 8465 this.collection.off( null, null, this ); 8466 } 8467 8468 // Unbind controller events. 8469 if ( this.controller && this.controller.off ) { 8470 this.controller.off( null, null, this ); 8471 } 8472 8473 return this; 8474 }, 8475 /** 8476 * @returns {wp.media.View} Returns itself to allow chaining 8477 */ 8478 remove: function() { 8479 this.dispose(); 8480 /** 8481 * call 'remove' directly on the parent class 8482 */ 8483 return wp.Backbone.View.prototype.remove.apply( this, arguments ); 8484 } 8485 }); 8486 8487 module.exports = View; 8488 8489 },{}]},{},[19]);
Note: See TracChangeset
for help on using the changeset viewer.