+ Backbone.history.start({
+ root: themes.data.settings.adminUrl,
+ pushState: true,
+ hashChange: false
+ });
+ },
+
+ routes: function() {
+ var self = this;
+ // Bind to our global thx object
+ // so that the object is available to sub-views
+ themes.router = new themes.Router();
+
+ // Handles theme details route event
+ themes.router.on( 'route:theme', function( slug ) {
+ self.view.view.expand( slug );
+ });
+
+ themes.router.on( 'route:themes', function() {
+ self.themes.doSearch( '' );
+ self.view.trigger( 'theme:close' );
+ });
+
+ // Handles search route event
+ themes.router.on( 'route:search', function() {
+ $( '.wp-filter-search' ).trigger( 'keyup' );
+ });
+
+ this.extraRoutes();
+ },
+
+ extraRoutes: function() {
+ return false;
+ }
+};
+
+// Extend the main Search view
+themes.view.InstallerSearch = themes.view.Search.extend({
+
+ events: {
+ 'keyup': 'search'
+ },
+
+ // Handles Ajax request for searching through themes in public repo
+ search: function( event ) {
+
+ // Tabbing or reverse tabbing into the search input shouldn't trigger a search
+ if ( event.type === 'keyup' && ( event.which === 9 || event.which === 16 ) ) {
+ return;
+ }
+
+ this.collection = this.options.parent.view.collection;
+
+ // Clear on escape.
+ if ( event.type === 'keyup' && event.which === 27 ) {
+ event.target.value = '';
+ }
+
+ _.debounce( _.bind( this.doSearch, this ), 300 )( event.target.value );
+ },
+
+ doSearch: _.debounce( function( value ) {
+ var request = {};
+
+ request.search = value;
+
+ // Intercept an [author] search.
+ //
+ // If input value starts with `author:` send a request
+ // for `author` instead of a regular `search`
+ if ( value.substring( 0, 7 ) === 'author:' ) {
+ request.search = '';
+ request.author = value.slice( 7 );
+ }
+
+ // Intercept a [tag] search.
+ //
+ // If input value starts with `tag:` send a request
+ // for `tag` instead of a regular `search`
+ if ( value.substring( 0, 4 ) === 'tag:' ) {
+ request.search = '';
+ request.tag = [ value.slice( 4 ) ];
+ }
+
+ $( '.filter-links li > a.current' ).removeClass( 'current' );
+ $( 'body' ).removeClass( 'show-filters filters-applied' );
+
+ // Get the themes by sending Ajax POST request to api.wordpress.org/themes
+ // or searching the local cache
+ this.collection.query( request );
+
+ // Set route
+ themes.router.navigate( themes.router.baseUrl( themes.router.searchPath + value ), { replace: true } );
+ }, 300 )
+});
+
+themes.view.Installer = themes.view.Appearance.extend({
+
+ el: '#wpbody-content .wrap',
+
+ // Register events for sorting and filters in theme-navigation
+ events: {
+ 'click .filter-links li > a': 'onSort',
+ 'click .theme-filter': 'onFilter',
+ 'click .drawer-toggle': 'moreFilters',
+ 'click .filter-drawer .apply-filters': 'applyFilters',
+ 'click .filter-group [type="checkbox"]': 'addFilter',
+ 'click .filter-drawer .clear-filters': 'clearFilters',
+ 'click .filtered-by': 'backToFilters'
+ },
+
+ // Initial render method
+ render: function() {
+ var self = this;
+
+ this.search();
+ this.uploader();
+
+ this.collection = new themes.Collection();
+
+ // Bump `collection.currentQuery.page` and request more themes if we hit the end of the page.
+ this.listenTo( this, 'theme:end', function() {
+
+ // Make sure we are not already loading
+ if ( self.collection.loadingThemes ) {
+ return;
+ }
+
+ // Set loadingThemes to true and bump page instance of currentQuery.
+ self.collection.loadingThemes = true;
+ self.collection.currentQuery.page++;
+
+ // Use currentQuery.page to build the themes request.
+ _.extend( self.collection.currentQuery.request, { page: self.collection.currentQuery.page } );
+ self.collection.query( self.collection.currentQuery.request );
+ });
+
+ this.listenTo( this.collection, 'query:success', function() {
+ $( 'body' ).removeClass( 'loading-content' );
+ $( '.theme-browser' ).find( 'div.error' ).remove();
+ });
+
+ this.listenTo( this.collection, 'query:fail', function() {
+ $( 'body' ).removeClass( 'loading-content' );
+ $( '.theme-browser' ).find( 'div.error' ).remove();
+ $( '.theme-browser' ).find( 'div.themes' ).before( '<div class="error"><p>' + l10n.error + '</p></div>' );
+ });
+
+ if ( this.view ) {
+ this.view.remove();
+ }
+
+ // Set ups the view and passes the section argument
+ this.view = new themes.view.Themes({
+ collection: this.collection,
+ parent: this
+ });
+
+ // Reset pagination every time the install view handler is run
+ this.page = 0;
+
+ // Render and append
+ this.$el.find( '.themes' ).remove();
+ this.view.render();
+ this.$el.find( '.theme-browser' ).append( this.view.el ).addClass( 'rendered' );
+ },
+
+ // Handles all the rendering of the public theme directory
+ browse: function( section ) {
+ // Create a new collection with the proper theme data
+ // for each section
+ this.collection.query( { browse: section } );
+ },
+
+ // Sorting navigation
+ onSort: function( event ) {
+ var $el = $( event.target ),
+ sort = $el.data( 'sort' );
+
+ event.preventDefault();
+
+ $( 'body' ).removeClass( 'filters-applied show-filters' );
+
+ // Bail if this is already active
+ if ( $el.hasClass( this.activeClass ) ) {
+ return;
+ }
+
+ this.sort( sort );
+
+ // Trigger a router.naviagte update
+ themes.router.navigate( themes.router.baseUrl( themes.router.browsePath + sort ) );
+ },
+
+ sort: function( sort ) {
+ this.clearSearch();
+
+ $( '.filter-links li > a, .theme-filter' ).removeClass( this.activeClass );
+ $( '[data-sort="' + sort + '"]' ).addClass( this.activeClass );
+
+ this.browse( sort );
+ },
+
+ // Filters and Tags
+ onFilter: function( event ) {
+ var request,
+ $el = $( event.target ),
+ filter = $el.data( 'filter' );
+
+ // Bail if this is already active
+ if ( $el.hasClass( this.activeClass ) ) {
+ return;
+ }
+
+ $( '.filter-links li > a, .theme-section' ).removeClass( this.activeClass );
+ $el.addClass( this.activeClass );
+
+ if ( ! filter ) {
+ return;
+ }
+
+ // Construct the filter request
+ // using the default values
+ filter = _.union( filter, this.filtersChecked() );
+ request = { tag: [ filter ] };
+
+ // Get the themes by sending Ajax POST request to api.wordpress.org/themes
+ // or searching the local cache
+ this.collection.query( request );
+ },
+
+ // Clicking on a checkbox to add another filter to the request
+ addFilter: function() {
+ this.filtersChecked();
+ },
+
+ // Applying filters triggers a tag request
+ applyFilters: function( event ) {
+ var name,
+ tags = this.filtersChecked(),
+ request = { tag: tags },
+ filteringBy = $( '.filtered-by .tags' );
+
+ if ( event ) {
+ event.preventDefault();
+ }
+
+ $( 'body' ).addClass( 'filters-applied' );
+ $( '.filter-links li > a.current' ).removeClass( 'current' );
+ filteringBy.empty();
+
+ _.each( tags, function( tag ) {
+ name = $( 'label[for="filter-id-' + tag + '"]' ).text();
+ filteringBy.append( '<span class="tag">' + name + '</span>' );
+ });
+
+ // Get the themes by sending Ajax POST request to api.wordpress.org/themes
+ // or searching the local cache
+ this.collection.query( request );
+ },
+
+ // Get the checked filters
+ // @return {array} of tags or false
+ filtersChecked: function() {
+ var items = $( '.filter-group' ).find( ':checkbox' ),
+ tags = [];
+
+ _.each( items.filter( ':checked' ), function( item ) {
+ tags.push( $( item ).prop( 'value' ) );
+ });
+
+ // When no filters are checked, restore initial state and return
+ if ( tags.length === 0 ) {
+ $( '.filter-drawer .apply-filters' ).find( 'span' ).text( '' );
+ $( '.filter-drawer .clear-filters' ).hide();
+ $( 'body' ).removeClass( 'filters-applied' );
+ return false;
+ }
+
+ $( '.filter-drawer .apply-filters' ).find( 'span' ).text( tags.length );
+ $( '.filter-drawer .clear-filters' ).css( 'display', 'inline-block' );
+
+ return tags;
+ },
+
+ activeClass: 'current',
+
+ // Overwrite search container class to append search
+ // in new location
+ searchContainer: $( '.wp-filter .search-form' ),
+
+ uploader: function() {
+ $( 'a.upload' ).on( 'click', function( event ) {
+ event.preventDefault();
+ $( 'body' ).addClass( 'show-upload-theme' );
+ themes.router.navigate( themes.router.baseUrl( '?upload' ), { replace: true } );
+ });
+ $( 'a.browse-themes' ).on( 'click', function( event ) {
+ event.preventDefault();
+ $( 'body' ).removeClass( 'show-upload-theme' );
+ themes.router.navigate( themes.router.baseUrl( '' ), { replace: true } );
+ });
+ },
+
+ // Toggle the full filters navigation
+ moreFilters: function( event ) {
+ event.preventDefault();
+
+ if ( $( 'body' ).hasClass( 'filters-applied' ) ) {
+ return this.backToFilters();