WordPress.org

Make WordPress Core

Ticket #26601: 26601.3.patch

File 26601.3.patch, 118.0 KB (added by trishasalas, 5 years ago)
  • src/wp-admin/css/common.css

     
    262262
    263263a:focus {
    264264        color: #124964;
    265     -webkit-box-shadow:
    266         0 0 0 1px #5b9dd9,
    267                 0 0 2px 1px rgba(30, 140, 190, .8);
    268     box-shadow:
    269         0 0 0 1px #5b9dd9,
    270                 0 0 2px 1px rgba(30, 140, 190, .8);
     265        -webkit-box-shadow:
     266        0 0 0 1px #5b9dd9,
     267        0 0 2px 1px rgba(30, 140, 190, .8);
     268        box-shadow:
     269        0 0 0 1px #5b9dd9,
     270        0 0 2px 1px rgba(30, 140, 190, .8);
    271271}
    272272
    273273.ie8 a:focus {
     
    950950/* not a part of filter bar, but derived from it, so here for now */
    951951.title-count {
    952952        display: inline;
    953         top: -3px;
    954         margin-left: 5px;
    955953        margin-right: 20px;
     954        vertical-align: bottom;
    956955}
    957956
     957.add-new-theme {
     958        display: inline;
     959        position: relative;
     960        bottom: -10px;
     961}
     962
    958963.filter-items {
    959964        float: left;
    960965}
     
    12741279
    12751280.notice-success,
    12761281div.updated {
    1277      border-color: #7ad03a;
     1282        border-color: #7ad03a;
    12781283}
    12791284
    12801285.notice-warning {
    1281     border-color: #ffba00;
     1286        border-color: #ffba00;
    12821287}
    12831288
    12841289.notice-error,
    12851290div.error {
    1286     border-color: #dd3d36;
     1291        border-color: #dd3d36;
    12871292}
    12881293
    12891294.notice-info {
    1290     border-color: #2ea2cc;
     1295        border-color: #2ea2cc;
    12911296}
    12921297
    12931298.wrap .notice,
     
    25902595}
    25912596
    25922597@media print,
    2593         (-o-min-device-pixel-ratio: 5/4),
    2594         (-webkit-min-device-pixel-ratio: 1.25),
    2595         (min-resolution: 120dpi) {
     2598(-o-min-device-pixel-ratio: 5/4),
     2599(-webkit-min-device-pixel-ratio: 1.25),
     2600(min-resolution: 120dpi) {
    25962601
    25972602        body.plugin-install-php #TB_window,
    25982603        body.import-php #TB_window,
     
    30553060 * HiDPI Displays
    30563061 */
    30573062@media print,
    3058   (-o-min-device-pixel-ratio: 5/4),
    3059   (-webkit-min-device-pixel-ratio: 1.25),
    3060   (min-resolution: 120dpi) {
     3063(-o-min-device-pixel-ratio: 5/4),
     3064(-webkit-min-device-pixel-ratio: 1.25),
     3065(min-resolution: 120dpi) {
    30613066        /* Back-compat for pre-3.8 */
    30623067        div.star-holder,
    30633068        div.star-holder .star-rating {
     
    32523257        div#post-body.metabox-holder.columns-1 {
    32533258                overflow-x: hidden;
    32543259        }
    3255 }
     3260}
     3261 No newline at end of file
  • src/wp-admin/css/themes.css

     
    479479.theme-overlay .theme-header .close:focus,
    480480.theme-overlay .theme-header .right:focus,
    481481.theme-overlay .theme-header .left:focus {
    482     -webkit-box-shadow: none;
    483     box-shadow: none;
    484     outline: none;
     482        -webkit-box-shadow: none;
     483        box-shadow: none;
     484        outline: none;
    485485}
    486486
    487487.theme-overlay .theme-header .left.disabled,
     
    17151715 * HiDPI Displays
    17161716 */
    17171717@media print,
    1718   (-o-min-device-pixel-ratio: 5/4),
    1719   (-webkit-min-device-pixel-ratio: 1.25),
    1720   (min-resolution: 120dpi) {
     1718(-o-min-device-pixel-ratio: 5/4),
     1719(-webkit-min-device-pixel-ratio: 1.25),
     1720(min-resolution: 120dpi) {
    17211721        .wp-full-overlay .collapse-sidebar-arrow {
    17221722                background-image: url(../images/arrows-2x.png);
    17231723                -webkit-background-size: 15px 123px;
     
    17541754                margin-top: 6px;
    17551755                line-height: normal;
    17561756        }
    1757 }
     1757}
     1758 No newline at end of file
  • src/wp-admin/js/theme.js

     
    44( function($) {
    55
    66// Set up our namespace...
    7 var themes, l10n;
    8 themes = wp.themes = wp.themes || {};
     7        var themes, l10n;
     8        themes = wp.themes = wp.themes || {};
    99
    1010// Store the theme data and settings for organized and quick access
    1111// themes.data.settings, themes.data.themes, themes.data.l10n
    12 themes.data = _wpThemeSettings;
    13 l10n = themes.data.l10n;
     12        themes.data = _wpThemeSettings;
     13        l10n = themes.data.l10n;
    1414
    1515// Shortcut for isInstall check
    16 themes.isInstall = !! themes.data.settings.isInstall;
     16        themes.isInstall = !! themes.data.settings.isInstall;
    1717
    1818// Setup app structure
    19 _.extend( themes, { model: {}, view: {}, routes: {}, router: {}, template: wp.template });
     19        _.extend( themes, { model: {}, view: {}, routes: {}, router: {}, template: wp.template });
    2020
    21 themes.Model = Backbone.Model.extend({
    22         // Adds attributes to the default data coming through the .org themes api
    23         // Map `id` to `slug` for shared code
    24         initialize: function() {
    25                 var description;
     21        themes.Model = Backbone.Model.extend({
     22                // Adds attributes to the default data coming through the .org themes api
     23                // Map `id` to `slug` for shared code
     24                initialize: function() {
     25                        var description;
    2626
    27                 // If theme is already installed, set an attribute.
    28                 if ( _.indexOf( themes.data.installedThemes, this.get( 'slug' ) ) !== -1 ) {
    29                         this.set({ installed: true });
    30                 }
     27                        // If theme is already installed, set an attribute.
     28                        if ( _.indexOf( themes.data.installedThemes, this.get( 'slug' ) ) !== -1 ) {
     29                                this.set({ installed: true });
     30                        }
    3131
    32                 // Set the attributes
    33                 this.set({
    34                         // slug is for installation, id is for existing.
    35                         id: this.get( 'slug' ) || this.get( 'id' )
    36                 });
     32                        // Set the attributes
     33                        this.set({
     34                                // slug is for installation, id is for existing.
     35                                id: this.get( 'slug' ) || this.get( 'id' )
     36                        });
    3737
    38                 // Map `section.description` to `description`
    39                 // as the API sometimes returns it differently
    40                 if ( this.has( 'sections' ) ) {
    41                         description = this.get( 'sections' ).description;
    42                         this.set({ description: description });
     38                        // Map `section.description` to `description`
     39                        // as the API sometimes returns it differently
     40                        if ( this.has( 'sections' ) ) {
     41                                description = this.get( 'sections' ).description;
     42                                this.set({ description: description });
     43                        }
    4344                }
    44         }
    45 });
     45        });
    4646
    4747// Main view controller for themes.php
    4848// Unifies and renders all available views
    49 themes.view.Appearance = wp.Backbone.View.extend({
     49        themes.view.Appearance = wp.Backbone.View.extend({
    5050
    51         el: '#wpbody-content .wrap .theme-browser',
     51                el: '#wpbody-content .wrap .theme-browser',
    5252
    53         window: $( window ),
    54         // Pagination instance
    55         page: 0,
     53                window: $( window ),
     54                // Pagination instance
     55                page: 0,
    5656
    57         // Sets up a throttler for binding to 'scroll'
    58         initialize: function( options ) {
    59                 // Scroller checks how far the scroll position is
    60                 _.bindAll( this, 'scroller' );
     57                // Sets up a throttler for binding to 'scroll'
     58                initialize: function( options ) {
     59                        // Scroller checks how far the scroll position is
     60                        _.bindAll( this, 'scroller' );
    6161
    62                 this.SearchView = options.SearchView ? options.SearchView : themes.view.Search;
    63                 // Bind to the scroll event and throttle
    64                 // the results from this.scroller
    65                 this.window.bind( 'scroll', _.throttle( this.scroller, 300 ) );
    66         },
     62                        this.SearchView = options.SearchView ? options.SearchView : themes.view.Search;
     63                        // Bind to the scroll event and throttle
     64                        // the results from this.scroller
     65                        this.window.bind( 'scroll', _.throttle( this.scroller, 300 ) );
     66                },
    6767
    68         // Main render control
    69         render: function() {
    70                 // Setup the main theme view
    71                 // with the current theme collection
    72                 this.view = new themes.view.Themes({
    73                         collection: this.collection,
    74                         parent: this
    75                 });
     68                // Main render control
     69                render: function() {
     70                        // Setup the main theme view
     71                        // with the current theme collection
     72                        this.view = new themes.view.Themes({
     73                                collection: this.collection,
     74                                parent: this
     75                        });
    7676
    77                 // Render search form.
    78                 this.search();
     77                        // Render search form.
     78                        this.search();
    7979
    80                 // Render and append
    81                 this.view.render();
    82                 this.$el.empty().append( this.view.el ).addClass('rendered');
    83                 this.$el.append( '<br class="clear"/>' );
    84         },
     80                        // Render and append
     81                        this.view.render();
     82                        this.$el.empty().append( this.view.el ).addClass('rendered');
     83                        this.$el.append( '<br class="clear"/>' );
     84                },
    8585
    86         // Defines search element container
    87         searchContainer: $( '#wpbody h2:first' ),
     86                // Defines search element container
     87                searchContainer: $( '#wpbody .add-new-theme' ),
    8888
    89         // Search input and view
    90         // for current theme collection
    91         search: function() {
    92                 var view,
    93                         self = this;
     89                // Search input and view
     90                // for current theme collection
     91                search: function() {
     92                        var view,
     93                                self = this;
    9494
    95                 // Don't render the search if there is only one theme
    96                 if ( themes.data.themes.length === 1 ) {
    97                         return;
    98                 }
     95                        // Don't render the search if there is only one theme
     96                        if ( themes.data.themes.length === 1 ) {
     97                                return;
     98                        }
    9999
    100                 view = new this.SearchView({
    101                         collection: self.collection,
    102                         parent: this
    103                 });
     100                        view = new this.SearchView({
     101                                collection: self.collection,
     102                                parent: this
     103                        });
    104104
    105                 // Render and append after screen title
    106                 view.render();
    107                 this.searchContainer
    108                         .append( $.parseHTML( '<label class="screen-reader-text" for="wp-filter-search-input">' + l10n.search + '</label>' ) )
    109                         .append( view.el );
    110         },
     105                        // Render and append after screen title
     106                        view.render();
     107                        this.searchContainer
     108                                .append( $.parseHTML( '<label class="screen-reader-text" for="wp-filter-search-input">' + l10n.search + '</label>' ) )
     109                                .append( view.el );
     110                },
    111111
    112         // Checks when the user gets close to the bottom
    113         // of the mage and triggers a theme:scroll event
    114         scroller: function() {
    115                 var self = this,
    116                         bottom, threshold;
     112                // Checks when the user gets close to the bottom
     113                // of the mage and triggers a theme:scroll event
     114                scroller: function() {
     115                        var self = this,
     116                                bottom, threshold;
    117117
    118                 bottom = this.window.scrollTop() + self.window.height();
    119                 threshold = self.$el.offset().top + self.$el.outerHeight( false ) - self.window.height();
    120                 threshold = Math.round( threshold * 0.9 );
     118                        bottom = this.window.scrollTop() + self.window.height();
     119                        threshold = self.$el.offset().top + self.$el.outerHeight( false ) - self.window.height();
     120                        threshold = Math.round( threshold * 0.9 );
    121121
    122                 if ( bottom > threshold ) {
    123                         this.trigger( 'theme:scroll' );
     122                        if ( bottom > threshold ) {
     123                                this.trigger( 'theme:scroll' );
     124                        }
    124125                }
    125         }
    126 });
     126        });
    127127
    128128// Set up the Collection for our theme data
    129129// @has 'id' 'name' 'screenshot' 'author' 'authorURI' 'version' 'active' ...
    130 themes.Collection = Backbone.Collection.extend({
     130        themes.Collection = Backbone.Collection.extend({
    131131
    132         model: themes.Model,
     132                model: themes.Model,
    133133
    134         // Search terms
    135         terms: '',
     134                // Search terms
     135                terms: '',
    136136
    137         // Controls searching on the current theme collection
    138         // and triggers an update event
    139         doSearch: function( value ) {
     137                // Controls searching on the current theme collection
     138                // and triggers an update event
     139                doSearch: function( value ) {
    140140
    141                 // Don't do anything if we've already done this search
    142                 // Useful because the Search handler fires multiple times per keystroke
    143                 if ( this.terms === value ) {
    144                         return;
    145                 }
     141                        // Don't do anything if we've already done this search
     142                        // Useful because the Search handler fires multiple times per keystroke
     143                        if ( this.terms === value ) {
     144                                return;
     145                        }
    146146
    147                 // Updates terms with the value passed
    148                 this.terms = value;
     147                        // Updates terms with the value passed
     148                        this.terms = value;
    149149
    150                 // If we have terms, run a search...
    151                 if ( this.terms.length > 0 ) {
    152                         this.search( this.terms );
    153                 }
     150                        // If we have terms, run a search...
     151                        if ( this.terms.length > 0 ) {
     152                                this.search( this.terms );
     153                        }
    154154
    155                 // If search is blank, show all themes
    156                 // Useful for resetting the views when you clean the input
    157                 if ( this.terms === '' ) {
    158                         this.reset( themes.data.themes );
    159                 }
     155                        // If search is blank, show all themes
     156                        // Useful for resetting the views when you clean the input
     157                        if ( this.terms === '' ) {
     158                                this.reset( themes.data.themes );
     159                        }
    160160
    161                 // Trigger an 'update' event
    162                 this.trigger( 'update' );
    163         },
     161                        // Trigger an 'update' event
     162                        this.trigger( 'update' );
     163                },
    164164
    165         // Performs a search within the collection
    166         // @uses RegExp
    167         search: function( term ) {
    168                 var match, results, haystack, name, description, author;
     165                // Performs a search within the collection
     166                // @uses RegExp
     167                search: function( term ) {
     168                        var match, results, haystack, name, description, author;
    169169
    170                 // Start with a full collection
    171                 this.reset( themes.data.themes, { silent: true } );
     170                        // Start with a full collection
     171                        this.reset( themes.data.themes, { silent: true } );
    172172
    173                 // Escape the term string for RegExp meta characters
    174                 term = term.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' );
     173                        // Escape the term string for RegExp meta characters
     174                        term = term.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' );
    175175
    176                 // Consider spaces as word delimiters and match the whole string
    177                 // so matching terms can be combined
    178                 term = term.replace( / /g, ')(?=.*' );
    179                 match = new RegExp( '^(?=.*' + term + ').+', 'i' );
     176                        // Consider spaces as word delimiters and match the whole string
     177                        // so matching terms can be combined
     178                        term = term.replace( / /g, ')(?=.*' );
     179                        match = new RegExp( '^(?=.*' + term + ').+', 'i' );
    180180
    181                 // Find results
    182                 // _.filter and .test
    183                 results = this.filter( function( data ) {
    184                         name        = data.get( 'name' ).replace( /(<([^>]+)>)/ig, '' );
    185                         description = data.get( 'description' ).replace( /(<([^>]+)>)/ig, '' );
    186                         author      = data.get( 'author' ).replace( /(<([^>]+)>)/ig, '' );
     181                        // Find results
     182                        // _.filter and .test
     183                        results = this.filter( function( data ) {
     184                                name        = data.get( 'name' ).replace( /(<([^>]+)>)/ig, '' );
     185                                description = data.get( 'description' ).replace( /(<([^>]+)>)/ig, '' );
     186                                author      = data.get( 'author' ).replace( /(<([^>]+)>)/ig, '' );
    187187
    188                         haystack = _.union( name, data.get( 'id' ), description, author, data.get( 'tags' ) );
     188                                haystack = _.union( name, data.get( 'id' ), description, author, data.get( 'tags' ) );
    189189
    190                         if ( match.test( data.get( 'author' ) ) && term.length > 2 ) {
    191                                 data.set( 'displayAuthor', true );
    192                         }
     190                                if ( match.test( data.get( 'author' ) ) && term.length > 2 ) {
     191                                        data.set( 'displayAuthor', true );
     192                                }
    193193
    194                         return match.test( haystack );
    195                 });
     194                                return match.test( haystack );
     195                        });
    196196
    197                 if ( results.length === 0 ) {
    198                         this.trigger( 'query:empty' );
    199                 } else {
    200                         $( 'body' ).removeClass( 'no-results' );
    201                 }
     197                        if ( results.length === 0 ) {
     198                                this.trigger( 'query:empty' );
     199                        } else {
     200                                $( 'body' ).removeClass( 'no-results' );
     201                        }
    202202
    203                 this.reset( results );
    204         },
     203                        this.reset( results );
     204                },
    205205
    206         // Paginates the collection with a helper method
    207         // that slices the collection
    208         paginate: function( instance ) {
    209                 var collection = this;
    210                 instance = instance || 0;
     206                // Paginates the collection with a helper method
     207                // that slices the collection
     208                paginate: function( instance ) {
     209                        var collection = this;
     210                        instance = instance || 0;
    211211
    212                 // Themes per instance are set at 20
    213                 collection = _( collection.rest( 20 * instance ) );
    214                 collection = _( collection.first( 20 ) );
     212                        // Themes per instance are set at 20
     213                        collection = _( collection.rest( 20 * instance ) );
     214                        collection = _( collection.first( 20 ) );
    215215
    216                 return collection;
    217         },
     216                        return collection;
     217                },
    218218
    219         count: false,
     219                count: false,
    220220
    221         // Handles requests for more themes
    222         // and caches results
    223         //
    224         // When we are missing a cache object we fire an apiCall()
    225         // which triggers events of `query:success` or `query:fail`
    226         query: function( request ) {
    227                 /**
    228                 * @static
    229                 * @type Array
    230                 */
    231                 var queries = this.queries,
    232                         self = this,
    233                         query, isPaginated, count;
     221                // Handles requests for more themes
     222                // and caches results
     223                //
     224                // When we are missing a cache object we fire an apiCall()
     225                // which triggers events of `query:success` or `query:fail`
     226                query: function( request ) {
     227                        /**
     228                        * @static
     229                        * @type Array
     230                        */
     231                        var queries = this.queries,
     232                                self = this,
     233                                query, isPaginated, count;
    234234
    235                 // Store current query request args
    236                 // for later use with the event `theme:end`
    237                 this.currentQuery.request = request;
     235                        // Store current query request args
     236                        // for later use with the event `theme:end`
     237                        this.currentQuery.request = request;
    238238
    239                 // Search the query cache for matches.
    240                 query = _.find( queries, function( query ) {
    241                         return _.isEqual( query.request, request );
    242                 });
     239                        // Search the query cache for matches.
     240                        query = _.find( queries, function( query ) {
     241                                return _.isEqual( query.request, request );
     242                        });
    243243
    244                 // If the request matches the stored currentQuery.request
    245                 // it means we have a paginated request.
    246                 isPaginated = _.has( request, 'page' );
     244                        // If the request matches the stored currentQuery.request
     245                        // it means we have a paginated request.
     246                        isPaginated = _.has( request, 'page' );
    247247
    248                 // Reset the internal api page counter for non paginated queries.
    249                 if ( ! isPaginated ) {
    250                         this.currentQuery.page = 1;
    251                 }
     248                        // Reset the internal api page counter for non paginated queries.
     249                        if ( ! isPaginated ) {
     250                                this.currentQuery.page = 1;
     251                        }
    252252
    253                 // Otherwise, send a new API call and add it to the cache.
    254                 if ( ! query && ! isPaginated ) {
    255                         query = this.apiCall( request ).done( function( data ) {
     253                        // Otherwise, send a new API call and add it to the cache.
     254                        if ( ! query && ! isPaginated ) {
     255                                query = this.apiCall( request ).done( function( data ) {
    256256
    257                                 // Update the collection with the queried data.
    258                                 if ( data.themes ) {
    259                                         self.reset( data.themes );
    260                                         count = data.info.results;
    261                                         // Store the results and the query request
    262                                         queries.push( { themes: data.themes, request: request, total: count } );
    263                                 }
     257                                        // Update the collection with the queried data.
     258                                        if ( data.themes ) {
     259                                                self.reset( data.themes );
     260                                                count = data.info.results;
     261                                                // Store the results and the query request
     262                                                queries.push( { themes: data.themes, request: request, total: count } );
     263                                        }
    264264
    265                                 // Trigger a collection refresh event
    266                                 // and a `query:success` event with a `count` argument.
    267                                 self.trigger( 'update' );
    268                                 self.trigger( 'query:success', count );
     265                                        // Trigger a collection refresh event
     266                                        // and a `query:success` event with a `count` argument.
     267                                        self.trigger( 'update' );
     268                                        self.trigger( 'query:success', count );
    269269
    270                                 if ( data.themes && data.themes.length === 0 ) {
    271                                         self.trigger( 'query:empty' );
    272                                 }
     270                                        if ( data.themes && data.themes.length === 0 ) {
     271                                                self.trigger( 'query:empty' );
     272                                        }
    273273
    274                         }).fail( function() {
    275                                 self.trigger( 'query:fail' );
    276                         });
    277                 } else {
    278                         // If it's a paginated request we need to fetch more themes...
    279                         if ( isPaginated ) {
    280                                 return this.apiCall( request, isPaginated ).done( function( data ) {
    281                                         // Add the new themes to the current collection
    282                                         // @todo update counter
    283                                         self.add( data.themes );
    284                                         self.trigger( 'query:success' );
    285 
    286                                         // We are done loading themes for now.
    287                                         self.loadingThemes = false;
    288 
    289274                                }).fail( function() {
    290275                                        self.trigger( 'query:fail' );
    291276                                });
    292                         }
    293 
    294                         if ( query.themes.length === 0 ) {
    295                                 self.trigger( 'query:empty' );
    296277                        } else {
    297                                 $( 'body' ).removeClass( 'no-results' );
    298                         }
     278                                // If it's a paginated request we need to fetch more themes...
     279                                if ( isPaginated ) {
     280                                        return this.apiCall( request, isPaginated ).done( function( data ) {
     281                                                // Add the new themes to the current collection
     282                                                // @todo update counter
     283                                                self.add( data.themes );
     284                                                self.trigger( 'query:success' );
    299285
    300                         // Only trigger an update event since we already have the themes
    301                         // on our cached object
    302                         if ( _.isNumber( query.total ) ) {
    303                                 this.count = query.total;
    304                         }
     286                                                // We are done loading themes for now.
     287                                                self.loadingThemes = false;
    305288
    306                         this.reset( query.themes );
    307                         if ( ! query.total ) {
    308                                 this.count = this.length;
     289                                        }).fail( function() {
     290                                                self.trigger( 'query:fail' );
     291                                        });
     292                                }
     293
     294                                if ( query.themes.length === 0 ) {
     295                                        self.trigger( 'query:empty' );
     296                                } else {
     297                                        $( 'body' ).removeClass( 'no-results' );
     298                                }
     299
     300                                // Only trigger an update event since we already have the themes
     301                                // on our cached object
     302                                if ( _.isNumber( query.total ) ) {
     303                                        this.count = query.total;
     304                                }
     305
     306                                this.reset( query.themes );
     307                                if ( ! query.total ) {
     308                                        this.count = this.length;
     309                                }
     310
     311                                this.trigger( 'update' );
     312                                this.trigger( 'query:success', this.count );
    309313                        }
     314                },
    310315
    311                         this.trigger( 'update' );
    312                         this.trigger( 'query:success', this.count );
    313                 }
    314         },
     316                // Local cache array for API queries
     317                queries: [],
    315318
    316         // Local cache array for API queries
    317         queries: [],
     319                // Keep track of current query so we can handle pagination
     320                currentQuery: {
     321                        page: 1,
     322                        request: {}
     323                },
    318324
    319         // Keep track of current query so we can handle pagination
    320         currentQuery: {
    321                 page: 1,
    322                 request: {}
    323         },
     325                // Send request to api.wordpress.org/themes
     326                apiCall: function( request, paginated ) {
     327                        return wp.ajax.send( 'query-themes', {
     328                                data: {
     329                                        // Request data
     330                                        request: _.extend({
     331                                                per_page: 100,
     332                                                fields: {
     333                                                        description: true,
     334                                                        tested: true,
     335                                                        requires: true,
     336                                                        rating: true,
     337                                                        downloaded: true,
     338                                                        downloadLink: true,
     339                                                        last_updated: true,
     340                                                        homepage: true,
     341                                                        num_ratings: true
     342                                                }
     343                                        }, request)
     344                                },
    324345
    325         // Send request to api.wordpress.org/themes
    326         apiCall: function( request, paginated ) {
    327                 return wp.ajax.send( 'query-themes', {
    328                         data: {
    329                         // Request data
    330                                 request: _.extend({
    331                                         per_page: 100,
    332                                         fields: {
    333                                                 description: true,
    334                                                 tested: true,
    335                                                 requires: true,
    336                                                 rating: true,
    337                                                 downloaded: true,
    338                                                 downloadLink: true,
    339                                                 last_updated: true,
    340                                                 homepage: true,
    341                                                 num_ratings: true
     346                                beforeSend: function() {
     347                                        if ( ! paginated ) {
     348                                                // Spin it
     349                                                $( 'body' ).addClass( 'loading-content' ).removeClass( 'no-results' );
    342350                                        }
    343                                 }, request)
    344                         },
    345 
    346                         beforeSend: function() {
    347                                 if ( ! paginated ) {
    348                                         // Spin it
    349                                         $( 'body' ).addClass( 'loading-content' ).removeClass( 'no-results' );
    350351                                }
    351                         }
    352                 });
    353         },
     352                        });
     353                },
    354354
    355         // Static status controller for when we are loading themes.
    356         loadingThemes: false
    357 });
     355                // Static status controller for when we are loading themes.
     356                loadingThemes: false
     357        });
    358358
    359359// This is the view that controls each theme item
    360360// that will be displayed on the screen
    361 themes.view.Theme = wp.Backbone.View.extend({
     361        themes.view.Theme = wp.Backbone.View.extend({
    362362
    363         // Wrap theme data on a div.theme element
    364         className: 'theme',
     363                // Wrap theme data on a div.theme element
     364                className: 'theme',
    365365
    366         // Reflects which theme view we have
    367         // 'grid' (default) or 'detail'
    368         state: 'grid',
     366                // Reflects which theme view we have
     367                // 'grid' (default) or 'detail'
     368                state: 'grid',
    369369
    370         // The HTML template for each element to be rendered
    371         html: themes.template( 'theme' ),
     370                // The HTML template for each element to be rendered
     371                html: themes.template( 'theme' ),
    372372
    373         events: {
    374                 'click': themes.isInstall ? 'preview': 'expand',
    375                 'keydown': themes.isInstall ? 'preview': 'expand',
    376                 'touchend': themes.isInstall ? 'preview': 'expand',
    377                 'keyup': 'addFocus',
    378                 'touchmove': 'preventExpand'
    379         },
     373                events: {
     374                        'click': themes.isInstall ? 'preview': 'expand',
     375                        'keydown': themes.isInstall ? 'preview': 'expand',
     376                        'touchend': themes.isInstall ? 'preview': 'expand',
     377                        'keyup': 'addFocus',
     378                        'touchmove': 'preventExpand'
     379                },
    380380
    381         touchDrag: false,
     381                touchDrag: false,
    382382
    383         render: function() {
    384                 var data = this.model.toJSON();
    385                 // Render themes using the html template
    386                 this.$el.html( this.html( data ) ).attr({
    387                         tabindex: 0,
    388                         'aria-describedby' : data.id + '-action ' + data.id + '-name'
    389                 });
     383                render: function() {
     384                        var data = this.model.toJSON();
     385                        // Render themes using the html template
     386                        this.$el.html( this.html( data ) ).attr({
     387                                tabindex: 0,
     388                                'aria-describedby' : data.id + '-action ' + data.id + '-name'
     389                        });
    390390
    391                 // Renders active theme styles
    392                 this.activeTheme();
     391                        // Renders active theme styles
     392                        this.activeTheme();
    393393
    394                 if ( this.model.get( 'displayAuthor' ) ) {
    395                         this.$el.addClass( 'display-author' );
    396                 }
     394                        if ( this.model.get( 'displayAuthor' ) ) {
     395                                this.$el.addClass( 'display-author' );
     396                        }
    397397
    398                 if ( this.model.get( 'installed' ) ) {
    399                         this.$el.addClass( 'is-installed' );
    400                 }
    401         },
     398                        if ( this.model.get( 'installed' ) ) {
     399                                this.$el.addClass( 'is-installed' );
     400                        }
     401                },
    402402
    403         // Adds a class to the currently active theme
    404         // and to the overlay in detailed view mode
    405         activeTheme: function() {
    406                 if ( this.model.get( 'active' ) ) {
    407                         this.$el.addClass( 'active' );
    408                 }
    409         },
     403                // Adds a class to the currently active theme
     404                // and to the overlay in detailed view mode
     405                activeTheme: function() {
     406                        if ( this.model.get( 'active' ) ) {
     407                                this.$el.addClass( 'active' );
     408                        }
     409                },
    410410
    411         // Add class of focus to the theme we are focused on.
    412         addFocus: function() {
    413                 var $themeToFocus = ( $( ':focus' ).hasClass( 'theme' ) ) ? $( ':focus' ) : $(':focus').parents('.theme');
     411                // Add class of focus to the theme we are focused on.
     412                addFocus: function() {
     413                        var $themeToFocus = ( $( ':focus' ).hasClass( 'theme' ) ) ? $( ':focus' ) : $(':focus').parents('.theme');
    414414
    415                 $('.theme.focus').removeClass('focus');
    416                 $themeToFocus.addClass('focus');
    417         },
     415                        $('.theme.focus').removeClass('focus');
     416                        $themeToFocus.addClass('focus');
     417                },
    418418
    419         // Single theme overlay screen
    420         // It's shown when clicking a theme
    421         expand: function( event ) {
    422                 var self = this;
     419                // Single theme overlay screen
     420                // It's shown when clicking a theme
     421                expand: function( event ) {
     422                        var self = this;
    423423
    424                 event = event || window.event;
     424                        event = event || window.event;
    425425
    426                 // 'enter' and 'space' keys expand the details view when a theme is :focused
    427                 if ( event.type === 'keydown' && ( event.which !== 13 && event.which !== 32 ) ) {
    428                         return;
    429                 }
     426                        // 'enter' and 'space' keys expand the details view when a theme is :focused
     427                        if ( event.type === 'keydown' && ( event.which !== 13 && event.which !== 32 ) ) {
     428                                return;
     429                        }
    430430
    431                 // Bail if the user scrolled on a touch device
    432                 if ( this.touchDrag === true ) {
    433                         return this.touchDrag = false;
    434                 }
     431                        // Bail if the user scrolled on a touch device
     432                        if ( this.touchDrag === true ) {
     433                                return this.touchDrag = false;
     434                        }
    435435
    436                 // Prevent the modal from showing when the user clicks
    437                 // one of the direct action buttons
    438                 if ( $( event.target ).is( '.theme-actions a' ) ) {
    439                         return;
    440                 }
     436                        // Prevent the modal from showing when the user clicks
     437                        // one of the direct action buttons
     438                        if ( $( event.target ).is( '.theme-actions a' ) ) {
     439                                return;
     440                        }
    441441
    442                 // Set focused theme to current element
    443                 themes.focusedTheme = this.$el;
     442                        // Set focused theme to current element
     443                        themes.focusedTheme = this.$el;
    444444
    445                 this.trigger( 'theme:expand', self.model.cid );
    446         },
     445                        this.trigger( 'theme:expand', self.model.cid );
     446                },
    447447
    448         preventExpand: function() {
    449                 this.touchDrag = true;
    450         },
     448                preventExpand: function() {
     449                        this.touchDrag = true;
     450                },
    451451
    452         preview: function( event ) {
    453                 var self = this,
    454                         current, preview;
     452                preview: function( event ) {
     453                        var self = this,
     454                                current, preview;
    455455
    456                 // Bail if the user scrolled on a touch device
    457                 if ( this.touchDrag === true ) {
    458                         return this.touchDrag = false;
    459                 }
     456                        // Bail if the user scrolled on a touch device
     457                        if ( this.touchDrag === true ) {
     458                                return this.touchDrag = false;
     459                        }
    460460
    461                 // Allow direct link path to installing a theme.
    462                 if ( $( event.target ).hasClass( 'button-primary' ) ) {
    463                         return;
    464                 }
     461                        // Allow direct link path to installing a theme.
     462                        if ( $( event.target ).hasClass( 'button-primary' ) ) {
     463                                return;
     464                        }
    465465
    466                 // 'enter' and 'space' keys expand the details view when a theme is :focused
    467                 if ( event.type === 'keydown' && ( event.which !== 13 && event.which !== 32 ) ) {
    468                         return;
    469                 }
     466                        // 'enter' and 'space' keys expand the details view when a theme is :focused
     467                        if ( event.type === 'keydown' && ( event.which !== 13 && event.which !== 32 ) ) {
     468                                return;
     469                        }
    470470
    471                 // pressing enter while focused on the buttons shouldn't open the preview
    472                 if ( event.type === 'keydown' && event.which !== 13 && $( ':focus' ).hasClass( 'button' ) ) {
    473                         return;
    474                 }
     471                        // pressing enter while focused on the buttons shouldn't open the preview
     472                        if ( event.type === 'keydown' && event.which !== 13 && $( ':focus' ).hasClass( 'button' ) ) {
     473                                return;
     474                        }
    475475
    476                 event.preventDefault();
     476                        event.preventDefault();
    477477
    478                 event = event || window.event;
     478                        event = event || window.event;
    479479
    480                 // Set focus to current theme.
    481                 themes.focusedTheme = this.$el;
     480                        // Set focus to current theme.
     481                        themes.focusedTheme = this.$el;
    482482
    483                 // Construct a new Preview view.
    484                 preview = new themes.view.Preview({
    485                         model: this.model
    486                 });
     483                        // Construct a new Preview view.
     484                        preview = new themes.view.Preview({
     485                                model: this.model
     486                        });
    487487
    488                 // Render the view and append it.
    489                 preview.render();
    490                 this.setNavButtonsState();
     488                        // Render the view and append it.
     489                        preview.render();
     490                        this.setNavButtonsState();
    491491
    492                 // Hide previous/next navigation if there is only one theme
    493                 if ( this.model.collection.length === 1 ) {
    494                         preview.$el.addClass( 'no-navigation' );
    495                 } else {
    496                         preview.$el.removeClass( 'no-navigation' );
    497                 }
     492                        // Hide previous/next navigation if there is only one theme
     493                        if ( this.model.collection.length === 1 ) {
     494                                preview.$el.addClass( 'no-navigation' );
     495                        } else {
     496                                preview.$el.removeClass( 'no-navigation' );
     497                        }
    498498
    499                 // Append preview
    500                 $( 'div.wrap' ).append( preview.el );
     499                        // Append preview
     500                        $( 'div.wrap' ).append( preview.el );
    501501
    502                 // Listen to our preview object
    503                 // for `theme:next` and `theme:previous` events.
    504                 this.listenTo( preview, 'theme:next', function() {
     502                        // Listen to our preview object
     503                        // for `theme:next` and `theme:previous` events.
     504                        this.listenTo( preview, 'theme:next', function() {
    505505
    506                         // Keep local track of current theme model.
    507                         current = self.model;
     506                                // Keep local track of current theme model.
     507                                current = self.model;
    508508
    509                         // If we have ventured away from current model update the current model position.
    510                         if ( ! _.isUndefined( self.current ) ) {
    511                                 current = self.current;
    512                         }
     509                                // If we have ventured away from current model update the current model position.
     510                                if ( ! _.isUndefined( self.current ) ) {
     511                                        current = self.current;
     512                                }
    513513
    514                         // Get next theme model.
    515                         self.current = self.model.collection.at( self.model.collection.indexOf( current ) + 1 );
     514                                // Get next theme model.
     515                                self.current = self.model.collection.at( self.model.collection.indexOf( current ) + 1 );
    516516
    517                         // If we have no more themes, bail.
    518                         if ( _.isUndefined( self.current ) ) {
    519                                 self.options.parent.parent.trigger( 'theme:end' );
    520                                 return self.current = current;
    521                         }
     517                                // If we have no more themes, bail.
     518                                if ( _.isUndefined( self.current ) ) {
     519                                        self.options.parent.parent.trigger( 'theme:end' );
     520                                        return self.current = current;
     521                                }
    522522
    523                         preview.model = self.current;
     523                                preview.model = self.current;
    524524
    525                         // Render and append.
    526                         preview.render();
    527                         this.setNavButtonsState();
    528                         $( '.next-theme' ).focus();
    529                 })
    530                 .listenTo( preview, 'theme:previous', function() {
     525                                // Render and append.
     526                                preview.render();
     527                                this.setNavButtonsState();
     528                                $( '.next-theme' ).focus();
     529                        })
     530                                .listenTo( preview, 'theme:previous', function() {
    531531
    532                         // Keep track of current theme model.
    533                         current = self.model;
     532                                        // Keep track of current theme model.
     533                                        current = self.model;
    534534
    535                         // Bail early if we are at the beginning of the collection
    536                         if ( self.model.collection.indexOf( self.current ) === 0 ) {
    537                                 return;
    538                         }
     535                                        // Bail early if we are at the beginning of the collection
     536                                        if ( self.model.collection.indexOf( self.current ) === 0 ) {
     537                                                return;
     538                                        }
    539539
    540                         // If we have ventured away from current model update the current model position.
    541                         if ( ! _.isUndefined( self.current ) ) {
    542                                 current = self.current;
    543                         }
     540                                        // If we have ventured away from current model update the current model position.
     541                                        if ( ! _.isUndefined( self.current ) ) {
     542                                                current = self.current;
     543                                        }
    544544
    545                         // Get previous theme model.
    546                         self.current = self.model.collection.at( self.model.collection.indexOf( current ) - 1 );
     545                                        // Get previous theme model.
     546                                        self.current = self.model.collection.at( self.model.collection.indexOf( current ) - 1 );
    547547
    548                         // If we have no more themes, bail.
    549                         if ( _.isUndefined( self.current ) ) {
    550                                 return;
    551                         }
     548                                        // If we have no more themes, bail.
     549                                        if ( _.isUndefined( self.current ) ) {
     550                                                return;
     551                                        }
    552552
    553                         preview.model = self.current;
     553                                        preview.model = self.current;
    554554
    555                         // Render and append.
    556                         preview.render();
    557                         this.setNavButtonsState();
    558                         $( '.previous-theme' ).focus();
    559                 });
     555                                        // Render and append.
     556                                        preview.render();
     557                                        this.setNavButtonsState();
     558                                        $( '.previous-theme' ).focus();
     559                                });
    560560
    561                 this.listenTo( preview, 'preview:close', function() {
    562                         self.current = self.model;
    563                 });
    564         },
     561                        this.listenTo( preview, 'preview:close', function() {
     562                                self.current = self.model;
     563                        });
     564                },
    565565
    566         // Handles .disabled classes for previous/next buttons in theme installer preview
    567         setNavButtonsState: function() {
    568                 var $themeInstaller = $( '.theme-install-overlay' ),
    569                         current = _.isUndefined( this.current ) ? this.model : this.current;
     566                // Handles .disabled classes for previous/next buttons in theme installer preview
     567                setNavButtonsState: function() {
     568                        var $themeInstaller = $( '.theme-install-overlay' ),
     569                                current = _.isUndefined( this.current ) ? this.model : this.current;
    570570
    571                 // Disable previous at the zero position
    572                 if ( 0 === this.model.collection.indexOf( current ) ) {
    573                         $themeInstaller.find( '.previous-theme' ).addClass( 'disabled' );
    574                 }
     571                        // Disable previous at the zero position
     572                        if ( 0 === this.model.collection.indexOf( current ) ) {
     573                                $themeInstaller.find( '.previous-theme' ).addClass( 'disabled' );
     574                        }
    575575
    576                 // Disable next if the next model is undefined
    577                 if ( _.isUndefined( this.model.collection.at( this.model.collection.indexOf( current ) + 1 ) ) ) {
    578                         $themeInstaller.find( '.next-theme' ).addClass( 'disabled' );
     576                        // Disable next if the next model is undefined
     577                        if ( _.isUndefined( this.model.collection.at( this.model.collection.indexOf( current ) + 1 ) ) ) {
     578                                $themeInstaller.find( '.next-theme' ).addClass( 'disabled' );
     579                        }
    579580                }
    580         }
    581 });
     581        });
    582582
    583583// Theme Details view
    584584// Set ups a modal overlay with the expanded theme data
    585 themes.view.Details = wp.Backbone.View.extend({
     585        themes.view.Details = wp.Backbone.View.extend({
    586586
    587         // Wrap theme data on a div.theme element
    588         className: 'theme-overlay',
     587                // Wrap theme data on a div.theme element
     588                className: 'theme-overlay',
    589589
    590         events: {
    591                 'click': 'collapse',
    592                 'click .delete-theme': 'deleteTheme',
    593                 'click .left': 'previousTheme',
    594                 'click .right': 'nextTheme'
    595         },
     590                events: {
     591                        'click': 'collapse',
     592                        'click .delete-theme': 'deleteTheme',
     593                        'click .left': 'previousTheme',
     594                        'click .right': 'nextTheme'
     595                },
    596596
    597         // The HTML template for the theme overlay
    598         html: themes.template( 'theme-single' ),
     597                // The HTML template for the theme overlay
     598                html: themes.template( 'theme-single' ),
    599599
    600         render: function() {
    601                 var data = this.model.toJSON();
    602                 this.$el.html( this.html( data ) );
    603                 // Renders active theme styles
    604                 this.activeTheme();
    605                 // Set up navigation events
    606                 this.navigation();
    607                 // Checks screenshot size
    608                 this.screenshotCheck( this.$el );
    609                 // Contain "tabbing" inside the overlay
    610                 this.containFocus( this.$el );
    611         },
     600                render: function() {
     601                        var data = this.model.toJSON();
     602                        this.$el.html( this.html( data ) );
     603                        // Renders active theme styles
     604                        this.activeTheme();
     605                        // Set up navigation events
     606                        this.navigation();
     607                        // Checks screenshot size
     608                        this.screenshotCheck( this.$el );
     609                        // Contain "tabbing" inside the overlay
     610                        this.containFocus( this.$el );
     611                },
    612612
    613         // Adds a class to the currently active theme
    614         // and to the overlay in detailed view mode
    615         activeTheme: function() {
    616                 // Check the model has the active property
    617                 this.$el.toggleClass( 'active', this.model.get( 'active' ) );
    618         },
     613                // Adds a class to the currently active theme
     614                // and to the overlay in detailed view mode
     615                activeTheme: function() {
     616                        // Check the model has the active property
     617                        this.$el.toggleClass( 'active', this.model.get( 'active' ) );
     618                },
    619619
    620         // Keeps :focus within the theme details elements
    621         containFocus: function( $el ) {
    622                 var $target;
     620                // Keeps :focus within the theme details elements
     621                containFocus: function( $el ) {
     622                        var $target;
    623623
    624                 // Move focus to the primary action
    625                 _.delay( function() {
    626                         $( '.theme-wrap a.button-primary:visible' ).focus();
    627                 }, 500 );
     624                        // Move focus to the primary action
     625                        _.delay( function() {
     626                                $( '.theme-wrap a.button-primary:visible' ).focus();
     627                        }, 500 );
    628628
    629                 $el.on( 'keydown.wp-themes', function( event ) {
     629                        $el.on( 'keydown.wp-themes', function( event ) {
    630630
    631                         // Tab key
    632                         if ( event.which === 9 ) {
    633                                 $target = $( event.target );
     631                                // Tab key
     632                                if ( event.which === 9 ) {
     633                                        $target = $( event.target );
    634634
    635                                 // Keep focus within the overlay by making the last link on theme actions
    636                                 // switch focus to button.left on tabbing and vice versa
    637                                 if ( $target.is( 'button.left' ) && event.shiftKey ) {
    638                                         $el.find( '.theme-actions a:last-child' ).focus();
    639                                         event.preventDefault();
    640                                 } else if ( $target.is( '.theme-actions a:last-child' ) ) {
    641                                         $el.find( 'button.left' ).focus();
    642                                         event.preventDefault();
     635                                        // Keep focus within the overlay by making the last link on theme actions
     636                                        // switch focus to button.left on tabbing and vice versa
     637                                        if ( $target.is( 'button.left' ) && event.shiftKey ) {
     638                                                $el.find( '.theme-actions a:last-child' ).focus();
     639                                                event.preventDefault();
     640                                        } else if ( $target.is( '.theme-actions a:last-child' ) ) {
     641                                                $el.find( 'button.left' ).focus();
     642                                                event.preventDefault();
     643                                        }
    643644                                }
    644                         }
    645                 });
    646         },
     645                        });
     646                },
    647647
    648         // Single theme overlay screen
    649         // It's shown when clicking a theme
    650         collapse: function( event ) {
    651                 var self = this,
    652                         scroll;
     648                // Single theme overlay screen
     649                // It's shown when clicking a theme
     650                collapse: function( event ) {
     651                        var self = this,
     652                                scroll;
    653653
    654                 event = event || window.event;
     654                        event = event || window.event;
    655655
    656                 // Prevent collapsing detailed view when there is only one theme available
    657                 if ( themes.data.themes.length === 1 ) {
    658                         return;
    659                 }
     656                        // Prevent collapsing detailed view when there is only one theme available
     657                        if ( themes.data.themes.length === 1 ) {
     658                                return;
     659                        }
    660660
    661                 // Detect if the click is inside the overlay
    662                 // and don't close it unless the target was
    663                 // the div.back button
    664                 if ( $( event.target ).is( '.theme-backdrop' ) || $( event.target ).is( '.close' ) || event.keyCode === 27 ) {
     661                        // Detect if the click is inside the overlay
     662                        // and don't close it unless the target was
     663                        // the div.back button
     664                        if ( $( event.target ).is( '.theme-backdrop' ) || $( event.target ).is( '.close' ) || event.keyCode === 27 ) {
    665665
    666                         // Add a temporary closing class while overlay fades out
    667                         $( 'body' ).addClass( 'closing-overlay' );
     666                                // Add a temporary closing class while overlay fades out
     667                                $( 'body' ).addClass( 'closing-overlay' );
    668668
    669                         // With a quick fade out animation
    670                         this.$el.fadeOut( 130, function() {
    671                                 // Clicking outside the modal box closes the overlay
    672                                 $( 'body' ).removeClass( 'closing-overlay' );
    673                                 // Handle event cleanup
    674                                 self.closeOverlay();
     669                                // With a quick fade out animation
     670                                this.$el.fadeOut( 130, function() {
     671                                        // Clicking outside the modal box closes the overlay
     672                                        $( 'body' ).removeClass( 'closing-overlay' );
     673                                        // Handle event cleanup
     674                                        self.closeOverlay();
    675675
    676                                 // Get scroll position to avoid jumping to the top
    677                                 scroll = document.body.scrollTop;
     676                                        // Get scroll position to avoid jumping to the top
     677                                        scroll = document.body.scrollTop;
    678678
    679                                 // Clean the url structure
    680                                 themes.router.navigate( themes.router.baseUrl( '' ) );
     679                                        // Clean the url structure
     680                                        themes.router.navigate( themes.router.baseUrl( '' ) );
    681681
    682                                 // Restore scroll position
    683                                 document.body.scrollTop = scroll;
     682                                        // Restore scroll position
     683                                        document.body.scrollTop = scroll;
    684684
    685                                 // Return focus to the theme div
    686                                 if ( themes.focusedTheme ) {
    687                                         themes.focusedTheme.focus();
    688                                 }
    689                         });
    690                 }
    691         },
     685                                        // Return focus to the theme div
     686                                        if ( themes.focusedTheme ) {
     687                                                themes.focusedTheme.focus();
     688                                        }
     689                                });
     690                        }
     691                },
    692692
    693         // Handles .disabled classes for next/previous buttons
    694         navigation: function() {
     693                // Handles .disabled classes for next/previous buttons
     694                navigation: function() {
    695695
    696                 // Disable Left/Right when at the start or end of the collection
    697                 if ( this.model.cid === this.model.collection.at(0).cid ) {
    698                         this.$el.find( '.left' ).addClass( 'disabled' );
    699                 }
    700                 if ( this.model.cid === this.model.collection.at( this.model.collection.length - 1 ).cid ) {
    701                         this.$el.find( '.right' ).addClass( 'disabled' );
    702                 }
    703         },
     696                        // Disable Left/Right when at the start or end of the collection
     697                        if ( this.model.cid === this.model.collection.at(0).cid ) {
     698                                this.$el.find( '.left' ).addClass( 'disabled' );
     699                        }
     700                        if ( this.model.cid === this.model.collection.at( this.model.collection.length - 1 ).cid ) {
     701                                this.$el.find( '.right' ).addClass( 'disabled' );
     702                        }
     703                },
    704704
    705         // Performs the actions to effectively close
    706         // the theme details overlay
    707         closeOverlay: function() {
    708                 $( 'body' ).removeClass( 'modal-open' );
    709                 this.remove();
    710                 this.unbind();
    711                 this.trigger( 'theme:collapse' );
    712         },
     705                // Performs the actions to effectively close
     706                // the theme details overlay
     707                closeOverlay: function() {
     708                        $( 'body' ).removeClass( 'modal-open' );
     709                        this.remove();
     710                        this.unbind();
     711                        this.trigger( 'theme:collapse' );
     712                },
    713713
    714         // Confirmation dialog for deleting a theme
    715         deleteTheme: function() {
    716                 return confirm( themes.data.settings.confirmDelete );
    717         },
     714                // Confirmation dialog for deleting a theme
     715                deleteTheme: function() {
     716                        return confirm( themes.data.settings.confirmDelete );
     717                },
    718718
    719         nextTheme: function() {
    720                 var self = this;
    721                 self.trigger( 'theme:next', self.model.cid );
    722                 return false;
    723         },
     719                nextTheme: function() {
     720                        var self = this;
     721                        self.trigger( 'theme:next', self.model.cid );
     722                        return false;
     723                },
    724724
    725         previousTheme: function() {
    726                 var self = this;
    727                 self.trigger( 'theme:previous', self.model.cid );
    728                 return false;
    729         },
     725                previousTheme: function() {
     726                        var self = this;
     727                        self.trigger( 'theme:previous', self.model.cid );
     728                        return false;
     729                },
    730730
    731         // Checks if the theme screenshot is the old 300px width version
    732         // and adds a corresponding class if it's true
    733         screenshotCheck: function( el ) {
    734                 var screenshot, image;
     731                // Checks if the theme screenshot is the old 300px width version
     732                // and adds a corresponding class if it's true
     733                screenshotCheck: function( el ) {
     734                        var screenshot, image;
    735735
    736                 screenshot = el.find( '.screenshot img' );
    737                 image = new Image();
    738                 image.src = screenshot.attr( 'src' );
     736                        screenshot = el.find( '.screenshot img' );
     737                        image = new Image();
     738                        image.src = screenshot.attr( 'src' );
    739739
    740                 // Width check
    741                 if ( image.width && image.width <= 300 ) {
    742                         el.addClass( 'small-screenshot' );
     740                        // Width check
     741                        if ( image.width && image.width <= 300 ) {
     742                                el.addClass( 'small-screenshot' );
     743                        }
    743744                }
    744         }
    745 });
     745        });
    746746
    747747// Theme Preview view
    748748// Set ups a modal overlay with the expanded theme data
    749 themes.view.Preview = themes.view.Details.extend({
     749        themes.view.Preview = themes.view.Details.extend({
    750750
    751         className: 'wp-full-overlay expanded',
    752         el: '.theme-install-overlay',
     751                className: 'wp-full-overlay expanded',
     752                el: '.theme-install-overlay',
    753753
    754         events: {
    755                 'click .close-full-overlay': 'close',
    756                 'click .collapse-sidebar': 'collapse',
    757                 'click .previous-theme': 'previousTheme',
    758                 'click .next-theme': 'nextTheme',
    759                 'keyup': 'keyEvent'
    760         },
     754                events: {
     755                        'click .close-full-overlay': 'close',
     756                        'click .collapse-sidebar': 'collapse',
     757                        'click .previous-theme': 'previousTheme',
     758                        'click .next-theme': 'nextTheme',
     759                        'keyup': 'keyEvent'
     760                },
    761761
    762         // The HTML template for the theme preview
    763         html: themes.template( 'theme-preview' ),
     762                // The HTML template for the theme preview
     763                html: themes.template( 'theme-preview' ),
    764764
    765         render: function() {
    766                 var data = this.model.toJSON();
     765                render: function() {
     766                        var data = this.model.toJSON();
    767767
    768                 this.$el.html( this.html( data ) );
     768                        this.$el.html( this.html( data ) );
    769769
    770                 themes.router.navigate( themes.router.baseUrl( themes.router.themePath + this.model.get( 'id' ) ), { replace: true } );
     770                        themes.router.navigate( themes.router.baseUrl( themes.router.themePath + this.model.get( 'id' ) ), { replace: true } );
    771771
    772                 this.$el.fadeIn( 200, function() {
    773                         $( 'body' ).addClass( 'theme-installer-active full-overlay-active' );
    774                         $( '.close-full-overlay' ).focus();
    775                 });
    776         },
     772                        this.$el.fadeIn( 200, function() {
     773                                $( 'body' ).addClass( 'theme-installer-active full-overlay-active' );
     774                                $( '.close-full-overlay' ).focus();
     775                        });
     776                },
    777777
    778         close: function() {
    779                 this.$el.fadeOut( 200, function() {
    780                         $( 'body' ).removeClass( 'theme-installer-active full-overlay-active' );
     778                close: function() {
     779                        this.$el.fadeOut( 200, function() {
     780                                $( 'body' ).removeClass( 'theme-installer-active full-overlay-active' );
    781781
    782                         // Return focus to the theme div
    783                         if ( themes.focusedTheme ) {
    784                                 themes.focusedTheme.focus();
    785                         }
    786                 });
     782                                // Return focus to the theme div
     783                                if ( themes.focusedTheme ) {
     784                                        themes.focusedTheme.focus();
     785                                }
     786                        });
    787787
    788                 themes.router.navigate( themes.router.baseUrl( '' ) );
    789                 this.trigger( 'preview:close' );
    790                 this.undelegateEvents();
    791                 this.unbind();
    792                 return false;
    793         },
     788                        themes.router.navigate( themes.router.baseUrl( '' ) );
     789                        this.trigger( 'preview:close' );
     790                        this.undelegateEvents();
     791                        this.unbind();
     792                        return false;
     793                },
    794794
    795         collapse: function() {
     795                collapse: function() {
    796796
    797                 this.$el.toggleClass( 'collapsed' ).toggleClass( 'expanded' );
    798                 return false;
    799         },
     797                        this.$el.toggleClass( 'collapsed' ).toggleClass( 'expanded' );
     798                        return false;
     799                },
    800800
    801         keyEvent: function( event ) {
    802                 // The escape key closes the preview
    803                 if ( event.keyCode === 27 ) {
    804                         this.undelegateEvents();
    805                         this.close();
    806                 }
    807                 // The right arrow key, next theme
    808                 if ( event.keyCode === 39 ) {
    809                         _.once( this.nextTheme() );
    810                 }
     801                keyEvent: function( event ) {
     802                        // The escape key closes the preview
     803                        if ( event.keyCode === 27 ) {
     804                                this.undelegateEvents();
     805                                this.close();
     806                        }
     807                        // The right arrow key, next theme
     808                        if ( event.keyCode === 39 ) {
     809                                _.once( this.nextTheme() );
     810                        }
    811811
    812                 // The left arrow key, previous theme
    813                 if ( event.keyCode === 37 ) {
    814                         this.previousTheme();
     812                        // The left arrow key, previous theme
     813                        if ( event.keyCode === 37 ) {
     814                                this.previousTheme();
     815                        }
    815816                }
    816         }
    817 });
     817        });
    818818
    819819// Controls the rendering of div.themes,
    820820// a wrapper that will hold all the theme elements
    821 themes.view.Themes = wp.Backbone.View.extend({
     821        themes.view.Themes = wp.Backbone.View.extend({
    822822
    823         className: 'themes',
    824         $overlay: $( 'div.theme-overlay' ),
     823                className: 'themes',
     824                $overlay: $( 'div.theme-overlay' ),
    825825
    826         // Number to keep track of scroll position
    827         // while in theme-overlay mode
    828         index: 0,
     826                // Number to keep track of scroll position
     827                // while in theme-overlay mode
     828                index: 0,
    829829
    830         // The theme count element
    831         count: $( '.wp-filter .theme-count' ),
     830                // The theme count element
     831                count: $( '.wp-filter .theme-count' ),
    832832
    833         initialize: function( options ) {
    834                 var self = this;
     833                initialize: function( options ) {
     834                        var self = this;
    835835
    836                 // Set up parent
    837                 this.parent = options.parent;
     836                        // Set up parent
     837                        this.parent = options.parent;
    838838
    839                 // Set current view to [grid]
    840                 this.setView( 'grid' );
     839                        // Set current view to [grid]
     840                        this.setView( 'grid' );
    841841
    842                 // Move the active theme to the beginning of the collection
    843                 self.currentTheme();
    844 
    845                 // When the collection is updated by user input...
    846                 this.listenTo( self.collection, 'update', function() {
    847                         self.parent.page = 0;
     842                        // Move the active theme to the beginning of the collection
    848843                        self.currentTheme();
    849                         self.render( this );
    850                 });
    851844
    852                 // Update theme count to full result set when available.
    853                 this.listenTo( self.collection, 'query:success', function( count ) {
    854                         if ( _.isNumber( count ) ) {
    855                                 self.count.text( count );
    856                         } else {
    857                                 self.count.text( self.collection.length );
    858                         }
    859                 });
     845                        // When the collection is updated by user input...
     846                        this.listenTo( self.collection, 'update', function() {
     847                                self.parent.page = 0;
     848                                self.currentTheme();
     849                                self.render( this );
     850                        });
    860851
    861                 this.listenTo( self.collection, 'query:empty', function() {
    862                         $( 'body' ).addClass( 'no-results' );
    863                 });
     852                        // Update theme count to full result set when available.
     853                        this.listenTo( self.collection, 'query:success', function( count ) {
     854                                if ( _.isNumber( count ) ) {
     855                                        self.count.text( count );
     856                                } else {
     857                                        self.count.text( self.collection.length );
     858                                }
     859                        });
    864860
    865                 this.listenTo( this.parent, 'theme:scroll', function() {
    866                         self.renderThemes( self.parent.page );
    867                 });
     861                        this.listenTo( self.collection, 'query:empty', function() {
     862                                $( 'body' ).addClass( 'no-results' );
     863                        });
    868864
    869                 this.listenTo( this.parent, 'theme:close', function() {
    870                         if ( self.overlay ) {
    871                                 self.overlay.closeOverlay();
    872                         }
    873                 } );
     865                        this.listenTo( this.parent, 'theme:scroll', function() {
     866                                self.renderThemes( self.parent.page );
     867                        });
    874868
    875                 // Bind keyboard events.
    876                 $( 'body' ).on( 'keyup', function( event ) {
    877                         if ( ! self.overlay ) {
    878                                 return;
    879                         }
     869                        this.listenTo( this.parent, 'theme:close', function() {
     870                                if ( self.overlay ) {
     871                                        self.overlay.closeOverlay();
     872                                }
     873                        } );
    880874
    881                         // Pressing the right arrow key fires a theme:next event
    882                         if ( event.keyCode === 39 ) {
    883                                 self.overlay.nextTheme();
    884                         }
     875                        // Bind keyboard events.
     876                        $( 'body' ).on( 'keyup', function( event ) {
     877                                if ( ! self.overlay ) {
     878                                        return;
     879                                }
    885880
    886                         // Pressing the left arrow key fires a theme:previous event
    887                         if ( event.keyCode === 37 ) {
    888                                 self.overlay.previousTheme();
    889                         }
     881                                // Pressing the right arrow key fires a theme:next event
     882                                if ( event.keyCode === 39 ) {
     883                                        self.overlay.nextTheme();
     884                                }
    890885
    891                         // Pressing the escape key fires a theme:collapse event
    892                         if ( event.keyCode === 27 ) {
    893                                 self.overlay.collapse( event );
    894                         }
    895                 });
    896         },
     886                                // Pressing the left arrow key fires a theme:previous event
     887                                if ( event.keyCode === 37 ) {
     888                                        self.overlay.previousTheme();
     889                                }
    897890
    898         // Manages rendering of theme pages
    899         // and keeping theme count in sync
    900         render: function() {
    901                 // Clear the DOM, please
    902                 this.$el.html( '' );
     891                                // Pressing the escape key fires a theme:collapse event
     892                                if ( event.keyCode === 27 ) {
     893                                        self.overlay.collapse( event );
     894                                }
     895                        });
     896                },
    903897
    904                 // If the user doesn't have switch capabilities
    905                 // or there is only one theme in the collection
    906                 // render the detailed view of the active theme
    907                 if ( themes.data.themes.length === 1 ) {
     898                // Manages rendering of theme pages
     899                // and keeping theme count in sync
     900                render: function() {
     901                        // Clear the DOM, please
     902                        this.$el.html( '' );
    908903
    909                         // Constructs the view
    910                         this.singleTheme = new themes.view.Details({
    911                                 model: this.collection.models[0]
    912                         });
     904                        // If the user doesn't have switch capabilities
     905                        // or there is only one theme in the collection
     906                        // render the detailed view of the active theme
     907                        if ( themes.data.themes.length === 1 ) {
    913908
    914                         // Render and apply a 'single-theme' class to our container
    915                         this.singleTheme.render();
    916                         this.$el.addClass( 'single-theme' );
    917                         this.$el.append( this.singleTheme.el );
    918                 }
     909                                // Constructs the view
     910                                this.singleTheme = new themes.view.Details({
     911                                        model: this.collection.models[0]
     912                                });
    919913
    920                 // Generate the themes
    921                 // Using page instance
    922                 // While checking the collection has items
    923                 if ( this.options.collection.size() > 0 ) {
    924                         this.renderThemes( this.parent.page );
    925                 }
     914                                // Render and apply a 'single-theme' class to our container
     915                                this.singleTheme.render();
     916                                this.$el.addClass( 'single-theme' );
     917                                this.$el.append( this.singleTheme.el );
     918                        }
    926919
    927                 // Display a live theme count for the collection
    928                 this.count.text( this.collection.count ? this.collection.count : this.collection.length );
    929         },
     920                        // Generate the themes
     921                        // Using page instance
     922                        // While checking the collection has items
     923                        if ( this.options.collection.size() > 0 ) {
     924                                this.renderThemes( this.parent.page );
     925                        }
    930926
    931         // Iterates through each instance of the collection
    932         // and renders each theme module
    933         renderThemes: function( page ) {
    934                 var self = this;
     927                        // Display a live theme count for the collection
     928                        this.count.text( this.collection.count ? this.collection.count : this.collection.length );
     929                },
    935930
    936                 self.instance = self.collection.paginate( page );
     931                // Iterates through each instance of the collection
     932                // and renders each theme module
     933                renderThemes: function( page ) {
     934                        var self = this;
    937935
    938                 // If we have no more themes bail
    939                 if ( self.instance.size() === 0 ) {
    940                         // Fire a no-more-themes event.
    941                         this.parent.trigger( 'theme:end' );
    942                         return;
    943                 }
     936                        self.instance = self.collection.paginate( page );
    944937
    945                 // Make sure the add-new stays at the end
    946                 if ( page >= 1 ) {
    947                         $( '.add-new-theme' ).remove();
    948                 }
     938                        // If we have no more themes bail
     939                        if ( self.instance.size() === 0 ) {
     940                                // Fire a no-more-themes event.
     941                                this.parent.trigger( 'theme:end' );
     942                                return;
     943                        }
    949944
    950                 // Loop through the themes and setup each theme view
    951                 self.instance.each( function( theme ) {
    952                         self.theme = new themes.view.Theme({
    953                                 model: theme,
    954                                 parent: self
    955                         });
     945                        // Make sure the add-new stays at the end
     946                        if ( page >= 1 ) {
     947                                $( '.add-new-theme' ).remove();
     948                        }
    956949
    957                         // Render the views...
    958                         self.theme.render();
    959                         // and append them to div.themes
    960                         self.$el.append( self.theme.el );
     950                        // Loop through the themes and setup each theme view
     951                        self.instance.each( function( theme ) {
     952                                self.theme = new themes.view.Theme({
     953                                        model: theme,
     954                                        parent: self
     955                                });
    961956
    962                         // Binds to theme:expand to show the modal box
    963                         // with the theme details
    964                         self.listenTo( self.theme, 'theme:expand', self.expand, self );
    965                 });
     957                                // Render the views...
     958                                self.theme.render();
     959                                // and append them to div.themes
     960                                self.$el.append( self.theme.el );
    966961
    967                 // 'Add new theme' element shown at the end of the grid
    968                 if ( themes.data.settings.canInstall ) {
    969                         this.$el.append( '<div class="theme add-new-theme"><a href="' + themes.data.settings.installURI + '"><div class="theme-screenshot"><span></span></div><h3 class="theme-name">' + l10n.addNew + '</h3></a></div>' );
    970                 }
     962                                // Binds to theme:expand to show the modal box
     963                                // with the theme details
     964                                self.listenTo( self.theme, 'theme:expand', self.expand, self );
     965                        });
    971966
    972                 this.parent.page++;
    973         },
     967                        // 'Add new theme' element shown at the end of the grid
     968                        if ( themes.data.settings.canInstall ) {
     969                                this.$el.append( '<div class="theme add-new-theme"><a href="' + themes.data.settings.installURI + '"><div class="theme-screenshot"><span></span></div><h3 class="theme-name">' + l10n.addNew + '</h3></a></div>' );
     970                        }
    974971
    975         // Grabs current theme and puts it at the beginning of the collection
    976         currentTheme: function() {
    977                 var self = this,
    978                         current;
     972                        this.parent.page++;
     973                },
    979974
    980                 current = self.collection.findWhere({ active: true });
     975                // Grabs current theme and puts it at the beginning of the collection
     976                currentTheme: function() {
     977                        var self = this,
     978                                current;
    981979
    982                 // Move the active theme to the beginning of the collection
    983                 if ( current ) {
    984                         self.collection.remove( current );
    985                         self.collection.add( current, { at:0 } );
    986                 }
    987         },
     980                        current = self.collection.findWhere({ active: true });
    988981
    989         // Sets current view
    990         setView: function( view ) {
    991                 return view;
    992         },
     982                        // Move the active theme to the beginning of the collection
     983                        if ( current ) {
     984                                self.collection.remove( current );
     985                                self.collection.add( current, { at:0 } );
     986                        }
     987                },
    993988
    994         // Renders the overlay with the ThemeDetails view
    995         // Uses the current model data
    996         expand: function( id ) {
    997                 var self = this;
     989                // Sets current view
     990                setView: function( view ) {
     991                        return view;
     992                },
    998993
    999                 // Set the current theme model
    1000                 this.model = self.collection.get( id );
     994                // Renders the overlay with the ThemeDetails view
     995                // Uses the current model data
     996                expand: function( id ) {
     997                        var self = this;
    1001998
    1002                 // Trigger a route update for the current model
    1003                 themes.router.navigate( themes.router.baseUrl( themes.router.themePath + this.model.id ) );
     999                        // Set the current theme model
     1000                        this.model = self.collection.get( id );
    10041001
    1005                 // Sets this.view to 'detail'
    1006                 this.setView( 'detail' );
    1007                 $( 'body' ).addClass( 'modal-open' );
     1002                        // Trigger a route update for the current model
     1003                        themes.router.navigate( themes.router.baseUrl( themes.router.themePath + this.model.id ) );
    10081004
    1009                 // Set up the theme details view
    1010                 this.overlay = new themes.view.Details({
    1011                         model: self.model
    1012                 });
     1005                        // Sets this.view to 'detail'
     1006                        this.setView( 'detail' );
     1007                        $( 'body' ).addClass( 'modal-open' );
    10131008
    1014                 this.overlay.render();
    1015                 this.$overlay.html( this.overlay.el );
     1009                        // Set up the theme details view
     1010                        this.overlay = new themes.view.Details({
     1011                                model: self.model
     1012                        });
    10161013
    1017                 // Bind to theme:next and theme:previous
    1018                 // triggered by the arrow keys
    1019                 //
    1020                 // Keep track of the current model so we
    1021                 // can infer an index position
    1022                 this.listenTo( this.overlay, 'theme:next', function() {
    1023                         // Renders the next theme on the overlay
    1024                         self.next( [ self.model.cid ] );
     1014                        this.overlay.render();
     1015                        this.$overlay.html( this.overlay.el );
    10251016
    1026                 })
    1027                 .listenTo( this.overlay, 'theme:previous', function() {
    1028                         // Renders the previous theme on the overlay
    1029                         self.previous( [ self.model.cid ] );
    1030                 });
    1031         },
     1017                        // Bind to theme:next and theme:previous
     1018                        // triggered by the arrow keys
     1019                        //
     1020                        // Keep track of the current model so we
     1021                        // can infer an index position
     1022                        this.listenTo( this.overlay, 'theme:next', function() {
     1023                                // Renders the next theme on the overlay
     1024                                self.next( [ self.model.cid ] );
    10321025
    1033         // This method renders the next theme on the overlay modal
    1034         // based on the current position in the collection
    1035         // @params [model cid]
    1036         next: function( args ) {
    1037                 var self = this,
    1038                         model, nextModel;
     1026                        })
     1027                                .listenTo( this.overlay, 'theme:previous', function() {
     1028                                        // Renders the previous theme on the overlay
     1029                                        self.previous( [ self.model.cid ] );
     1030                                });
     1031                },
    10391032
    1040                 // Get the current theme
    1041                 model = self.collection.get( args[0] );
    1042                 // Find the next model within the collection
    1043                 nextModel = self.collection.at( self.collection.indexOf( model ) + 1 );
     1033                // This method renders the next theme on the overlay modal
     1034                // based on the current position in the collection
     1035                // @params [model cid]
     1036                next: function( args ) {
     1037                        var self = this,
     1038                                model, nextModel;
    10441039
    1045                 // Sanity check which also serves as a boundary test
    1046                 if ( nextModel !== undefined ) {
     1040                        // Get the current theme
     1041                        model = self.collection.get( args[0] );
     1042                        // Find the next model within the collection
     1043                        nextModel = self.collection.at( self.collection.indexOf( model ) + 1 );
    10471044
    1048                         // We have a new theme...
    1049                         // Close the overlay
    1050                         this.overlay.closeOverlay();
     1045                        // Sanity check which also serves as a boundary test
     1046                        if ( nextModel !== undefined ) {
    10511047
    1052                         // Trigger a route update for the current model
    1053                         self.theme.trigger( 'theme:expand', nextModel.cid );
     1048                                // We have a new theme...
     1049                                // Close the overlay
     1050                                this.overlay.closeOverlay();
    10541051
    1055                 }
    1056         },
     1052                                // Trigger a route update for the current model
     1053                                self.theme.trigger( 'theme:expand', nextModel.cid );
    10571054
    1058         // This method renders the previous theme on the overlay modal
    1059         // based on the current position in the collection
    1060         // @params [model cid]
    1061         previous: function( args ) {
    1062                 var self = this,
    1063                         model, previousModel;
     1055                        }
     1056                },
    10641057
    1065                 // Get the current theme
    1066                 model = self.collection.get( args[0] );
    1067                 // Find the previous model within the collection
    1068                 previousModel = self.collection.at( self.collection.indexOf( model ) - 1 );
     1058                // This method renders the previous theme on the overlay modal
     1059                // based on the current position in the collection
     1060                // @params [model cid]
     1061                previous: function( args ) {
     1062                        var self = this,
     1063                                model, previousModel;
    10691064
    1070                 if ( previousModel !== undefined ) {
     1065                        // Get the current theme
     1066                        model = self.collection.get( args[0] );
     1067                        // Find the previous model within the collection
     1068                        previousModel = self.collection.at( self.collection.indexOf( model ) - 1 );
    10711069
    1072                         // We have a new theme...
    1073                         // Close the overlay
    1074                         this.overlay.closeOverlay();
     1070                        if ( previousModel !== undefined ) {
    10751071
    1076                         // Trigger a route update for the current model
    1077                         self.theme.trigger( 'theme:expand', previousModel.cid );
     1072                                // We have a new theme...
     1073                                // Close the overlay
     1074                                this.overlay.closeOverlay();
    10781075
     1076                                // Trigger a route update for the current model
     1077                                self.theme.trigger( 'theme:expand', previousModel.cid );
     1078
     1079                        }
    10791080                }
    1080         }
    1081 });
     1081        });
    10821082
    10831083// Search input view controller.
    1084 themes.view.Search = wp.Backbone.View.extend({
     1084        themes.view.Search = wp.Backbone.View.extend({
    10851085
    1086         tagName: 'input',
    1087         className: 'wp-filter-search',
    1088         id: 'wp-filter-search-input',
    1089         searching: false,
     1086                tagName: 'input',
     1087                className: 'wp-filter-search',
     1088                id: 'wp-filter-search-input',
     1089                searching: false,
    10901090
    1091         attributes: {
    1092                 placeholder: l10n.searchPlaceholder,
    1093                 type: 'search'
    1094         },
     1091                attributes: {
     1092                        placeholder: l10n.searchPlaceholder,
     1093                        type: 'search'
     1094                },
    10951095
    1096         events: {
    1097                 'input':  'search',
    1098                 'keyup':  'search',
    1099                 'change': 'search',
    1100                 'search': 'search',
    1101                 'blur':   'pushState'
    1102         },
     1096                events: {
     1097                        'input':  'search',
     1098                        'keyup':  'search',
     1099                        'change': 'search',
     1100                        'search': 'search',
     1101                        'blur':   'pushState'
     1102                },
    11031103
    1104         initialize: function( options ) {
     1104                initialize: function( options ) {
    11051105
    1106                 this.parent = options.parent;
     1106                        this.parent = options.parent;
    11071107
    1108                 this.listenTo( this.parent, 'theme:close', function() {
    1109                         this.searching = false;
    1110                 } );
     1108                        this.listenTo( this.parent, 'theme:close', function() {
     1109                                this.searching = false;
     1110                        } );
    11111111
    1112         },
     1112                },
    11131113
    1114         // Runs a search on the theme collection.
    1115         search: function( event ) {
    1116                 var options = {};
     1114                // Runs a search on the theme collection.
     1115                search: function( event ) {
     1116                        var options = {};
    11171117
    1118                 // Clear on escape.
    1119                 if ( event.type === 'keyup' && event.which === 27 ) {
    1120                         event.target.value = '';
    1121                 }
     1118                        // Clear on escape.
     1119                        if ( event.type === 'keyup' && event.which === 27 ) {
     1120                                event.target.value = '';
     1121                        }
    11221122
    1123                 // Lose input focus when pressing enter
    1124                 if ( event.which === 13 ) {
    1125                         this.$el.trigger( 'blur' );
    1126                 }
     1123                        // Lose input focus when pressing enter
     1124                        if ( event.which === 13 ) {
     1125                                this.$el.trigger( 'blur' );
     1126                        }
    11271127
    1128                 this.collection.doSearch( event.target.value );
     1128                        this.collection.doSearch( event.target.value );
    11291129
    1130                 // if search is initiated and key is not return
    1131                 if ( this.searching && event.which !== 13 ) {
    1132                         options.replace = true;
    1133                 } else {
    1134                         this.searching = true;
    1135                 }
     1130                        // if search is initiated and key is not return
     1131                        if ( this.searching && event.which !== 13 ) {
     1132                                options.replace = true;
     1133                        } else {
     1134                                this.searching = true;
     1135                        }
    11361136
    1137                 // Update the URL hash
    1138                 if ( event.target.value ) {
    1139                         themes.router.navigate( themes.router.baseUrl( themes.router.searchPath + event.target.value ), options );
    1140                 } else {
    1141                         themes.router.navigate( themes.router.baseUrl( '' ) );
    1142                 }
    1143         },
     1137                        // Update the URL hash
     1138                        if ( event.target.value ) {
     1139                                themes.router.navigate( themes.router.baseUrl( themes.router.searchPath + event.target.value ), options );
     1140                        } else {
     1141                                themes.router.navigate( themes.router.baseUrl( '' ) );
     1142                        }
     1143                },
    11441144
    1145         pushState: function( event ) {
    1146                 var url = themes.router.baseUrl( '' );
     1145                pushState: function( event ) {
     1146                        var url = themes.router.baseUrl( '' );
    11471147
    1148                 if ( event.target.value ) {
    1149                         url = themes.router.baseUrl( themes.router.searchPath + event.target.value );
    1150                 }
     1148                        if ( event.target.value ) {
     1149                                url = themes.router.baseUrl( themes.router.searchPath + event.target.value );
     1150                        }
    11511151
    1152                 this.searching = false;
    1153                 themes.router.navigate( url );
     1152                        this.searching = false;
     1153                        themes.router.navigate( url );
    11541154
    1155         }
    1156 });
     1155                }
     1156        });
    11571157
    11581158// Sets up the routes events for relevant url queries
    11591159// Listens to [theme] and [search] params
    1160 themes.Router = Backbone.Router.extend({
     1160        themes.Router = Backbone.Router.extend({
    11611161
    1162         routes: {
    1163                 'themes.php?theme=:slug': 'theme',
    1164                 'themes.php?search=:query': 'search',
    1165                 'themes.php?s=:query': 'search',
    1166                 'themes.php': 'themes',
    1167                 '': 'themes'
    1168         },
     1162                routes: {
     1163                        'themes.php?theme=:slug': 'theme',
     1164                        'themes.php?search=:query': 'search',
     1165                        'themes.php?s=:query': 'search',
     1166                        'themes.php': 'themes',
     1167                        '': 'themes'
     1168                },
    11691169
    1170         baseUrl: function( url ) {
    1171                 return 'themes.php' + url;
    1172         },
     1170                baseUrl: function( url ) {
     1171                        return 'themes.php' + url;
     1172                },
    11731173
    1174         themePath: '?theme=',
    1175         searchPath: '?search=',
     1174                themePath: '?theme=',
     1175                searchPath: '?search=',
    11761176
    1177         search: function( query ) {
    1178                 $( '.wp-filter-search' ).val( query );
    1179         },
     1177                search: function( query ) {
     1178                        $( '.wp-filter-search' ).val( query );
     1179                },
    11801180
    1181         themes: function() {
    1182                 $( '.wp-filter-search' ).val( '' );
    1183         },
     1181                themes: function() {
     1182                        $( '.wp-filter-search' ).val( '' );
     1183                },
    11841184
    1185         navigate: function() {
    1186                 if ( Backbone.history._hasPushState ) {
    1187                         Backbone.Router.prototype.navigate.apply( this, arguments );
     1185                navigate: function() {
     1186                        if ( Backbone.history._hasPushState ) {
     1187                                Backbone.Router.prototype.navigate.apply( this, arguments );
     1188                        }
    11881189                }
    1189         }
    11901190
    1191 });
     1191        });
    11921192
    11931193// Execute and setup the application
    1194 themes.Run = {
    1195         init: function() {
    1196                 // Initializes the blog's theme library view
    1197                 // Create a new collection with data
    1198                 this.themes = new themes.Collection( themes.data.themes );
     1194        themes.Run = {
     1195                init: function() {
     1196                        // Initializes the blog's theme library view
     1197                        // Create a new collection with data
     1198                        this.themes = new themes.Collection( themes.data.themes );
    11991199
    1200                 // Set up the view
    1201                 this.view = new themes.view.Appearance({
    1202                         collection: this.themes
    1203                 });
     1200                        // Set up the view
     1201                        this.view = new themes.view.Appearance({
     1202                                collection: this.themes
     1203                        });
    12041204
    1205                 this.render();
    1206         },
     1205                        this.render();
     1206                },
    12071207
    1208         render: function() {
     1208                render: function() {
    12091209
    1210                 // Render results
    1211                 this.view.render();
    1212                 this.routes();
     1210                        // Render results
     1211                        this.view.render();
     1212                        this.routes();
    12131213
    1214                 Backbone.history.start({
    1215                         root: themes.data.settings.adminUrl,
    1216                         pushState: true,
    1217                         hashChange: false
    1218                 });
    1219         },
     1214                        Backbone.history.start({
     1215                                root: themes.data.settings.adminUrl,
     1216                                pushState: true,
     1217                                hashChange: false
     1218                        });
     1219                },
    12201220
    1221         routes: function() {
    1222                 var self = this;
    1223                 // Bind to our global thx object
    1224                 // so that the object is available to sub-views
    1225                 themes.router = new themes.Router();
     1221                routes: function() {
     1222                        var self = this;
     1223                        // Bind to our global thx object
     1224                        // so that the object is available to sub-views
     1225                        themes.router = new themes.Router();
    12261226
    1227                 // Handles theme details route event
    1228                 themes.router.on( 'route:theme', function( slug ) {
    1229                         self.view.view.expand( slug );
    1230                 });
     1227                        // Handles theme details route event
     1228                        themes.router.on( 'route:theme', function( slug ) {
     1229                                self.view.view.expand( slug );
     1230                        });
    12311231
    1232                 themes.router.on( 'route:themes', function() {
    1233                         self.themes.doSearch( '' );
    1234                         self.view.trigger( 'theme:close' );
    1235                 });
     1232                        themes.router.on( 'route:themes', function() {
     1233                                self.themes.doSearch( '' );
     1234                                self.view.trigger( 'theme:close' );
     1235                        });
    12361236
    1237                 // Handles search route event
    1238                 themes.router.on( 'route:search', function() {
    1239                         $( '.wp-filter-search' ).trigger( 'keyup' );
    1240                 });
     1237                        // Handles search route event
     1238                        themes.router.on( 'route:search', function() {
     1239                                $( '.wp-filter-search' ).trigger( 'keyup' );
     1240                        });
    12411241
    1242                 this.extraRoutes();
    1243         },
     1242                        this.extraRoutes();
     1243                },
    12441244
    1245         extraRoutes: function() {
    1246                 return false;
    1247         }
    1248 };
     1245                extraRoutes: function() {
     1246                        return false;
     1247                }
     1248        };
    12491249
    12501250// Extend the main Search view
    1251 themes.view.InstallerSearch =  themes.view.Search.extend({
     1251        themes.view.InstallerSearch =  themes.view.Search.extend({
    12521252
    1253         events: {
    1254                 'keyup': 'search'
    1255         },
     1253                events: {
     1254                        'keyup': 'search'
     1255                },
    12561256
    1257         // Handles Ajax request for searching through themes in public repo
    1258         search: function( event ) {
     1257                // Handles Ajax request for searching through themes in public repo
     1258                search: function( event ) {
    12591259
    1260                 // Tabbing or reverse tabbing into the search input shouldn't trigger a search
    1261                 if ( event.type === 'keyup' && ( event.which === 9 || event.which === 16 ) ) {
    1262                         return;
    1263                 }
     1260                        // Tabbing or reverse tabbing into the search input shouldn't trigger a search
     1261                        if ( event.type === 'keyup' && ( event.which === 9 || event.which === 16 ) ) {
     1262                                return;
     1263                        }
    12641264
    1265                 this.collection = this.options.parent.view.collection;
     1265                        this.collection = this.options.parent.view.collection;
    12661266
    1267                 // Clear on escape.
    1268                 if ( event.type === 'keyup' && event.which === 27 ) {
    1269                         event.target.value = '';
    1270                 }
     1267                        // Clear on escape.
     1268                        if ( event.type === 'keyup' && event.which === 27 ) {
     1269                                event.target.value = '';
     1270                        }
    12711271
    1272                 _.debounce( _.bind( this.doSearch, this ), 300 )( event.target.value );
    1273         },
     1272                        _.debounce( _.bind( this.doSearch, this ), 300 )( event.target.value );
     1273                },
    12741274
    1275         doSearch: _.debounce( function( value ) {
    1276                 var request = {};
     1275                doSearch: _.debounce( function( value ) {
     1276                        var request = {};
    12771277
    1278                 request.search = value;
     1278                        request.search = value;
    12791279
    1280                 // Intercept an [author] search.
    1281                 //
    1282                 // If input value starts with `author:` send a request
    1283                 // for `author` instead of a regular `search`
    1284                 if ( value.substring( 0, 7 ) === 'author:' ) {
    1285                         request.search = '';
    1286                         request.author = value.slice( 7 );
    1287                 }
     1280                        // Intercept an [author] search.
     1281                        //
     1282                        // If input value starts with `author:` send a request
     1283                        // for `author` instead of a regular `search`
     1284                        if ( value.substring( 0, 7 ) === 'author:' ) {
     1285                                request.search = '';
     1286                                request.author = value.slice( 7 );
     1287                        }
    12881288
    1289                 // Intercept a [tag] search.
    1290                 //
    1291                 // If input value starts with `tag:` send a request
    1292                 // for `tag` instead of a regular `search`
    1293                 if ( value.substring( 0, 4 ) === 'tag:' ) {
    1294                         request.search = '';
    1295                         request.tag = [ value.slice( 4 ) ];
    1296                 }
     1289                        // Intercept a [tag] search.
     1290                        //
     1291                        // If input value starts with `tag:` send a request
     1292                        // for `tag` instead of a regular `search`
     1293                        if ( value.substring( 0, 4 ) === 'tag:' ) {
     1294                                request.search = '';
     1295                                request.tag = [ value.slice( 4 ) ];
     1296                        }
    12971297
    1298                 $( '.filter-links li > a.current' ).removeClass( 'current' );
    1299                 $( 'body' ).removeClass( 'show-filters filters-applied' );
     1298                        $( '.filter-links li > a.current' ).removeClass( 'current' );
     1299                        $( 'body' ).removeClass( 'show-filters filters-applied' );
    13001300
    1301                 // Get the themes by sending Ajax POST request to api.wordpress.org/themes
    1302                 // or searching the local cache
    1303                 this.collection.query( request );
     1301                        // Get the themes by sending Ajax POST request to api.wordpress.org/themes
     1302                        // or searching the local cache
     1303                        this.collection.query( request );
    13041304
    1305                 // Set route
    1306                 themes.router.navigate( themes.router.baseUrl( themes.router.searchPath + value ), { replace: true } );
    1307         }, 300 )
    1308 });
     1305                        // Set route
     1306                        themes.router.navigate( themes.router.baseUrl( themes.router.searchPath + value ), { replace: true } );
     1307                }, 300 )
     1308        });
    13091309
    1310 themes.view.Installer = themes.view.Appearance.extend({
     1310        themes.view.Installer = themes.view.Appearance.extend({
    13111311
    1312         el: '#wpbody-content .wrap',
     1312                el: '#wpbody-content .wrap',
    13131313
    1314         // Register events for sorting and filters in theme-navigation
    1315         events: {
    1316                 'click .filter-links li > a': 'onSort',
    1317                 'click .theme-filter': 'onFilter',
    1318                 'click .drawer-toggle': 'moreFilters',
    1319                 'click .filter-drawer .apply-filters': 'applyFilters',
    1320                 'click .filter-group [type="checkbox"]': 'addFilter',
    1321                 'click .filter-drawer .clear-filters': 'clearFilters',
    1322                 'click .filtered-by': 'backToFilters'
    1323         },
     1314                // Register events for sorting and filters in theme-navigation
     1315                events: {
     1316                        'click .filter-links li > a': 'onSort',
     1317                        'click .theme-filter': 'onFilter',
     1318                        'click .drawer-toggle': 'moreFilters',
     1319                        'click .filter-drawer .apply-filters': 'applyFilters',
     1320                        'click .filter-group [type="checkbox"]': 'addFilter',
     1321                        'click .filter-drawer .clear-filters': 'clearFilters',
     1322                        'click .filtered-by': 'backToFilters'
     1323                },
    13241324
    1325         // Initial render method
    1326         render: function() {
    1327                 var self = this;
     1325                // Initial render method
     1326                render: function() {
     1327                        var self = this;
    13281328
    1329                 this.search();
    1330                 this.uploader();
     1329                        this.search();
     1330                        this.uploader();
    13311331
    1332                 this.collection = new themes.Collection();
     1332                        this.collection = new themes.Collection();
    13331333
    1334                 // Bump `collection.currentQuery.page` and request more themes if we hit the end of the page.
    1335                 this.listenTo( this, 'theme:end', function() {
     1334                        // Bump `collection.currentQuery.page` and request more themes if we hit the end of the page.
     1335                        this.listenTo( this, 'theme:end', function() {
    13361336
    1337                         // Make sure we are not already loading
    1338                         if ( self.collection.loadingThemes ) {
    1339                                 return;
    1340                         }
     1337                                // Make sure we are not already loading
     1338                                if ( self.collection.loadingThemes ) {
     1339                                        return;
     1340                                }
    13411341
    1342                         // Set loadingThemes to true and bump page instance of currentQuery.
    1343                         self.collection.loadingThemes = true;
    1344                         self.collection.currentQuery.page++;
     1342                                // Set loadingThemes to true and bump page instance of currentQuery.
     1343                                self.collection.loadingThemes = true;
     1344                                self.collection.currentQuery.page++;
    13451345
    1346                         // Use currentQuery.page to build the themes request.
    1347                         _.extend( self.collection.currentQuery.request, { page: self.collection.currentQuery.page } );
    1348                         self.collection.query( self.collection.currentQuery.request );
    1349                 });
     1346                                // Use currentQuery.page to build the themes request.
     1347                                _.extend( self.collection.currentQuery.request, { page: self.collection.currentQuery.page } );
     1348                                self.collection.query( self.collection.currentQuery.request );
     1349                        });
    13501350
    1351                 this.listenTo( this.collection, 'query:success', function() {
    1352                         $( 'body' ).removeClass( 'loading-content' );
    1353                         $( '.theme-browser' ).find( 'div.error' ).remove();
    1354                 });
     1351                        this.listenTo( this.collection, 'query:success', function() {
     1352                                $( 'body' ).removeClass( 'loading-content' );
     1353                                $( '.theme-browser' ).find( 'div.error' ).remove();
     1354                        });
    13551355
    1356                 this.listenTo( this.collection, 'query:fail', function() {
    1357                         $( 'body' ).removeClass( 'loading-content' );
    1358                         $( '.theme-browser' ).find( 'div.error' ).remove();
    1359                         $( '.theme-browser' ).find( 'div.themes' ).before( '<div class="error"><p>' + l10n.error + '</p></div>' );
    1360                 });
     1356                        this.listenTo( this.collection, 'query:fail', function() {
     1357                                $( 'body' ).removeClass( 'loading-content' );
     1358                                $( '.theme-browser' ).find( 'div.error' ).remove();
     1359                                $( '.theme-browser' ).find( 'div.themes' ).before( '<div class="error"><p>' + l10n.error + '</p></div>' );
     1360                        });
    13611361
    1362                 if ( this.view ) {
    1363                         this.view.remove();
    1364                 }
     1362                        if ( this.view ) {
     1363                                this.view.remove();
     1364                        }
    13651365
    1366                 // Set ups the view and passes the section argument
    1367                 this.view = new themes.view.Themes({
    1368                         collection: this.collection,
    1369                         parent: this
    1370                 });
     1366                        // Set ups the view and passes the section argument
     1367                        this.view = new themes.view.Themes({
     1368                                collection: this.collection,
     1369                                parent: this
     1370                        });
    13711371
    1372                 // Reset pagination every time the install view handler is run
    1373                 this.page = 0;
     1372                        // Reset pagination every time the install view handler is run
     1373                        this.page = 0;
    13741374
    1375                 // Render and append
    1376                 this.$el.find( '.themes' ).remove();
    1377                 this.view.render();
    1378                 this.$el.find( '.theme-browser' ).append( this.view.el ).addClass( 'rendered' );
    1379         },
     1375                        // Render and append
     1376                        this.$el.find( '.themes' ).remove();
     1377                        this.view.render();
     1378                        this.$el.find( '.theme-browser' ).append( this.view.el ).addClass( 'rendered' );
     1379                },
    13801380
    1381         // Handles all the rendering of the public theme directory
    1382         browse: function( section ) {
    1383                 // Create a new collection with the proper theme data
    1384                 // for each section
    1385                 this.collection.query( { browse: section } );
    1386         },
     1381                // Handles all the rendering of the public theme directory
     1382                browse: function( section ) {
     1383                        // Create a new collection with the proper theme data
     1384                        // for each section
     1385                        this.collection.query( { browse: section } );
     1386                },
    13871387
    1388         // Sorting navigation
    1389         onSort: function( event ) {
    1390                 var $el = $( event.target ),
    1391                         sort = $el.data( 'sort' );
     1388                // Sorting navigation
     1389                onSort: function( event ) {
     1390                        var $el = $( event.target ),
     1391                                sort = $el.data( 'sort' );
    13921392
    1393                 event.preventDefault();
     1393                        event.preventDefault();
    13941394
    1395                 $( 'body' ).removeClass( 'filters-applied show-filters' );
     1395                        $( 'body' ).removeClass( 'filters-applied show-filters' );
    13961396
    1397                 // Bail if this is already active
    1398                 if ( $el.hasClass( this.activeClass ) ) {
    1399                         return;
    1400                 }
     1397                        // Bail if this is already active
     1398                        if ( $el.hasClass( this.activeClass ) ) {
     1399                                return;
     1400                        }
    14011401
    1402                 this.sort( sort );
     1402                        this.sort( sort );
    14031403
    1404                 // Trigger a router.naviagte update
    1405                 themes.router.navigate( themes.router.baseUrl( themes.router.browsePath + sort ) );
    1406         },
     1404                        // Trigger a router.naviagte update
     1405                        themes.router.navigate( themes.router.baseUrl( themes.router.browsePath + sort ) );
     1406                },
    14071407
    1408         sort: function( sort ) {
    1409                 this.clearSearch();
     1408                sort: function( sort ) {
     1409                        this.clearSearch();
    14101410
    1411                 $( '.filter-links li > a, .theme-filter' ).removeClass( this.activeClass );
    1412                 $( '[data-sort="' + sort + '"]' ).addClass( this.activeClass );
     1411                        $( '.filter-links li > a, .theme-filter' ).removeClass( this.activeClass );
     1412                        $( '[data-sort="' + sort + '"]' ).addClass( this.activeClass );
    14131413
    1414                 this.browse( sort );
    1415         },
     1414                        this.browse( sort );
     1415                },
    14161416
    1417         // Filters and Tags
    1418         onFilter: function( event ) {
    1419                 var request,
    1420                         $el = $( event.target ),
    1421                         filter = $el.data( 'filter' );
     1417                // Filters and Tags
     1418                onFilter: function( event ) {
     1419                        var request,
     1420                                $el = $( event.target ),
     1421                                filter = $el.data( 'filter' );
    14221422
    1423                 // Bail if this is already active
    1424                 if ( $el.hasClass( this.activeClass ) ) {
    1425                         return;
    1426                 }
     1423                        // Bail if this is already active
     1424                        if ( $el.hasClass( this.activeClass ) ) {
     1425                                return;
     1426                        }
    14271427
    1428                 $( '.filter-links li > a, .theme-section' ).removeClass( this.activeClass );
    1429                 $el.addClass( this.activeClass );
     1428                        $( '.filter-links li > a, .theme-section' ).removeClass( this.activeClass );
     1429                        $el.addClass( this.activeClass );
    14301430
    1431                 if ( ! filter ) {
    1432                         return;
    1433                 }
     1431                        if ( ! filter ) {
     1432                                return;
     1433                        }
    14341434
    1435                 // Construct the filter request
    1436                 // using the default values
    1437                 filter = _.union( filter, this.filtersChecked() );
    1438                 request = { tag: [ filter ] };
     1435                        // Construct the filter request
     1436                        // using the default values
     1437                        filter = _.union( filter, this.filtersChecked() );
     1438                        request = { tag: [ filter ] };
    14391439
    1440                 // Get the themes by sending Ajax POST request to api.wordpress.org/themes
    1441                 // or searching the local cache
    1442                 this.collection.query( request );
    1443         },
     1440                        // Get the themes by sending Ajax POST request to api.wordpress.org/themes
     1441                        // or searching the local cache
     1442                        this.collection.query( request );
     1443                },
    14441444
    1445         // Clicking on a checkbox to add another filter to the request
    1446         addFilter: function() {
    1447                 this.filtersChecked();
    1448         },
     1445                // Clicking on a checkbox to add another filter to the request
     1446                addFilter: function() {
     1447                        this.filtersChecked();
     1448                },
    14491449
    1450         // Applying filters triggers a tag request
    1451         applyFilters: function( event ) {
    1452                 var name,
    1453                         tags = this.filtersChecked(),
    1454                         request = { tag: tags },
    1455                         filteringBy = $( '.filtered-by .tags' );
     1450                // Applying filters triggers a tag request
     1451                applyFilters: function( event ) {
     1452                        var name,
     1453                                tags = this.filtersChecked(),
     1454                                request = { tag: tags },
     1455                                filteringBy = $( '.filtered-by .tags' );
    14561456
    1457                 if ( event ) {
    1458                         event.preventDefault();
    1459                 }
     1457                        if ( event ) {
     1458                                event.preventDefault();
     1459                        }
    14601460
    1461                 $( 'body' ).addClass( 'filters-applied' );
    1462                 $( '.filter-links li > a.current' ).removeClass( 'current' );
    1463                 filteringBy.empty();
     1461                        $( 'body' ).addClass( 'filters-applied' );
     1462                        $( '.filter-links li > a.current' ).removeClass( 'current' );
     1463                        filteringBy.empty();
    14641464
    1465                 _.each( tags, function( tag ) {
    1466                         name = $( 'label[for="filter-id-' + tag + '"]' ).text();
    1467                         filteringBy.append( '<span class="tag">' + name + '</span>' );
    1468                 });
     1465                        _.each( tags, function( tag ) {
     1466                                name = $( 'label[for="filter-id-' + tag + '"]' ).text();
     1467                                filteringBy.append( '<span class="tag">' + name + '</span>' );
     1468                        });
    14691469
    1470                 // Get the themes by sending Ajax POST request to api.wordpress.org/themes
    1471                 // or searching the local cache
    1472                 this.collection.query( request );
    1473         },
     1470                        // Get the themes by sending Ajax POST request to api.wordpress.org/themes
     1471                        // or searching the local cache
     1472                        this.collection.query( request );
     1473                },
    14741474
    1475         // Get the checked filters
    1476         // @return {array} of tags or false
    1477         filtersChecked: function() {
    1478                 var items = $( '.filter-group' ).find( ':checkbox' ),
    1479                         tags = [];
     1475                // Get the checked filters
     1476                // @return {array} of tags or false
     1477                filtersChecked: function() {
     1478                        var items = $( '.filter-group' ).find( ':checkbox' ),
     1479                                tags = [];
    14801480
    1481                 _.each( items.filter( ':checked' ), function( item ) {
    1482                         tags.push( $( item ).prop( 'value' ) );
    1483                 });
     1481                        _.each( items.filter( ':checked' ), function( item ) {
     1482                                tags.push( $( item ).prop( 'value' ) );
     1483                        });
    14841484
    1485                 // When no filters are checked, restore initial state and return
    1486                 if ( tags.length === 0 ) {
    1487                         $( '.filter-drawer .apply-filters' ).find( 'span' ).text( '' );
    1488                         $( '.filter-drawer .clear-filters' ).hide();
    1489                         $( 'body' ).removeClass( 'filters-applied' );
    1490                         return false;
    1491                 }
     1485                        // When no filters are checked, restore initial state and return
     1486                        if ( tags.length === 0 ) {
     1487                                $( '.filter-drawer .apply-filters' ).find( 'span' ).text( '' );
     1488                                $( '.filter-drawer .clear-filters' ).hide();
     1489                                $( 'body' ).removeClass( 'filters-applied' );
     1490                                return false;
     1491                        }
    14921492
    1493                 $( '.filter-drawer .apply-filters' ).find( 'span' ).text( tags.length );
    1494                 $( '.filter-drawer .clear-filters' ).css( 'display', 'inline-block' );
     1493                        $( '.filter-drawer .apply-filters' ).find( 'span' ).text( tags.length );
     1494                        $( '.filter-drawer .clear-filters' ).css( 'display', 'inline-block' );
    14951495
    1496                 return tags;
    1497         },
     1496                        return tags;
     1497                },
    14981498
    1499         activeClass: 'current',
     1499                activeClass: 'current',
    15001500
    1501         // Overwrite search container class to append search
    1502         // in new location
    1503         searchContainer: $( '.wp-filter .search-form' ),
     1501                // Overwrite search container class to append search
     1502                // in new location
     1503                searchContainer: $( '.wp-filter .search-form' ),
    15041504
    1505         uploader: function() {
    1506                 $( 'a.upload' ).on( 'click', function( event ) {
     1505                uploader: function() {
     1506                        $( 'a.upload' ).on( 'click', function( event ) {
     1507                                event.preventDefault();
     1508                                $( 'body' ).addClass( 'show-upload-theme' );
     1509                                themes.router.navigate( themes.router.baseUrl( '?upload' ), { replace: true } );
     1510                        });
     1511                        $( 'a.browse-themes' ).on( 'click', function( event ) {
     1512                                event.preventDefault();
     1513                                $( 'body' ).removeClass( 'show-upload-theme' );
     1514                                themes.router.navigate( themes.router.baseUrl( '' ), { replace: true } );
     1515                        });
     1516                },
     1517
     1518                // Toggle the full filters navigation
     1519                moreFilters: function( event ) {
    15071520                        event.preventDefault();
    1508                         $( 'body' ).addClass( 'show-upload-theme' );
    1509                         themes.router.navigate( themes.router.baseUrl( '?upload' ), { replace: true } );
    1510                 });
    1511                 $( 'a.browse-themes' ).on( 'click', function( event ) {
    1512                         event.preventDefault();
    1513                         $( 'body' ).removeClass( 'show-upload-theme' );
    1514                         themes.router.navigate( themes.router.baseUrl( '' ), { replace: true } );
    1515                 });
    1516         },
    15171521
    1518         // Toggle the full filters navigation
    1519         moreFilters: function( event ) {
    1520                 event.preventDefault();
     1522                        if ( $( 'body' ).hasClass( 'filters-applied' ) ) {
     1523                                return this.backToFilters();
     1524                        }
    15211525
    1522                 if ( $( 'body' ).hasClass( 'filters-applied' ) ) {
    1523                         return this.backToFilters();
    1524                 }
     1526                        // If the filters section is opened and filters are checked
     1527                        // run the relevant query collapsing to filtered-by state
     1528                        if ( $( 'body' ).hasClass( 'show-filters' ) && this.filtersChecked() ) {
     1529                                return this.addFilter();
     1530                        }
    15251531
    1526                 // If the filters section is opened and filters are checked
    1527                 // run the relevant query collapsing to filtered-by state
    1528                 if ( $( 'body' ).hasClass( 'show-filters' ) && this.filtersChecked() ) {
    1529                         return this.addFilter();
    1530                 }
     1532                        this.clearSearch();
    15311533
    1532                 this.clearSearch();
     1534                        themes.router.navigate( themes.router.baseUrl( '' ) );
     1535                        $( 'body' ).toggleClass( 'show-filters' );
     1536                },
    15331537
    1534                 themes.router.navigate( themes.router.baseUrl( '' ) );
    1535                 $( 'body' ).toggleClass( 'show-filters' );
    1536         },
     1538                // Clears all the checked filters
     1539                // @uses filtersChecked()
     1540                clearFilters: function( event ) {
     1541                        var items = $( '.filter-group' ).find( ':checkbox' ),
     1542                                self = this;
    15371543
    1538         // Clears all the checked filters
    1539         // @uses filtersChecked()
    1540         clearFilters: function( event ) {
    1541                 var items = $( '.filter-group' ).find( ':checkbox' ),
    1542                         self = this;
     1544                        event.preventDefault();
    15431545
    1544                 event.preventDefault();
     1546                        _.each( items.filter( ':checked' ), function( item ) {
     1547                                $( item ).prop( 'checked', false );
     1548                                return self.filtersChecked();
     1549                        });
     1550                },
    15451551
    1546                 _.each( items.filter( ':checked' ), function( item ) {
    1547                         $( item ).prop( 'checked', false );
    1548                         return self.filtersChecked();
    1549                 });
    1550         },
     1552                backToFilters: function( event ) {
     1553                        if ( event ) {
     1554                                event.preventDefault();
     1555                        }
    15511556
    1552         backToFilters: function( event ) {
    1553                 if ( event ) {
    1554                         event.preventDefault();
     1557                        $( 'body' ).removeClass( 'filters-applied' );
     1558                },
     1559
     1560                clearSearch: function() {
     1561                        $( '#wp-filter-search-input').val( '' );
    15551562                }
     1563        });
    15561564
    1557                 $( 'body' ).removeClass( 'filters-applied' );
    1558         },
     1565        themes.InstallerRouter = Backbone.Router.extend({
     1566                routes: {
     1567                        'theme-install.php?theme=:slug': 'preview',
     1568                        'theme-install.php?browse=:sort': 'sort',
     1569                        'theme-install.php?upload': 'upload',
     1570                        'theme-install.php?search=:query': 'search',
     1571                        'theme-install.php': 'sort'
     1572                },
    15591573
    1560         clearSearch: function() {
    1561                 $( '#wp-filter-search-input').val( '' );
    1562         }
    1563 });
     1574                baseUrl: function( url ) {
     1575                        return 'theme-install.php' + url;
     1576                },
    15641577
    1565 themes.InstallerRouter = Backbone.Router.extend({
    1566         routes: {
    1567                 'theme-install.php?theme=:slug': 'preview',
    1568                 'theme-install.php?browse=:sort': 'sort',
    1569                 'theme-install.php?upload': 'upload',
    1570                 'theme-install.php?search=:query': 'search',
    1571                 'theme-install.php': 'sort'
    1572         },
     1578                themePath: '?theme=',
     1579                browsePath: '?browse=',
     1580                searchPath: '?search=',
    15731581
    1574         baseUrl: function( url ) {
    1575                 return 'theme-install.php' + url;
    1576         },
     1582                search: function( query ) {
     1583                        $( '.wp-filter-search' ).val( query );
     1584                },
    15771585
    1578         themePath: '?theme=',
    1579         browsePath: '?browse=',
    1580         searchPath: '?search=',
    1581 
    1582         search: function( query ) {
    1583                 $( '.wp-filter-search' ).val( query );
    1584         },
    1585 
    1586         navigate: function() {
    1587                 if ( Backbone.history._hasPushState ) {
    1588                         Backbone.Router.prototype.navigate.apply( this, arguments );
     1586                navigate: function() {
     1587                        if ( Backbone.history._hasPushState ) {
     1588                                Backbone.Router.prototype.navigate.apply( this, arguments );
     1589                        }
    15891590                }
    1590         }
    1591 });
     1591        });
    15921592
    15931593
    1594 themes.RunInstaller = {
     1594        themes.RunInstaller = {
    15951595
    1596         init: function() {
    1597                 // Set up the view
    1598                 // Passes the default 'section' as an option
    1599                 this.view = new themes.view.Installer({
    1600                         section: 'featured',
    1601                         SearchView: themes.view.InstallerSearch
    1602                 });
     1596                init: function() {
     1597                        // Set up the view
     1598                        // Passes the default 'section' as an option
     1599                        this.view = new themes.view.Installer({
     1600                                section: 'featured',
     1601                                SearchView: themes.view.InstallerSearch
     1602                        });
    16031603
    1604                 // Render results
    1605                 this.render();
     1604                        // Render results
     1605                        this.render();
    16061606
    1607         },
     1607                },
    16081608
    1609         render: function() {
     1609                render: function() {
    16101610
    1611                 // Render results
    1612                 this.view.render();
    1613                 this.routes();
     1611                        // Render results
     1612                        this.view.render();
     1613                        this.routes();
    16141614
    1615                 Backbone.history.start({
    1616                         root: themes.data.settings.adminUrl,
    1617                         pushState: true,
    1618                         hashChange: false
    1619                 });
    1620         },
     1615                        Backbone.history.start({
     1616                                root: themes.data.settings.adminUrl,
     1617                                pushState: true,
     1618                                hashChange: false
     1619                        });
     1620                },
    16211621
    1622         routes: function() {
    1623                 var self = this,
    1624                         request = {};
     1622                routes: function() {
     1623                        var self = this,
     1624                                request = {};
    16251625
    1626                 // Bind to our global `wp.themes` object
    1627                 // so that the router is available to sub-views
    1628                 themes.router = new themes.InstallerRouter();
     1626                        // Bind to our global `wp.themes` object
     1627                        // so that the router is available to sub-views
     1628                        themes.router = new themes.InstallerRouter();
    16291629
    1630                 // Handles `theme` route event
    1631                 // Queries the API for the passed theme slug
    1632                 themes.router.on( 'route:preview', function( slug ) {
    1633                         request.theme = slug;
    1634                         self.view.collection.query( request );
    1635                 });
     1630                        // Handles `theme` route event
     1631                        // Queries the API for the passed theme slug
     1632                        themes.router.on( 'route:preview', function( slug ) {
     1633                                request.theme = slug;
     1634                                self.view.collection.query( request );
     1635                        });
    16361636
    1637                 // Handles sorting / browsing routes
    1638                 // Also handles the root URL triggering a sort request
    1639                 // for `featured`, the default view
    1640                 themes.router.on( 'route:sort', function( sort ) {
    1641                         if ( ! sort ) {
    1642                                 sort = 'featured';
    1643                         }
    1644                         self.view.sort( sort );
    1645                         self.view.trigger( 'theme:close' );
    1646                 });
     1637                        // Handles sorting / browsing routes
     1638                        // Also handles the root URL triggering a sort request
     1639                        // for `featured`, the default view
     1640                        themes.router.on( 'route:sort', function( sort ) {
     1641                                if ( ! sort ) {
     1642                                        sort = 'featured';
     1643                                }
     1644                                self.view.sort( sort );
     1645                                self.view.trigger( 'theme:close' );
     1646                        });
    16471647
    1648                 // Support the `upload` route by going straight to upload section
    1649                 themes.router.on( 'route:upload', function() {
    1650                         $( 'a.upload' ).trigger( 'click' );
    1651                 });
     1648                        // Support the `upload` route by going straight to upload section
     1649                        themes.router.on( 'route:upload', function() {
     1650                                $( 'a.upload' ).trigger( 'click' );
     1651                        });
    16521652
    1653                 // The `search` route event. The router populates the input field.
    1654                 themes.router.on( 'route:search', function() {
    1655                         $( '.wp-filter-search' ).focus().trigger( 'keyup' );
    1656                 });
     1653                        // The `search` route event. The router populates the input field.
     1654                        themes.router.on( 'route:search', function() {
     1655                                $( '.wp-filter-search' ).focus().trigger( 'keyup' );
     1656                        });
    16571657
    1658                 this.extraRoutes();
    1659         },
     1658                        this.extraRoutes();
     1659                },
    16601660
    1661         extraRoutes: function() {
    1662                 return false;
    1663         }
    1664 };
     1661                extraRoutes: function() {
     1662                        return false;
     1663                }
     1664        };
    16651665
    16661666// Ready...
    1667 $( document ).ready(function() {
    1668         if ( themes.isInstall ) {
    1669                 themes.RunInstaller.init();
    1670         } else {
    1671                 themes.Run.init();
    1672         }
     1667        $( document ).ready(function() {
     1668                if ( themes.isInstall ) {
     1669                        themes.RunInstaller.init();
     1670                } else {
     1671                        themes.Run.init();
     1672                }
    16731673
    1674         $( '.broken-themes .delete-theme' ).on( 'click', function() {
    1675                 return confirm( _wpThemeSettings.settings.confirmDelete );
     1674                $( '.broken-themes .delete-theme' ).on( 'click', function() {
     1675                        return confirm( _wpThemeSettings.settings.confirmDelete );
     1676                });
    16761677        });
    1677 });
    16781678
    16791679})( jQuery );
    16801680
     
    17031703        };
    17041704
    17051705        $(window).resize(function(){ tb_position(); });
    1706 });
     1706});
     1707 No newline at end of file
  • src/wp-admin/themes.php

     
    4343// Help tab: Overview
    4444if ( current_user_can( 'switch_themes' ) ) {
    4545        $help_overview  = '<p>' . __( 'This screen is used for managing your installed themes. Aside from the default theme(s) included with your WordPress installation, themes are designed and developed by third parties.' ) . '</p>' .
    46                 '<p>' . __( 'From this screen you can:' ) . '</p>' .
    47                 '<ul><li>' . __( 'Hover or tap to see Activate and Live Preview buttons' ) . '</li>' .
    48                 '<li>' . __( 'Click on the theme to see the theme name, version, author, description, tags, and the Delete link' ) . '</li>' .
    49                 '<li>' . __( 'Click Customize for the current theme or Live Preview for any other theme to see a live preview' ) . '</li></ul>' .
    50                 '<p>' . __( 'The current theme is displayed highlighted as the first theme.' ) . '</p>';
     46                          '<p>' . __( 'From this screen you can:' ) . '</p>' .
     47                          '<ul><li>' . __( 'Hover or tap to see Activate and Live Preview buttons' ) . '</li>' .
     48                          '<li>' . __( 'Click on the theme to see the theme name, version, author, description, tags, and the Delete link' ) . '</li>' .
     49                          '<li>' . __( 'Click Customize for the current theme or Live Preview for any other theme to see a live preview' ) . '</li></ul>' .
     50                          '<p>' . __( 'The current theme is displayed highlighted as the first theme.' ) . '</p>';
    5151
    5252        get_current_screen()->add_help_tab( array(
    5353                'id'      => 'overview',
     
    106106                'confirmDelete' => __( "Are you sure you want to delete this theme?\n\nClick 'Cancel' to go back, 'OK' to confirm the delete." ),
    107107                'adminUrl'      => parse_url( admin_url(), PHP_URL_PATH ),
    108108        ),
    109         'l10n' => array(
    110                 'addNew' => __( 'Add New Theme' ),
    111                 'search'  => __( 'Search Installed Themes' ),
    112                 'searchPlaceholder' => __( 'Search installed themes...' ), // placeholder (no ellipsis)
    113         ),
     109        'l10n' => array(
     110                'addNew' => __( 'Add New Theme' ),
     111                'search'  => __( 'Search Installed Themes' ),
     112                'searchPlaceholder' => __( 'Search installed themes...' ), // placeholder (no ellipsis)
     113        ),
    114114) );
    115115
    116116add_thickbox();
     
    120120require_once( ABSPATH . 'wp-admin/admin-header.php' );
    121121?>
    122122
    123 <div class="wrap">
    124         <h2><?php esc_html_e( 'Themes' ); ?>
    125                 <span class="title-count theme-count"><?php echo count( $themes ); ?></span>
    126         <?php if ( ! is_multisite() && current_user_can( 'install_themes' ) ) : ?>
    127                 <a href="<?php echo admin_url( 'theme-install.php' ); ?>" class="hide-if-no-js add-new-h2"><?php echo esc_html_x( 'Add New', 'Add new theme' ); ?></a>
    128         <?php endif; ?>
    129         </h2>
    130 <?php
    131 if ( ! validate_current_theme() || isset( $_GET['broken'] ) ) : ?>
    132 <div id="message1" class="updated"><p><?php _e('The active theme is broken. Reverting to the default theme.'); ?></p></div>
    133 <?php elseif ( isset($_GET['activated']) ) :
    134                 if ( isset( $_GET['previewed'] ) ) { ?>
    135                 <div id="message2" class="updated"><p><?php printf( __( 'Settings saved and theme activated. <a href="%s">Visit site</a>' ), home_url( '/' ) ); ?></p></div>
    136                 <?php } else { ?>
    137 <div id="message2" class="updated"><p><?php printf( __( 'New theme activated. <a href="%s">Visit site</a>' ), home_url( '/' ) ); ?></p></div><?php
    138                 }
    139         elseif ( isset($_GET['deleted']) ) : ?>
    140 <div id="message3" class="updated"><p><?php _e('Theme deleted.') ?></p></div>
    141 <?php elseif ( isset( $_GET['delete-active-child'] ) ) : ?>
    142         <div id="message4" class="error"><p><?php _e( 'You cannot delete a theme while it has an active child theme.' ); ?></p></div>
    143 <?php
    144 endif;
     123        <div class="wrap">
     124                <div class="header-content">
     125                        <h2><?php esc_html_e( 'Themes' ); ?></h2>
     126                        <div class="title-count theme-count"><?php echo count( $themes ); ?></div>
     127                        <?php if ( ! is_multisite() && current_user_can( 'install_themes' ) ) : ?>
     128                                <div class="add-new-theme"><a href="<?php echo admin_url( 'theme-install.php' ); ?>" class="hide-if-no-js add-new-h2"><?php echo esc_html_x( 'Add New', 'Add new theme' ); ?></a></div>
     129                        <?php endif; ?>
     130                </div>
    145131
    146 $ct = wp_get_theme();
     132                <?php
     133                if ( ! validate_current_theme() || isset( $_GET['broken'] ) ) : ?>
     134                        <div id="message1" class="updated"><p><?php _e('The active theme is broken. Reverting to the default theme.'); ?></p></div>
     135                <?php elseif ( isset($_GET['activated']) ) :
     136                        if ( isset( $_GET['previewed'] ) ) { ?>
     137                                <div id="message2" class="updated"><p><?php printf( __( 'Settings saved and theme activated. <a href="%s">Visit site</a>' ), home_url( '/' ) ); ?></p></div>
     138                        <?php } else { ?>
     139                                <div id="message2" class="updated"><p><?php printf( __( 'New theme activated. <a href="%s">Visit site</a>' ), home_url( '/' ) ); ?></p></div><?php
     140                        }
     141                elseif ( isset($_GET['deleted']) ) : ?>
     142                        <div id="message3" class="updated"><p><?php _e('Theme deleted.') ?></p></div>
     143                <?php elseif ( isset( $_GET['delete-active-child'] ) ) : ?>
     144                        <div id="message4" class="error"><p><?php _e( 'You cannot delete a theme while it has an active child theme.' ); ?></p></div>
     145                <?php
     146                endif;
    147147
    148 if ( $ct->errors() && ( ! is_multisite() || current_user_can( 'manage_network_themes' ) ) ) {
    149         echo '<div class="error"><p>' . sprintf( __( 'ERROR: %s' ), $ct->errors()->get_error_message() ) . '</p></div>';
    150 }
     148                $ct = wp_get_theme();
    151149
    152 /*
    153 // Certain error codes are less fatal than others. We can still display theme information in most cases.
    154 if ( ! $ct->errors() || ( 1 == count( $ct->errors()->get_error_codes() )
    155         && in_array( $ct->errors()->get_error_code(), array( 'theme_no_parent', 'theme_parent_invalid', 'theme_no_index' ) ) ) ) : ?>
    156 */
     150                if ( $ct->errors() && ( ! is_multisite() || current_user_can( 'manage_network_themes' ) ) ) {
     151                        echo '<div class="error"><p>' . sprintf( __( 'ERROR: %s' ), $ct->errors()->get_error_message() ) . '</p></div>';
     152                }
    157153
    158         // Pretend you didn't see this.
    159         $current_theme_actions = array();
    160         if ( is_array( $submenu ) && isset( $submenu['themes.php'] ) ) {
    161                 foreach ( (array) $submenu['themes.php'] as $item) {
    162                         $class = '';
    163                         if ( 'themes.php' == $item[2] || 'theme-editor.php' == $item[2] || 0 === strpos( $item[2], 'customize.php' ) )
    164                                 continue;
    165                         // 0 = name, 1 = capability, 2 = file
    166                         if ( ( strcmp($self, $item[2]) == 0 && empty($parent_file)) || ($parent_file && ($item[2] == $parent_file)) )
    167                                 $class = ' current';
    168                         if ( !empty($submenu[$item[2]]) ) {
    169                                 $submenu[$item[2]] = array_values($submenu[$item[2]]); // Re-index.
    170                                 $menu_hook = get_plugin_page_hook($submenu[$item[2]][0][2], $item[2]);
    171                                 if ( file_exists(WP_PLUGIN_DIR . "/{$submenu[$item[2]][0][2]}") || !empty($menu_hook))
    172                                         $current_theme_actions[] = "<a class='button button-secondary$class' href='admin.php?page={$submenu[$item[2]][0][2]}'>{$item[0]}</a>";
    173                                 else
    174                                         $current_theme_actions[] = "<a class='button button-secondary$class' href='{$submenu[$item[2]][0][2]}'>{$item[0]}</a>";
    175                         } elseif ( ! empty( $item[2] ) && current_user_can( $item[1] ) ) {
    176                                 $menu_file = $item[2];
     154                /*
     155                // Certain error codes are less fatal than others. We can still display theme information in most cases.
     156                if ( ! $ct->errors() || ( 1 == count( $ct->errors()->get_error_codes() )
     157                        && in_array( $ct->errors()->get_error_code(), array( 'theme_no_parent', 'theme_parent_invalid', 'theme_no_index' ) ) ) ) : ?>
     158                */
    177159
    178                                 if ( current_user_can( 'customize' ) ) {
    179                                         if ( 'custom-header' === $menu_file ) {
    180                                                 $current_theme_actions[] = "<a class='button button-secondary hide-if-no-customize$class' href='customize.php?autofocus[control]=header_image'>{$item[0]}</a>";
    181                                         } elseif ( 'custom-background' === $menu_file ) {
    182                                                 $current_theme_actions[] = "<a class='button button-secondary hide-if-no-customize$class' href='customize.php?autofocus[control]=background_image'>{$item[0]}</a>";
     160                // Pretend you didn't see this.
     161                $current_theme_actions = array();
     162                if ( is_array( $submenu ) && isset( $submenu['themes.php'] ) ) {
     163                        foreach ( (array) $submenu['themes.php'] as $item) {
     164                                $class = '';
     165                                if ( 'themes.php' == $item[2] || 'theme-editor.php' == $item[2] || 0 === strpos( $item[2], 'customize.php' ) )
     166                                        continue;
     167                                // 0 = name, 1 = capability, 2 = file
     168                                if ( ( strcmp($self, $item[2]) == 0 && empty($parent_file)) || ($parent_file && ($item[2] == $parent_file)) )
     169                                        $class = ' current';
     170                                if ( !empty($submenu[$item[2]]) ) {
     171                                        $submenu[$item[2]] = array_values($submenu[$item[2]]); // Re-index.
     172                                        $menu_hook = get_plugin_page_hook($submenu[$item[2]][0][2], $item[2]);
     173                                        if ( file_exists(WP_PLUGIN_DIR . "/{$submenu[$item[2]][0][2]}") || !empty($menu_hook))
     174                                                $current_theme_actions[] = "<a class='button button-secondary$class' href='admin.php?page={$submenu[$item[2]][0][2]}'>{$item[0]}</a>";
     175                                        else
     176                                                $current_theme_actions[] = "<a class='button button-secondary$class' href='{$submenu[$item[2]][0][2]}'>{$item[0]}</a>";
     177                                } elseif ( ! empty( $item[2] ) && current_user_can( $item[1] ) ) {
     178                                        $menu_file = $item[2];
     179
     180                                        if ( current_user_can( 'customize' ) ) {
     181                                                if ( 'custom-header' === $menu_file ) {
     182                                                        $current_theme_actions[] = "<a class='button button-secondary hide-if-no-customize$class' href='customize.php?autofocus[control]=header_image'>{$item[0]}</a>";
     183                                                } elseif ( 'custom-background' === $menu_file ) {
     184                                                        $current_theme_actions[] = "<a class='button button-secondary hide-if-no-customize$class' href='customize.php?autofocus[control]=background_image'>{$item[0]}</a>";
     185                                                }
    183186                                        }
    184                                 }
    185187
    186                                 if ( false !== ( $pos = strpos( $menu_file, '?' ) ) ) {
    187                                         $menu_file = substr( $menu_file, 0, $pos );
    188                                 }
     188                                        if ( false !== ( $pos = strpos( $menu_file, '?' ) ) ) {
     189                                                $menu_file = substr( $menu_file, 0, $pos );
     190                                        }
    189191
    190                                 if ( file_exists( ABSPATH . "wp-admin/$menu_file" ) ) {
    191                                         $current_theme_actions[] = "<a class='button button-secondary$class' href='{$item[2]}'>{$item[0]}</a>";
    192                                 } else {
    193                                         $current_theme_actions[] = "<a class='button button-secondary$class' href='themes.php?page={$item[2]}'>{$item[0]}</a>";
     192                                        if ( file_exists( ABSPATH . "wp-admin/$menu_file" ) ) {
     193                                                $current_theme_actions[] = "<a class='button button-secondary$class' href='{$item[2]}'>{$item[0]}</a>";
     194                                        } else {
     195                                                $current_theme_actions[] = "<a class='button button-secondary$class' href='themes.php?page={$item[2]}'>{$item[0]}</a>";
     196                                        }
    194197                                }
    195198                        }
    196199                }
    197         }
    198200
    199 ?>
     201                ?>
    200202
    201 <div class="theme-browser">
    202         <div class="themes">
     203                <div class="theme-browser">
     204                        <div class="themes">
    203205
    204 <?php
    205 /*
    206  * This PHP is synchronized with the tmpl-theme template below!
    207  */
     206                                <?php
     207                                /*
     208                                * This PHP is synchronized with the tmpl-theme template below!
     209                                */
    208210
    209 foreach ( $themes as $theme ) :
    210         $aria_action = esc_attr( $theme['id'] . '-action' );
    211         $aria_name   = esc_attr( $theme['id'] . '-name' );
    212         ?>
    213 <div class="theme<?php if ( $theme['active'] ) echo ' active'; ?>" tabindex="0" aria-describedby="<?php echo $aria_action . ' ' . $aria_name; ?>">
    214         <?php if ( ! empty( $theme['screenshot'][0] ) ) { ?>
    215                 <div class="theme-screenshot">
    216                         <img src="<?php echo $theme['screenshot'][0]; ?>" alt="" />
    217                 </div>
    218         <?php } else { ?>
    219                 <div class="theme-screenshot blank"></div>
    220         <?php } ?>
    221         <span class="more-details" id="<?php echo $aria_action; ?>"><?php _e( 'Theme Details' ); ?></span>
    222         <div class="theme-author"><?php printf( __( 'By %s' ), $theme['author'] ); ?></div>
     211                                foreach ( $themes as $theme ) :
     212                                        $aria_action = esc_attr( $theme['id'] . '-action' );
     213                                        $aria_name   = esc_attr( $theme['id'] . '-name' );
     214                                        ?>
     215                                        <div class="theme<?php if ( $theme['active'] ) echo ' active'; ?>" tabindex="0" aria-describedby="<?php echo $aria_action . ' ' . $aria_name; ?>">
     216                                                <?php if ( ! empty( $theme['screenshot'][0] ) ) { ?>
     217                                                        <div class="theme-screenshot">
     218                                                                <img src="<?php echo $theme['screenshot'][0]; ?>" alt="" />
     219                                                        </div>
     220                                                <?php } else { ?>
     221                                                        <div class="theme-screenshot blank"></div>
     222                                                <?php } ?>
     223                                                <span class="more-details" id="<?php echo $aria_action; ?>"><?php _e( 'Theme Details' ); ?></span>
     224                                                <div class="theme-author"><?php printf( __( 'By %s' ), $theme['author'] ); ?></div>
    223225
    224         <?php if ( $theme['active'] ) { ?>
    225                 <h3 class="theme-name" id="<?php echo $aria_name; ?>"><span><?php _ex( 'Active:', 'theme' ); ?></span> <?php echo $theme['name']; ?></h3>
    226         <?php } else { ?>
    227                 <h3 class="theme-name" id="<?php echo $aria_name; ?>"><?php echo $theme['name']; ?></h3>
    228         <?php } ?>
     226                                                <?php if ( $theme['active'] ) { ?>
     227                                                        <h3 class="theme-name" id="<?php echo $aria_name; ?>"><span><?php _ex( 'Active:', 'theme' ); ?></span> <?php echo $theme['name']; ?></h3>
     228                                                <?php } else { ?>
     229                                                        <h3 class="theme-name" id="<?php echo $aria_name; ?>"><?php echo $theme['name']; ?></h3>
     230                                                <?php } ?>
    229231
    230         <div class="theme-actions">
     232                                                <div class="theme-actions">
    231233
    232         <?php if ( $theme['active'] ) { ?>
    233                 <?php if ( $theme['actions']['customize'] && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) { ?>
    234                         <a class="button button-primary customize load-customize hide-if-no-customize" href="<?php echo $theme['actions']['customize']; ?>"><?php _e( 'Customize' ); ?></a>
    235                 <?php } ?>
    236         <?php } else { ?>
    237                 <a class="button button-secondary activate" href="<?php echo $theme['actions']['activate']; ?>"><?php _e( 'Activate' ); ?></a>
    238                 <?php if ( current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) { ?>
    239                         <a class="button button-primary load-customize hide-if-no-customize" href="<?php echo $theme['actions']['customize']; ?>"><?php _e( 'Live Preview' ); ?></a>
    240                         <a class="button button-secondary hide-if-customize" href="<?php echo $theme['actions']['preview']; ?>"><?php _e( 'Preview' ); ?></a>
    241                 <?php } ?>
    242         <?php } ?>
     234                                                        <?php if ( $theme['active'] ) { ?>
     235                                                                <?php if ( $theme['actions']['customize'] && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) { ?>
     236                                                                        <a class="button button-primary customize load-customize hide-if-no-customize" href="<?php echo $theme['actions']['customize']; ?>"><?php _e( 'Customize' ); ?></a>
     237                                                                <?php } ?>
     238                                                        <?php } else { ?>
     239                                                                <a class="button button-secondary activate" href="<?php echo $theme['actions']['activate']; ?>"><?php _e( 'Activate' ); ?></a>
     240                                                                <?php if ( current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) { ?>
     241                                                                        <a class="button button-primary load-customize hide-if-no-customize" href="<?php echo $theme['actions']['customize']; ?>"><?php _e( 'Live Preview' ); ?></a>
     242                                                                        <a class="button button-secondary hide-if-customize" href="<?php echo $theme['actions']['preview']; ?>"><?php _e( 'Preview' ); ?></a>
     243                                                                <?php } ?>
     244                                                        <?php } ?>
    243245
    244         </div>
     246                                                </div>
    245247
    246         <?php if ( $theme['hasUpdate'] ) { ?>
    247                 <div class="theme-update"><?php _e( 'Update Available' ); ?></div>
    248         <?php } ?>
    249 </div>
    250 <?php endforeach; ?>
    251         <br class="clear" />
    252         </div>
    253 </div>
    254 <div class="theme-overlay"></div>
     248                                                <?php if ( $theme['hasUpdate'] ) { ?>
     249                                                        <div class="theme-update"><?php _e( 'Update Available' ); ?></div>
     250                                                <?php } ?>
     251                                        </div>
     252                                <?php endforeach; ?>
     253                                <br class="clear" />
     254                        </div>
     255                </div>
     256                <div class="theme-overlay"></div>
    255257
    256 <p class="no-themes"><?php _e( 'No themes found. Try a different search.' ); ?></p>
     258                <p class="no-themes"><?php _e( 'No themes found. Try a different search.' ); ?></p>
    257259
    258 <?php
    259 // List broken themes, if any.
    260 if ( ! is_multisite() && current_user_can('edit_themes') && $broken_themes = wp_get_themes( array( 'errors' => true ) ) ) {
    261 ?>
     260                <?php
     261                // List broken themes, if any.
     262                if ( ! is_multisite() && current_user_can('edit_themes') && $broken_themes = wp_get_themes( array( 'errors' => true ) ) ) {
     263                        ?>
    262264
    263 <div class="broken-themes">
    264 <h3><?php _e('Broken Themes'); ?></h3>
    265 <p><?php _e('The following themes are installed but incomplete. Themes must have a stylesheet and a template.'); ?></p>
     265                        <div class="broken-themes">
     266                                <h3><?php _e('Broken Themes'); ?></h3>
     267                                <p><?php _e('The following themes are installed but incomplete. Themes must have a stylesheet and a template.'); ?></p>
    266268
    267 <?php
    268 $can_delete = current_user_can( 'delete_themes' );
    269 ?>
    270 <table>
    271         <tr>
    272                 <th><?php _ex('Name', 'theme name'); ?></th>
    273                 <th><?php _e('Description'); ?></th>
    274                 <?php if ( $can_delete ) { ?>
    275                         <th></th>
    276                 <?php } ?>
    277                 </tr>
    278         </tr>
    279         <?php foreach ( $broken_themes as $broken_theme ) : ?>
    280                 <tr>
    281                         <td><?php echo $broken_theme->get( 'Name' ) ? $broken_theme->display( 'Name' ) : $broken_theme->get_stylesheet(); ?></td>
    282                         <td><?php echo $broken_theme->errors()->get_error_message(); ?></td>
    283                         <?php
    284                         if ( $can_delete ) {
    285                                 $stylesheet = $broken_theme->get_stylesheet();
    286                                 $delete_url = add_query_arg( array(
    287                                         'action'     => 'delete',
    288                                         'stylesheet' => urlencode( $stylesheet ),
    289                                 ), admin_url( 'themes.php' ) );
    290                                 $delete_url = wp_nonce_url( $delete_url, 'delete-theme_' . $stylesheet );
     269                                <?php
     270                                $can_delete = current_user_can( 'delete_themes' );
    291271                                ?>
    292                                 <td><a href="<?php echo esc_url( $delete_url ); ?>" class="button button-secondary delete-theme"><?php _e( 'Delete' ); ?></a></td>
    293                                 <?php
    294                         }
    295                         ?>
    296                 </tr>
    297         <?php endforeach; ?>
    298 </table>
    299 </div>
     272                                <table>
     273                                        <tr>
     274                                                <th><?php _ex('Name', 'theme name'); ?></th>
     275                                                <th><?php _e('Description'); ?></th>
     276                                                <?php if ( $can_delete ) { ?>
     277                                                        <th></th>
     278                                                <?php } ?>
     279                                        </tr>
     280                                        </tr>
     281                                        <?php foreach ( $broken_themes as $broken_theme ) : ?>
     282                                                <tr>
     283                                                        <td><?php echo $broken_theme->get( 'Name' ) ? $broken_theme->display( 'Name' ) : $broken_theme->get_stylesheet(); ?></td>
     284                                                        <td><?php echo $broken_theme->errors()->get_error_message(); ?></td>
     285                                                        <?php
     286                                                        if ( $can_delete ) {
     287                                                                $stylesheet = $broken_theme->get_stylesheet();
     288                                                                $delete_url = add_query_arg( array(
     289                                                                        'action'     => 'delete',
     290                                                                        'stylesheet' => urlencode( $stylesheet ),
     291                                                                ), admin_url( 'themes.php' ) );
     292                                                                $delete_url = wp_nonce_url( $delete_url, 'delete-theme_' . $stylesheet );
     293                                                                ?>
     294                                                                <td><a href="<?php echo esc_url( $delete_url ); ?>" class="button button-secondary delete-theme"><?php _e( 'Delete' ); ?></a></td>
     295                                                        <?php
     296                                                        }
     297                                                        ?>
     298                                                </tr>
     299                                        <?php endforeach; ?>
     300                                </table>
     301                        </div>
    300302
    301 <?php
    302 }
    303 ?>
    304 </div><!-- .wrap -->
     303                <?php
     304                }
     305                ?>
     306        </div><!-- .wrap -->
    305307
    306308<?php
    307309/*
     
    308310 * The tmpl-theme template is synchronized with PHP above!
    309311 */
    310312?>
    311 <script id="tmpl-theme" type="text/template">
    312         <# if ( data.screenshot[0] ) { #>
    313                 <div class="theme-screenshot">
    314                         <img src="{{ data.screenshot[0] }}" alt="" />
    315                 </div>
    316         <# } else { #>
    317                 <div class="theme-screenshot blank"></div>
    318         <# } #>
    319         <span class="more-details" id="{{ data.id }}-action"><?php _e( 'Theme Details' ); ?></span>
    320         <div class="theme-author"><?php printf( __( 'By %s' ), '{{{ data.author }}}' ); ?></div>
     313        <script id="tmpl-theme" type="text/template">
     314                <# if ( data.screenshot[0] ) { #>
     315                        <div class="theme-screenshot">
     316                                <img src="{{ data.screenshot[0] }}" alt="" />
     317                        </div>
     318                        <# } else { #>
     319                                <div class="theme-screenshot blank"></div>
     320                                <# } #>
     321                                        <span class="more-details" id="{{ data.id }}-action"><?php _e( 'Theme Details' ); ?></span>
     322                                        <div class="theme-author"><?php printf( __( 'By %s' ), '{{{ data.author }}}' ); ?></div>
    321323
    322         <# if ( data.active ) { #>
    323                 <h3 class="theme-name" id="{{ data.id }}-name"><span><?php _ex( 'Active:', 'theme' ); ?></span> {{{ data.name }}}</h3>
    324         <# } else { #>
    325                 <h3 class="theme-name" id="{{ data.id }}-name">{{{ data.name }}}</h3>
    326         <# } #>
     324                                        <# if ( data.active ) { #>
     325                                                <h3 class="theme-name" id="{{ data.id }}-name"><span><?php _ex( 'Active:', 'theme' ); ?></span> {{{ data.name }}}</h3>
     326                                                <# } else { #>
     327                                                        <h3 class="theme-name" id="{{ data.id }}-name">{{{ data.name }}}</h3>
     328                                                        <# } #>
    327329
    328         <div class="theme-actions">
     330                                                                <div class="theme-actions">
    329331
    330         <# if ( data.active ) { #>
    331                 <# if ( data.actions.customize ) { #>
    332                         <a class="button button-primary customize load-customize hide-if-no-customize" href="{{ data.actions.customize }}"><?php _e( 'Customize' ); ?></a>
    333                 <# } #>
    334         <# } else { #>
    335                 <a class="button button-secondary activate" href="{{{ data.actions.activate }}}"><?php _e( 'Activate' ); ?></a>
    336                 <a class="button button-primary load-customize hide-if-no-customize" href="{{{ data.actions.customize }}}"><?php _e( 'Live Preview' ); ?></a>
    337                 <a class="button button-secondary hide-if-customize" href="{{{ data.actions.preview }}}"><?php _e( 'Preview' ); ?></a>
    338         <# } #>
     332                                                                        <# if ( data.active ) { #>
     333                                                                                <# if ( data.actions.customize ) { #>
     334                                                                                        <a class="button button-primary customize load-customize hide-if-no-customize" href="{{ data.actions.customize }}"><?php _e( 'Customize' ); ?></a>
     335                                                                                        <# } #>
     336                                                                                                <# } else { #>
     337                                                                                                        <a class="button button-secondary activate" href="{{{ data.actions.activate }}}"><?php _e( 'Activate' ); ?></a>
     338                                                                                                        <a class="button button-primary load-customize hide-if-no-customize" href="{{{ data.actions.customize }}}"><?php _e( 'Live Preview' ); ?></a>
     339                                                                                                        <a class="button button-secondary hide-if-customize" href="{{{ data.actions.preview }}}"><?php _e( 'Preview' ); ?></a>
     340                                                                                                        <# } #>
    339341
    340         </div>
     342                                                                </div>
    341343
    342         <# if ( data.hasUpdate ) { #>
    343                 <div class="theme-update"><?php _e( 'Update Available' ); ?></div>
    344         <# } #>
    345 </script>
     344                                                                <# if ( data.hasUpdate ) { #>
     345                                                                        <div class="theme-update"><?php _e( 'Update Available' ); ?></div>
     346                                                                        <# } #>
     347        </script>
    346348
    347 <script id="tmpl-theme-single" type="text/template">
    348         <div class="theme-backdrop"></div>
    349         <div class="theme-wrap">
    350                 <div class="theme-header">
    351                         <button class="left dashicons dashicons-no"><span class="screen-reader-text"><?php _e( 'Show previous theme' ); ?></span></button>
    352                         <button class="right dashicons dashicons-no"><span class="screen-reader-text"><?php _e( 'Show next theme' ); ?></span></button>
    353                         <button class="close dashicons dashicons-no"><span class="screen-reader-text"><?php _e( 'Close overlay' ); ?></span></button>
    354                 </div>
    355                 <div class="theme-about">
    356                         <div class="theme-screenshots">
    357                         <# if ( data.screenshot[0] ) { #>
    358                                 <div class="screenshot"><img src="{{ data.screenshot[0] }}" alt="" /></div>
    359                         <# } else { #>
    360                                 <div class="screenshot blank"></div>
    361                         <# } #>
     349        <script id="tmpl-theme-single" type="text/template">
     350                <div class="theme-backdrop"></div>
     351                <div class="theme-wrap">
     352                        <div class="theme-header">
     353                                <button class="left dashicons dashicons-no"><span class="screen-reader-text"><?php _e( 'Show previous theme' ); ?></span></button>
     354                                <button class="right dashicons dashicons-no"><span class="screen-reader-text"><?php _e( 'Show next theme' ); ?></span></button>
     355                                <button class="close dashicons dashicons-no"><span class="screen-reader-text"><?php _e( 'Close overlay' ); ?></span></button>
    362356                        </div>
     357                        <div class="theme-about">
     358                                <div class="theme-screenshots">
     359                                        <# if ( data.screenshot[0] ) { #>
     360                                                <div class="screenshot"><img src="{{ data.screenshot[0] }}" alt="" /></div>
     361                                                <# } else { #>
     362                                                        <div class="screenshot blank"></div>
     363                                                        <# } #>
     364                                </div>
    363365
    364                         <div class="theme-info">
    365                                 <# if ( data.active ) { #>
    366                                         <span class="current-label"><?php _e( 'Current Theme' ); ?></span>
    367                                 <# } #>
    368                                 <h3 class="theme-name">{{{ data.name }}}<span class="theme-version"><?php printf( __( 'Version: %s' ), '{{{ data.version }}}' ); ?></span></h3>
    369                                 <h4 class="theme-author"><?php printf( __( 'By %s' ), '{{{ data.authorAndUri }}}' ); ?></h4>
     366                                <div class="theme-info">
     367                                        <# if ( data.active ) { #>
     368                                                <span class="current-label"><?php _e( 'Current Theme' ); ?></span>
     369                                                <# } #>
     370                                                        <h3 class="theme-name">{{{ data.name }}}<span class="theme-version"><?php printf( __( 'Version: %s' ), '{{{ data.version }}}' ); ?></span></h3>
     371                                                        <h4 class="theme-author"><?php printf( __( 'By %s' ), '{{{ data.authorAndUri }}}' ); ?></h4>
    370372
    371                                 <# if ( data.hasUpdate ) { #>
    372                                 <div class="theme-update-message">
    373                                         <h4 class="theme-update"><?php _e( 'Update Available' ); ?></h4>
    374                                         {{{ data.update }}}
    375                                 </div>
    376                                 <# } #>
    377                                 <p class="theme-description">{{{ data.description }}}</p>
     373                                                        <# if ( data.hasUpdate ) { #>
     374                                                                <div class="theme-update-message">
     375                                                                        <h4 class="theme-update"><?php _e( 'Update Available' ); ?></h4>
     376                                                                        {{{ data.update }}}
     377                                                                </div>
     378                                                                <# } #>
     379                                                                        <p class="theme-description">{{{ data.description }}}</p>
    378380
    379                                 <# if ( data.parent ) { #>
    380                                         <p class="parent-theme"><?php printf( __( 'This is a child theme of %s.' ), '<strong>{{{ data.parent }}}</strong>' ); ?></p>
    381                                 <# } #>
     381                                                                        <# if ( data.parent ) { #>
     382                                                                                <p class="parent-theme"><?php printf( __( 'This is a child theme of %s.' ), '<strong>{{{ data.parent }}}</strong>' ); ?></p>
     383                                                                                <# } #>
    382384
    383                                 <# if ( data.tags ) { #>
    384                                         <p class="theme-tags"><span><?php _e( 'Tags:' ); ?></span> {{{ data.tags }}}</p>
    385                                 <# } #>
     385                                                                                        <# if ( data.tags ) { #>
     386                                                                                                <p class="theme-tags"><span><?php _e( 'Tags:' ); ?></span> {{{ data.tags }}}</p>
     387                                                                                                <# } #>
     388                                </div>
    386389                        </div>
    387                 </div>
    388390
    389                 <div class="theme-actions">
    390                         <div class="active-theme">
    391                                 <a href="{{{ data.actions.customize }}}" class="button button-primary customize load-customize hide-if-no-customize"><?php _e( 'Customize' ); ?></a>
    392                                 <?php echo implode( ' ', $current_theme_actions ); ?>
     391                        <div class="theme-actions">
     392                                <div class="active-theme">
     393                                        <a href="{{{ data.actions.customize }}}" class="button button-primary customize load-customize hide-if-no-customize"><?php _e( 'Customize' ); ?></a>
     394                                        <?php echo implode( ' ', $current_theme_actions ); ?>
     395                                </div>
     396                                <div class="inactive-theme">
     397                                        <# if ( data.actions.activate ) { #>
     398                                                <a href="{{{ data.actions.activate }}}" class="button button-secondary activate"><?php _e( 'Activate' ); ?></a>
     399                                                <# } #>
     400                                                        <a href="{{{ data.actions.customize }}}" class="button button-primary load-customize hide-if-no-customize"><?php _e( 'Live Preview' ); ?></a>
     401                                                        <a href="{{{ data.actions.preview }}}" class="button button-secondary hide-if-customize"><?php _e( 'Preview' ); ?></a>
     402                                </div>
     403
     404                                <# if ( ! data.active && data.actions['delete'] ) { #>
     405                                        <a href="{{{ data.actions['delete'] }}}" class="button button-secondary delete-theme"><?php _e( 'Delete' ); ?></a>
     406                                        <# } #>
    393407                        </div>
    394                         <div class="inactive-theme">
    395                                 <# if ( data.actions.activate ) { #>
    396                                         <a href="{{{ data.actions.activate }}}" class="button button-secondary activate"><?php _e( 'Activate' ); ?></a>
    397                                 <# } #>
    398                                 <a href="{{{ data.actions.customize }}}" class="button button-primary load-customize hide-if-no-customize"><?php _e( 'Live Preview' ); ?></a>
    399                                 <a href="{{{ data.actions.preview }}}" class="button button-secondary hide-if-customize"><?php _e( 'Preview' ); ?></a>
    400                         </div>
    401 
    402                         <# if ( ! data.active && data.actions['delete'] ) { #>
    403                                 <a href="{{{ data.actions['delete'] }}}" class="button button-secondary delete-theme"><?php _e( 'Delete' ); ?></a>
    404                         <# } #>
    405408                </div>
    406         </div>
    407 </script>
     409        </script>
    408410
    409 <?php require( ABSPATH . 'wp-admin/admin-footer.php' );
     411<?php require( ABSPATH . 'wp-admin/admin-footer.php' );
     412 No newline at end of file