WordPress.org

Make WordPress Core

Changeset 22321


Ignore:
Timestamp:
10/29/12 06:56:23 (21 months ago)
Author:
koopersmith
Message:

Add a sidebar to the media modal.

  • Adds wp.media.view.Sidebar, to aid in rendering the sidebar.
  • Removes the directions from the Attachments view and shifts search into a separate view (wp.mce.view.Search) that can be relocated at will. This also serves to simplify the Attachments view by removing the nested list and $list parameters.
  • Show the toolbar on the featured image workflow, effectively requiring confirmation before closing the dialog.

see #21390, #21776, #21808.

Location:
trunk
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/wp-admin/includes/meta-boxes.php

    r22320 r22321  
    10191019            $thumbnailId = $element.find('input[name="thumbnail_id"]'), 
    10201020            title        = '<?php _e( "Choose a Featured Image" ); ?>', 
    1021             workflow, selection, setFeaturedImage; 
     1021            update       = '<?php _e( "Update Featured Image" ); ?>', 
     1022            frame, selection, setFeaturedImage; 
    10221023 
    10231024        setFeaturedImage = function( thumbnailId ) { 
     
    10301031            event.preventDefault(); 
    10311032 
    1032             if ( ! workflow ) { 
    1033                 workflow = wp.media({ 
     1033            if ( ! frame ) { 
     1034                frame = wp.media({ 
    10341035                    title:   title, 
    10351036                    library: { 
     
    10381039                }); 
    10391040 
    1040                 selection = workflow.state().get('selection'); 
    1041  
    1042                 selection.on( 'add', function( model ) { 
    1043                     var sizes = model.get('sizes'), 
    1044                         size; 
    1045  
    1046                     setFeaturedImage( model.id ); 
    1047  
    1048                     // @todo: might need a size hierarchy equivalent. 
    1049                     if ( sizes ) 
    1050                         size = sizes['post-thumbnail'] || sizes.medium; 
    1051  
    1052                     // @todo: Need a better way of accessing full size 
    1053                     // data besides just calling toJSON(). 
    1054                     size = size || model.toJSON(); 
    1055  
    1056                     workflow.close(); 
    1057                     selection.clear(); 
    1058  
    1059                     $( '<img />', { 
    1060                         src:    size.url, 
    1061                         width:  size.width 
    1062                     }).prependTo( $element ); 
    1063                 }); 
     1041                frame.toolbar( new wp.media.view.Toolbar({ 
     1042                    controller: frame, 
     1043                    items: { 
     1044                        update: { 
     1045                            style:    'primary', 
     1046                            text:     update, 
     1047                            priority: 40, 
     1048 
     1049                            click: function() { 
     1050                                var selection = frame.state().get('selection'), 
     1051                                    model = selection.first(), 
     1052                                    sizes = model.get('sizes'), 
     1053                                    size; 
     1054 
     1055                                setFeaturedImage( model.id ); 
     1056 
     1057                                // @todo: might need a size hierarchy equivalent. 
     1058                                if ( sizes ) 
     1059                                    size = sizes['post-thumbnail'] || sizes.medium; 
     1060 
     1061                                // @todo: Need a better way of accessing full size 
     1062                                // data besides just calling toJSON(). 
     1063                                size = size || model.toJSON(); 
     1064 
     1065                                frame.close(); 
     1066                                selection.clear(); 
     1067 
     1068                                $( '<img />', { 
     1069                                    src:    size.url, 
     1070                                    width:  size.width 
     1071                                }).prependTo( $element ); 
     1072                            } 
     1073                        } 
     1074                    } 
     1075                }) ); 
    10641076            } 
    10651077 
    1066             workflow.open(); 
     1078            frame.open(); 
    10671079        }); 
    10681080 
  • trunk/wp-includes/css/media-views.css

    r22320 r22321  
    7373 */ 
    7474.media-toolbar { 
    75     position: relative; 
    76     z-index: 50; 
    77     height: 60px; 
     75    position: absolute; 
     76    top: 0; 
     77    left: 220px; 
     78    right: 0; 
     79    z-index: 100; 
     80    height: 50px; 
    7881    padding: 0 10px; 
    7982    border-bottom: 1px solid #dfdfdf; 
     
    9295    margin-left: 10px; 
    9396    float: left; 
    94     margin-top: 16px; 
     97    margin-top: 10px; 
    9598} 
    9699 
     
    99102    margin-right: 10px; 
    100103    float: left; 
    101     margin-top: 16px; 
     104    margin-top: 10px; 
     105} 
     106 
     107/** 
     108 * Sidebar 
     109 */ 
     110.media-sidebar { 
     111    position: absolute; 
     112    top: 0; 
     113    left: 0; 
     114    bottom: 0; 
     115    width: 219px; 
     116    z-index: 50; 
     117    background: #f5f5f5; 
     118    border-right: 1px solid #dfdfdf; 
     119} 
     120 
     121.hide-sidebar .media-sidebar { 
     122    display: none; 
     123} 
     124 
     125.media-sidebar .sidebar-title { 
     126    font-weight: 200; 
     127    font-size: 20px; 
     128    margin: 0; 
     129    padding: 12px 10px 10px; 
     130    line-height: 28px; 
     131    /*border-bottom: 1px solid #dfdfdf;*/ 
     132} 
     133 
     134.media-sidebar .sidebar-content { 
     135    padding: 0 10px; 
     136} 
     137 
     138.media-sidebar .search { 
     139    display: block; 
     140    width: 100%; 
     141} 
     142 
     143.media-sidebar .selection-preview { 
     144    display: block; 
     145    padding-top: 5px; 
    102146} 
    103147 
     
    106150 */ 
    107151 
    108 .media-frame .attachments, 
    109 .media-frame .media-toolbar { 
     152.media-frame .media-content, 
     153.media-frame .media-toolbar, 
     154.media-frame .media-sidebar { 
    110155    -webkit-transition-property: left, right, top, bottom, margin; 
    111156    -moz-transition-property:    left, right, top, bottom, margin; 
     
    121166} 
    122167 
    123 .media-frame .attachments { 
    124     position: absolute; 
    125     top: 61px; 
    126     left: 0; 
     168.media-frame .media-content { 
     169    position: absolute; 
     170    top: 51px; 
     171    left: 220px; 
    127172    right: 0; 
    128173    bottom: 0; 
    129174    height: auto; 
    130175    width: auto; 
    131 } 
    132  
    133 .media-frame.hide-toolbar .attachments { 
    134     top: 0; 
    135 } 
    136  
    137 .media-frame .media-toolbar { 
    138     margin-top: 0; 
    139 } 
    140  
    141 .media-frame.hide-toolbar .media-toolbar { 
    142     margin-top: -61px; 
     176    overflow: auto; 
     177} 
     178 
     179.media-frame.hide-sidebar .media-content { 
     180    left: 0; 
    143181} 
    144182 
     
    146184    display: none; 
    147185} 
    148 /** 
    149  * Attachments 
    150  */ 
    151 .attachments { 
    152     position: relative; 
    153     width: 100%; 
    154     height: 100%; 
    155 } 
    156  
    157 .attachments-header { 
    158     position: absolute; 
    159     top: 0; 
    160     left: 0; 
    161     right: 0; 
    162     height: 50px; 
    163     padding: 0 10px; 
    164     background: #fff; 
    165 } 
    166  
    167 .attachments-header h3 { 
    168     float: left; 
    169     margin: 0; 
    170     padding: 0; 
    171     line-height: 50px; 
    172     font-size: 18px; 
    173     font-weight: 200; 
    174 } 
    175  
    176 .attachments-header .search { 
    177     float: right; 
     186 
     187/** 
     188 * Search 
     189 */ 
     190.media-frame .search { 
    178191    margin-top: 11px; 
    179192    padding: 4px; 
     
    184197} 
    185198 
     199/** 
     200 * Attachments 
     201 */ 
     202/*.attachments { 
     203    position: relative; 
     204    width: 100%; 
     205    height: 100%; 
     206} 
     207 
     208.attachments-header { 
     209    position: absolute; 
     210    top: 0; 
     211    left: 0; 
     212    right: 0; 
     213    height: 50px; 
     214    padding: 0 10px; 
     215    background: #fff; 
     216} 
     217 
     218.attachments-header h3 { 
     219    float: left; 
     220    margin: 0; 
     221    padding: 0; 
     222    line-height: 50px; 
     223    font-size: 18px; 
     224    font-weight: 200; 
     225} 
     226 
    186227.attachments ul { 
    187228    position: absolute; 
     
    192233    overflow: auto; 
    193234    margin: 0 0 20px; 
    194 } 
     235}*/ 
    195236 
    196237/** 
     
    402443    background: rgba( 0, 86, 132, 0.9 ); 
    403444 
    404     /*z-index: -200;*/ 
    405445    z-index: 250000; 
    406446    display: none; 
     
    414454    transition:         opacity 250ms; 
    415455} 
    416  
    417 /*.drag-over .uploader-window { 
    418     z-index: 250000; 
    419 }*/ 
    420456 
    421457.uploader-window-content { 
  • trunk/wp-includes/js/media-views.js

    r22320 r22321  
    114114            id:       'library', 
    115115            multiple: false, 
    116             describe: false 
     116            describe: false, 
     117            title:    l10n.mediaLibrary 
    117118        }, 
    118119 
     
    131132                toolbar; 
    132133 
     134            // Toolbar. 
    133135            toolbar = this._postLibraryToolbar = new media.view.Toolbar.PostLibrary({ 
    134136                controller: frame, 
    135                 selection:  this.get('selection') 
     137                state:      this 
    136138            }); 
    137139 
     
    139141            this.get('selection').on( 'add remove', toolbar.visibility, toolbar ); 
    140142 
     143            // Sidebar. 
     144            frame.sidebar( new media.view.Sidebar({ 
     145                controller: frame, 
     146                views: { 
     147                    search: new media.view.Search({ 
     148                        controller: frame, 
     149                        model:      this.get('library').props, 
     150                        priority:   20 
     151                    }), 
     152 
     153                    selection: new media.view.SelectionPreview({ 
     154                        controller: frame, 
     155                        collection: this.get('selection'), 
     156                        priority:   40 
     157                    }) 
     158                } 
     159            }) ); 
     160 
     161            // Content. 
    141162            frame.content( new media.view.Attachments({ 
    142                 directions: this.get('multiple') ? l10n.selectMediaMultiple : l10n.selectMediaSingular, 
    143163                controller: frame, 
    144164                collection: this.get('library'), 
     
    146166                AttachmentView: media.view.Attachment.Library 
    147167            }).render() ); 
    148  
    149             if ( ! this.get('selection').length ) 
    150                 frame.$el.addClass('hide-toolbar'); 
    151168 
    152169            // If we're in a workflow that supports multiple attachments, 
     
    174191            id:         'gallery', 
    175192            multiple:   true, 
    176             describe:   true 
     193            describe:   true, 
     194            title:      l10n.createGallery 
    177195        }, 
    178196 
     
    187205            var frame = this.frame; 
    188206 
     207            // Toolbar. 
    189208            frame.toolbar( new media.view.Toolbar.Gallery({ 
    190209                controller: frame, 
    191                 editing:    this.get('editing'), 
    192                 selection:  this.get('selection') 
     210                state:      this 
    193211            }) ); 
    194212 
     213            // Sidebar. 
     214            frame.sidebar( new media.view.Sidebar({ 
     215                controller: frame 
     216            }).render() ); 
     217 
     218            // Content. 
    195219            frame.content( new media.view.Attachments({ 
    196                 directions: 'Gallery time!', 
    197220                controller: frame, 
    198221                collection: this.get('selection'), 
     
    246269 
    247270        render: function() { 
    248             var els = [ this.sidebar().el, this.toolbar().el, this.content().el ]; 
     271            var els = [ this.toolbar().el, this.sidebar().el, this.content().el ]; 
    249272 
    250273            if ( this.modal ) 
     
    635658    media.view.Toolbar.PostLibrary = media.view.Toolbar.extend({ 
    636659        initialize: function() { 
    637             var selection = this.options.selection, 
     660            var state = this.options.state, 
     661                selection = state.get('selection'), 
    638662                controller = this.options.controller; 
    639663 
    640664            this.options.items = { 
    641                 'selection-preview': new media.view.SelectionPreview({ 
    642                     controller: controller, 
    643                     collection: selection, 
    644                     priority: -40 
    645                 }), 
    646  
    647665                'create-new-gallery': { 
    648666                    style:    'primary', 
     
    663681                            click: function() { 
    664682                                controller.close(); 
    665                                 controller.state().trigger( 'insert', selection ); 
     683                                state.trigger( 'insert', selection ); 
    666684                                selection.clear(); 
    667685                            } 
     
    699717 
    700718            media.view.Toolbar.prototype.initialize.apply( this, arguments ); 
     719            this.visibility(); 
    701720        }, 
    702721 
    703722        visibility: function() { 
    704             var selection = this.options.selection, 
     723            var state = this.options.state, 
     724                selection = state.get('selection'), 
    705725                controller = this.options.controller, 
    706726                count = selection.length, 
    707727                showGallery; 
    708  
    709             controller.$el.toggleClass( 'hide-toolbar', ! count ); 
    710728 
    711729            // Check if every attachment in the selection is an image. 
     
    719737                button.model.set( 'style', showGallery ? '' : 'primary' ); 
    720738            }); 
     739 
     740            _.first( insert.buttons ).model.set( 'disabled', ! count ); 
    721741        } 
    722742    }); 
     
    726746    media.view.Toolbar.Gallery = media.view.Toolbar.extend({ 
    727747        initialize: function() { 
    728             var editing = this.options.editing, 
    729                 selection = this.options.selection, 
     748            var state = this.options.state, 
     749                editing = state.get('editing'), 
     750                selection = state.get('selection'), 
    730751                controller = this.options.controller; 
    731752 
     
    737758                    click:    function() { 
    738759                        controller.close(); 
    739                         controller.state().trigger( 'update', selection ); 
     760                        state.trigger( 'update', selection ); 
    740761                        selection.clear(); 
    741762                        controller.state('library'); 
     
    770791 
    771792        defaults: { 
    772             text:  '', 
    773             style: '', 
    774             size:  'large' 
     793            text:     '', 
     794            style:    '', 
     795            size:     'large', 
     796            disabled: false 
    775797        }, 
    776798 
     
    797819 
    798820        render: function() { 
    799             var classes = [ 'button', this.className ]; 
    800  
    801             if ( this.model.get('style') ) 
    802                 classes.push( 'button-' + this.model.get('style') ); 
    803  
    804             if ( this.model.get('size') ) 
    805                 classes.push( 'button-' + this.model.get('size') ); 
     821            var classes = [ 'button', this.className ], 
     822                model = this.model.toJSON(); 
     823 
     824            if ( model.style ) 
     825                classes.push( 'button-' + model.style ); 
     826 
     827            if ( model.size ) 
     828                classes.push( 'button-' + model.size ); 
    806829 
    807830            classes = _.uniq( classes.concat( this.options.classes ) ); 
    808831            this.el.className = classes.join(' '); 
    809832 
     833            this.$el.attr( 'disabled', model.disabled ); 
    810834 
    811835            // Detach the dropdown. 
     
    823847        click: function( event ) { 
    824848            event.preventDefault(); 
    825             if ( this.options.click ) 
     849            if ( this.options.click && ! this.model.get('disabled') ) 
    826850                this.options.click.apply( this, arguments ); 
    827851        } 
     
    851875        render: function() { 
    852876            this.$el.html( $( _.pluck( this.buttons, 'el' ) ).detach() ); 
     877            return this; 
     878        } 
     879    }); 
     880 
     881    /** 
     882     * wp.media.view.Sidebar 
     883     */ 
     884    media.view.Sidebar = Backbone.View.extend({ 
     885        tagName:   'div', 
     886        className: 'media-sidebar', 
     887        template:  media.template('sidebar'), 
     888 
     889        initialize: function() { 
     890            this.controller = this.options.controller; 
     891            this._views     = {}; 
     892 
     893            if ( this.options.views ) 
     894                this.add( this.options.views, { silent: true }).render(); 
     895        }, 
     896 
     897        render: function() { 
     898            var els = _( this._views ).chain().sortBy( function( view ) { 
     899                    return view.options.priority || 10; 
     900                }).pluck('el').value(); 
     901 
     902            // Make sure to detach the elements we want to reuse. 
     903            // Otherwise, `jQuery.html()` will unbind their events. 
     904            $( els ).detach(); 
     905 
     906            this.$el.html( this.template({ 
     907                title:    this.controller.state().get('title') || '', 
     908                uploader: this.controller.options.uploader 
     909            }) ); 
     910 
     911            this.$('.sidebar-content').html( els ); 
     912 
     913            return this; 
     914        }, 
     915 
     916        add: function( id, view, options ) { 
     917            // Accept an object with an `id` : `view` mapping. 
     918            if ( _.isObject( id ) ) { 
     919                _.each( id, function( view, id ) { 
     920                    this.add( id, view, options ); 
     921                }, this ); 
     922                return this; 
     923            } 
     924 
     925            view.controller = view.controller || this.controller; 
     926 
     927            this._views[ id ] = view; 
     928            if ( ! options || ! options.silent ) 
     929                this.render(); 
     930            return this; 
     931        }, 
     932 
     933        get: function( id ) { 
     934            return this._views[ id ]; 
     935        }, 
     936 
     937        remove: function( id, options ) { 
     938            delete this._views[ id ]; 
     939            if ( ! options || ! options.silent ) 
     940                this.render(); 
    853941            return this; 
    854942        } 
     
    10691157     */ 
    10701158    media.view.Attachments = Backbone.View.extend({ 
    1071         tagName:   'div', 
     1159        tagName:   'ul', 
    10721160        className: 'attachments', 
    1073         template:  media.template('attachments'), 
    10741161 
    10751162        events: { 
    1076             'keyup .search': 'search' 
     1163            'scroll': 'scroll' 
    10771164        }, 
    10781165 
     
    10931180            }, this ); 
    10941181 
    1095             this.collection.on( 'reset', this.refresh, this ); 
    1096  
    1097             this.$list = $('<ul />'); 
    1098             this.list  = this.$list[0]; 
    1099  
     1182            this.collection.on( 'reset', this.render, this ); 
     1183 
     1184            // Throttle the scroll handler. 
    11001185            this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value(); 
    1101             this.$list.on( 'scroll.attachments', this.scroll ); 
    11021186 
    11031187            this.initSortable(); 
     
    11111195                return; 
    11121196 
    1113             this.$list.sortable({ 
     1197            this.$el.sortable({ 
    11141198                // If the `collection` has a `comparator`, disable sorting. 
    11151199                disabled: !! collection.comparator, 
     
    11171201                // Prevent attachments from being dragged outside the bounding 
    11181202                // box of the list. 
    1119                 containment: this.$list, 
     1203                containment: this.$el, 
    11201204 
    11211205                // Change the position of the attachment as soon as the 
     
    11451229            // check to see if we have a `comparator`. If so, disable sorting. 
    11461230            collection.props.on( 'change:orderby', function() { 
    1147                 this.$list.sortable( 'option', 'disabled', !! collection.comparator ); 
     1231                this.$el.sortable( 'option', 'disabled', !! collection.comparator ); 
    11481232            }, this ); 
    11491233        }, 
    11501234 
    11511235        render: function() { 
    1152             // Detach the list from the DOM to prevent event removal. 
    1153             this.$list.detach(); 
    1154  
    1155             this.$el.html( this.template( this.options ) ).append( this.$list ); 
    1156             this.refresh(); 
    1157             return this; 
    1158         }, 
    1159  
    1160         refresh: function() { 
    11611236            // If there are no elements, load some. 
    11621237            if ( ! this.collection.length ) { 
    11631238                this.collection.more(); 
    1164                 this.$list.empty(); 
     1239                this.$el.empty(); 
    11651240                return this; 
    11661241            } 
     
    11681243            // Otherwise, create all of the Attachment views, and replace 
    11691244            // the list in a single DOM operation. 
    1170             this.$list.html( this.collection.map( function( attachment ) { 
     1245            this.$el.html( this.collection.map( function( attachment ) { 
    11711246                return new this.options.AttachmentView({ 
    11721247                    controller: this.controller, 
     
    11891264            }).render(); 
    11901265 
    1191             children = this.$list.children(); 
     1266            children = this.$el.children(); 
    11921267 
    11931268            if ( children.length > index ) 
    11941269                children.eq( index ).before( view.$el ); 
    11951270            else 
    1196                 this.$list.append( view.$el ); 
     1271                this.$el.append( view.$el ); 
    11971272        }, 
    11981273 
    11991274        remove: function( attachment, index ) { 
    1200             var children = this.$list.children(); 
     1275            var children = this.$el.children(); 
    12011276            if ( children.length ) 
    12021277                children.eq( index ).detach(); 
     
    12051280        scroll: function( event ) { 
    12061281            // @todo: is this still necessary? 
    1207             if ( ! this.$list.is(':visible') ) 
     1282            if ( ! this.$el.is(':visible') ) 
    12081283                return; 
    12091284 
    1210             if ( this.list.scrollHeight < this.list.scrollTop + ( this.list.clientHeight * this.options.refreshThreshold ) ) { 
     1285            if ( this.el.scrollHeight < this.el.scrollTop + ( this.el.clientHeight * this.options.refreshThreshold ) ) { 
    12111286                this.collection.more(); 
    12121287            } 
     1288        } 
     1289    }); 
     1290 
     1291    /** 
     1292     * wp.media.view.Search 
     1293     */ 
     1294    media.view.Search = Backbone.View.extend({ 
     1295        tagName:   'input', 
     1296        className: 'search', 
     1297 
     1298        attributes: { 
     1299            type:        'text', 
     1300            placeholder: l10n.search 
     1301        }, 
     1302 
     1303        events: { 
     1304            'keyup': 'search' 
     1305        }, 
     1306 
     1307        render: function() { 
     1308            this.el.value = this.model.escape('search'); 
     1309            return this; 
    12131310        }, 
    12141311 
    12151312        search: function( event ) { 
    1216             var props = this.collection.props; 
    1217  
    12181313            if ( event.target.value ) 
    1219                 props.set( 'search', event.target.value ); 
     1314                this.model.set( 'search', event.target.value ); 
    12201315            else 
    1221                 props.unset('search'); 
     1316                this.model.unset('search'); 
    12221317        } 
    12231318    }); 
  • trunk/wp-includes/media.php

    r22320 r22321  
    13101310    </script> 
    13111311 
    1312     <script type="text/html" id="tmpl-attachments"> 
    1313         <div class="attachments-header"> 
    1314             <h3><%- directions %></h3> 
    1315             <input class="search" type="text" placeholder="<?php esc_attr_e('Search'); ?>" /> 
    1316         </div> 
     1312    <script type="text/html" id="tmpl-sidebar"> 
     1313        <h2 class="sidebar-title"><%- title %></h2> 
     1314        <div class="sidebar-content"></div> 
    13171315    </script> 
    13181316 
  • trunk/wp-includes/script-loader.php

    r22236 r22321  
    324324    did_action( 'init' ) && $scripts->localize( 'media-views', '_wpMediaViewsL10n', array( 
    325325        // Generic 
    326         'insertMedia'           => __( 'Insert Media' ), 
    327         'selectMediaSingular'   => __( 'Select a media file:' ), 
    328         'selectMediaMultiple'   => __( 'Select one or more media files:' ), 
     326        'insertMedia' => __( 'Insert Media' ), 
     327        'search'      => __( 'Search' ), 
    329328 
    330329        // Library 
     330        'mediaLibrary'          => __( 'Media Library' ), 
    331331        'createNewGallery'      => __( 'Create a new gallery' ), 
    332332        'insertIntoPost'        => __( 'Insert into post' ), 
     
    334334 
    335335        // Gallery 
     336        'createGallery'          => __( 'Create Gallery' ), 
    336337        'returnToLibrary'        => __( 'Return to media library' ), 
    337338        'continueEditingGallery' => __( 'Continue editing gallery' ), 
Note: See TracChangeset for help on using the changeset viewer.