Ticket #26601: 26601.3.patch
File 26601.3.patch, 118.0 KB (added by , 6 years ago) |
---|
-
src/wp-admin/css/common.css
262 262 263 263 a:focus { 264 264 color: #124964; 265 266 267 268 269 270 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); 271 271 } 272 272 273 273 .ie8 a:focus { … … 950 950 /* not a part of filter bar, but derived from it, so here for now */ 951 951 .title-count { 952 952 display: inline; 953 top: -3px;954 margin-left: 5px;955 953 margin-right: 20px; 954 vertical-align: bottom; 956 955 } 957 956 957 .add-new-theme { 958 display: inline; 959 position: relative; 960 bottom: -10px; 961 } 962 958 963 .filter-items { 959 964 float: left; 960 965 } … … 1274 1279 1275 1280 .notice-success, 1276 1281 div.updated { 1277 1282 border-color: #7ad03a; 1278 1283 } 1279 1284 1280 1285 .notice-warning { 1281 1286 border-color: #ffba00; 1282 1287 } 1283 1288 1284 1289 .notice-error, 1285 1290 div.error { 1286 1291 border-color: #dd3d36; 1287 1292 } 1288 1293 1289 1294 .notice-info { 1290 1295 border-color: #2ea2cc; 1291 1296 } 1292 1297 1293 1298 .wrap .notice, … … 2590 2595 } 2591 2596 2592 2597 @media print, 2593 2594 2595 2598 (-o-min-device-pixel-ratio: 5/4), 2599 (-webkit-min-device-pixel-ratio: 1.25), 2600 (min-resolution: 120dpi) { 2596 2601 2597 2602 body.plugin-install-php #TB_window, 2598 2603 body.import-php #TB_window, … … 3055 3060 * HiDPI Displays 3056 3061 */ 3057 3062 @media print, 3058 3059 3060 3063 (-o-min-device-pixel-ratio: 5/4), 3064 (-webkit-min-device-pixel-ratio: 1.25), 3065 (min-resolution: 120dpi) { 3061 3066 /* Back-compat for pre-3.8 */ 3062 3067 div.star-holder, 3063 3068 div.star-holder .star-rating { … … 3252 3257 div#post-body.metabox-holder.columns-1 { 3253 3258 overflow-x: hidden; 3254 3259 } 3255 } 3260 } 3261 No newline at end of file -
src/wp-admin/css/themes.css
479 479 .theme-overlay .theme-header .close:focus, 480 480 .theme-overlay .theme-header .right:focus, 481 481 .theme-overlay .theme-header .left:focus { 482 483 484 482 -webkit-box-shadow: none; 483 box-shadow: none; 484 outline: none; 485 485 } 486 486 487 487 .theme-overlay .theme-header .left.disabled, … … 1715 1715 * HiDPI Displays 1716 1716 */ 1717 1717 @media print, 1718 1719 1720 1718 (-o-min-device-pixel-ratio: 5/4), 1719 (-webkit-min-device-pixel-ratio: 1.25), 1720 (min-resolution: 120dpi) { 1721 1721 .wp-full-overlay .collapse-sidebar-arrow { 1722 1722 background-image: url(../images/arrows-2x.png); 1723 1723 -webkit-background-size: 15px 123px; … … 1754 1754 margin-top: 6px; 1755 1755 line-height: normal; 1756 1756 } 1757 } 1757 } 1758 No newline at end of file -
src/wp-admin/js/theme.js
4 4 ( function($) { 5 5 6 6 // Set up our namespace... 7 var themes, l10n;8 themes = wp.themes = wp.themes || {};7 var themes, l10n; 8 themes = wp.themes = wp.themes || {}; 9 9 10 10 // Store the theme data and settings for organized and quick access 11 11 // 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; 14 14 15 15 // Shortcut for isInstall check 16 themes.isInstall = !! themes.data.settings.isInstall;16 themes.isInstall = !! themes.data.settings.isInstall; 17 17 18 18 // Setup app structure 19 _.extend( themes, { model: {}, view: {}, routes: {}, router: {}, template: wp.template });19 _.extend( themes, { model: {}, view: {}, routes: {}, router: {}, template: wp.template }); 20 20 21 themes.Model = Backbone.Model.extend({22 // Adds attributes to the default data coming through the .org themes api23 // Map `id` to `slug` for shared code24 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; 26 26 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 } 31 31 32 // Set the attributes33 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 }); 37 37 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 } 43 44 } 44 } 45 }); 45 }); 46 46 47 47 // Main view controller for themes.php 48 48 // Unifies and renders all available views 49 themes.view.Appearance = wp.Backbone.View.extend({49 themes.view.Appearance = wp.Backbone.View.extend({ 50 50 51 el: '#wpbody-content .wrap .theme-browser',51 el: '#wpbody-content .wrap .theme-browser', 52 52 53 window: $( window ),54 // Pagination instance55 page: 0,53 window: $( window ), 54 // Pagination instance 55 page: 0, 56 56 57 // Sets up a throttler for binding to 'scroll'58 initialize: function( options ) {59 // Scroller checks how far the scroll position is60 _.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' ); 61 61 62 this.SearchView = options.SearchView ? options.SearchView : themes.view.Search;63 // Bind to the scroll event and throttle64 // the results from this.scroller65 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 }, 67 67 68 // Main render control69 render: function() {70 // Setup the main theme view71 // with the current theme collection72 this.view = new themes.view.Themes({73 collection: this.collection,74 parent: this75 });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 }); 76 76 77 // Render search form.78 this.search();77 // Render search form. 78 this.search(); 79 79 80 // Render and append81 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 }, 85 85 86 // Defines search element container87 searchContainer: $( '#wpbody h2:first' ),86 // Defines search element container 87 searchContainer: $( '#wpbody .add-new-theme' ), 88 88 89 // Search input and view90 // for current theme collection91 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; 94 94 95 // Don't render the search if there is only one theme96 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 } 99 99 100 view = new this.SearchView({101 collection: self.collection,102 parent: this103 });100 view = new this.SearchView({ 101 collection: self.collection, 102 parent: this 103 }); 104 104 105 // Render and append after screen title106 view.render();107 this.searchContainer108 .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 }, 111 111 112 // Checks when the user gets close to the bottom113 // of the mage and triggers a theme:scroll event114 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; 117 117 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 ); 121 121 122 if ( bottom > threshold ) { 123 this.trigger( 'theme:scroll' ); 122 if ( bottom > threshold ) { 123 this.trigger( 'theme:scroll' ); 124 } 124 125 } 125 } 126 }); 126 }); 127 127 128 128 // Set up the Collection for our theme data 129 129 // @has 'id' 'name' 'screenshot' 'author' 'authorURI' 'version' 'active' ... 130 themes.Collection = Backbone.Collection.extend({130 themes.Collection = Backbone.Collection.extend({ 131 131 132 model: themes.Model,132 model: themes.Model, 133 133 134 // Search terms135 terms: '',134 // Search terms 135 terms: '', 136 136 137 // Controls searching on the current theme collection138 // and triggers an update event139 doSearch: function( value ) {137 // Controls searching on the current theme collection 138 // and triggers an update event 139 doSearch: function( value ) { 140 140 141 // Don't do anything if we've already done this search142 // Useful because the Search handler fires multiple times per keystroke143 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 } 146 146 147 // Updates terms with the value passed148 this.terms = value;147 // Updates terms with the value passed 148 this.terms = value; 149 149 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 } 154 154 155 // If search is blank, show all themes156 // Useful for resetting the views when you clean the input157 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 } 160 160 161 // Trigger an 'update' event162 this.trigger( 'update' );163 },161 // Trigger an 'update' event 162 this.trigger( 'update' ); 163 }, 164 164 165 // Performs a search within the collection166 // @uses RegExp167 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; 169 169 170 // Start with a full collection171 this.reset( themes.data.themes, { silent: true } );170 // Start with a full collection 171 this.reset( themes.data.themes, { silent: true } ); 172 172 173 // Escape the term string for RegExp meta characters174 term = term.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' );173 // Escape the term string for RegExp meta characters 174 term = term.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' ); 175 175 176 // Consider spaces as word delimiters and match the whole string177 // so matching terms can be combined178 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' ); 180 180 181 // Find results182 // _.filter and .test183 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, '' ); 187 187 188 haystack = _.union( name, data.get( 'id' ), description, author, data.get( 'tags' ) );188 haystack = _.union( name, data.get( 'id' ), description, author, data.get( 'tags' ) ); 189 189 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 } 193 193 194 return match.test( haystack );195 });194 return match.test( haystack ); 195 }); 196 196 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 } 202 202 203 this.reset( results );204 },203 this.reset( results ); 204 }, 205 205 206 // Paginates the collection with a helper method207 // that slices the collection208 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; 211 211 212 // Themes per instance are set at 20213 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 ) ); 215 215 216 return collection;217 },216 return collection; 217 }, 218 218 219 count: false,219 count: false, 220 220 221 // Handles requests for more themes222 // and caches results223 //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 * @static229 * @type Array230 */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; 234 234 235 // Store current query request args236 // 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; 238 238 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 }); 243 243 244 // If the request matches the stored currentQuery.request245 // 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' ); 247 247 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 } 252 252 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 ) { 256 256 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 request262 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 } 264 264 265 // Trigger a collection refresh event266 // 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 ); 269 269 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 } 273 273 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 collection282 // @todo update counter283 self.add( data.themes );284 self.trigger( 'query:success' );285 286 // We are done loading themes for now.287 self.loadingThemes = false;288 289 274 }).fail( function() { 290 275 self.trigger( 'query:fail' ); 291 276 }); 292 }293 294 if ( query.themes.length === 0 ) {295 self.trigger( 'query:empty' );296 277 } 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' ); 299 285 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; 305 288 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 ); 309 313 } 314 }, 310 315 311 this.trigger( 'update' ); 312 this.trigger( 'query:success', this.count ); 313 } 314 }, 316 // Local cache array for API queries 317 queries: [], 315 318 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 }, 318 324 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 }, 324 345 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' ); 342 350 } 343 }, request)344 },345 346 beforeSend: function() {347 if ( ! paginated ) {348 // Spin it349 $( 'body' ).addClass( 'loading-content' ).removeClass( 'no-results' );350 351 } 351 } 352 }); 353 }, 352 }); 353 }, 354 354 355 // Static status controller for when we are loading themes.356 loadingThemes: false357 });355 // Static status controller for when we are loading themes. 356 loadingThemes: false 357 }); 358 358 359 359 // This is the view that controls each theme item 360 360 // that will be displayed on the screen 361 themes.view.Theme = wp.Backbone.View.extend({361 themes.view.Theme = wp.Backbone.View.extend({ 362 362 363 // Wrap theme data on a div.theme element364 className: 'theme',363 // Wrap theme data on a div.theme element 364 className: 'theme', 365 365 366 // Reflects which theme view we have367 // 'grid' (default) or 'detail'368 state: 'grid',366 // Reflects which theme view we have 367 // 'grid' (default) or 'detail' 368 state: 'grid', 369 369 370 // The HTML template for each element to be rendered371 html: themes.template( 'theme' ),370 // The HTML template for each element to be rendered 371 html: themes.template( 'theme' ), 372 372 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 }, 380 380 381 touchDrag: false,381 touchDrag: false, 382 382 383 render: function() {384 var data = this.model.toJSON();385 // Render themes using the html template386 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 }); 390 390 391 // Renders active theme styles392 this.activeTheme();391 // Renders active theme styles 392 this.activeTheme(); 393 393 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 } 397 397 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 }, 402 402 403 // Adds a class to the currently active theme404 // and to the overlay in detailed view mode405 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 }, 410 410 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'); 414 414 415 $('.theme.focus').removeClass('focus');416 $themeToFocus.addClass('focus');417 },415 $('.theme.focus').removeClass('focus'); 416 $themeToFocus.addClass('focus'); 417 }, 418 418 419 // Single theme overlay screen420 // It's shown when clicking a theme421 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; 423 423 424 event = event || window.event;424 event = event || window.event; 425 425 426 // 'enter' and 'space' keys expand the details view when a theme is :focused427 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 } 430 430 431 // Bail if the user scrolled on a touch device432 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 } 435 435 436 // Prevent the modal from showing when the user clicks437 // one of the direct action buttons438 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 } 441 441 442 // Set focused theme to current element443 themes.focusedTheme = this.$el;442 // Set focused theme to current element 443 themes.focusedTheme = this.$el; 444 444 445 this.trigger( 'theme:expand', self.model.cid );446 },445 this.trigger( 'theme:expand', self.model.cid ); 446 }, 447 447 448 preventExpand: function() {449 this.touchDrag = true;450 },448 preventExpand: function() { 449 this.touchDrag = true; 450 }, 451 451 452 preview: function( event ) {453 var self = this,454 current, preview;452 preview: function( event ) { 453 var self = this, 454 current, preview; 455 455 456 // Bail if the user scrolled on a touch device457 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 } 460 460 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 } 465 465 466 // 'enter' and 'space' keys expand the details view when a theme is :focused467 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 } 470 470 471 // pressing enter while focused on the buttons shouldn't open the preview472 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 } 475 475 476 event.preventDefault();476 event.preventDefault(); 477 477 478 event = event || window.event;478 event = event || window.event; 479 479 480 // Set focus to current theme.481 themes.focusedTheme = this.$el;480 // Set focus to current theme. 481 themes.focusedTheme = this.$el; 482 482 483 // Construct a new Preview view.484 preview = new themes.view.Preview({485 model: this.model486 });483 // Construct a new Preview view. 484 preview = new themes.view.Preview({ 485 model: this.model 486 }); 487 487 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(); 491 491 492 // Hide previous/next navigation if there is only one theme493 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 } 498 498 499 // Append preview500 $( 'div.wrap' ).append( preview.el );499 // Append preview 500 $( 'div.wrap' ).append( preview.el ); 501 501 502 // Listen to our preview object503 // 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() { 505 505 506 // Keep local track of current theme model.507 current = self.model;506 // Keep local track of current theme model. 507 current = self.model; 508 508 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 } 513 513 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 ); 516 516 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 } 522 522 523 preview.model = self.current;523 preview.model = self.current; 524 524 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() { 531 531 532 // Keep track of current theme model.533 current = self.model;532 // Keep track of current theme model. 533 current = self.model; 534 534 535 // Bail early if we are at the beginning of the collection536 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 } 539 539 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 } 544 544 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 ); 547 547 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 } 552 552 553 preview.model = self.current;553 preview.model = self.current; 554 554 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 }); 560 560 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 }, 565 565 566 // Handles .disabled classes for previous/next buttons in theme installer preview567 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; 570 570 571 // Disable previous at the zero position572 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 } 575 575 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 } 579 580 } 580 } 581 }); 581 }); 582 582 583 583 // Theme Details view 584 584 // 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({ 586 586 587 // Wrap theme data on a div.theme element588 className: 'theme-overlay',587 // Wrap theme data on a div.theme element 588 className: 'theme-overlay', 589 589 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 }, 596 596 597 // The HTML template for the theme overlay598 html: themes.template( 'theme-single' ),597 // The HTML template for the theme overlay 598 html: themes.template( 'theme-single' ), 599 599 600 render: function() {601 var data = this.model.toJSON();602 this.$el.html( this.html( data ) );603 // Renders active theme styles604 this.activeTheme();605 // Set up navigation events606 this.navigation();607 // Checks screenshot size608 this.screenshotCheck( this.$el );609 // Contain "tabbing" inside the overlay610 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 }, 612 612 613 // Adds a class to the currently active theme614 // and to the overlay in detailed view mode615 activeTheme: function() {616 // Check the model has the active property617 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 }, 619 619 620 // Keeps :focus within the theme details elements621 containFocus: function( $el ) {622 var $target;620 // Keeps :focus within the theme details elements 621 containFocus: function( $el ) { 622 var $target; 623 623 624 // Move focus to the primary action625 _.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 ); 628 628 629 $el.on( 'keydown.wp-themes', function( event ) {629 $el.on( 'keydown.wp-themes', function( event ) { 630 630 631 // Tab key632 if ( event.which === 9 ) {633 $target = $( event.target );631 // Tab key 632 if ( event.which === 9 ) { 633 $target = $( event.target ); 634 634 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 } 643 644 } 644 } 645 }); 646 }, 645 }); 646 }, 647 647 648 // Single theme overlay screen649 // It's shown when clicking a theme650 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; 653 653 654 event = event || window.event;654 event = event || window.event; 655 655 656 // Prevent collapsing detailed view when there is only one theme available657 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 } 660 660 661 // Detect if the click is inside the overlay662 // and don't close it unless the target was663 // the div.back button664 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 ) { 665 665 666 // Add a temporary closing class while overlay fades out667 $( 'body' ).addClass( 'closing-overlay' );666 // Add a temporary closing class while overlay fades out 667 $( 'body' ).addClass( 'closing-overlay' ); 668 668 669 // With a quick fade out animation670 this.$el.fadeOut( 130, function() {671 // Clicking outside the modal box closes the overlay672 $( 'body' ).removeClass( 'closing-overlay' );673 // Handle event cleanup674 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(); 675 675 676 // Get scroll position to avoid jumping to the top677 scroll = document.body.scrollTop;676 // Get scroll position to avoid jumping to the top 677 scroll = document.body.scrollTop; 678 678 679 // Clean the url structure680 themes.router.navigate( themes.router.baseUrl( '' ) );679 // Clean the url structure 680 themes.router.navigate( themes.router.baseUrl( '' ) ); 681 681 682 // Restore scroll position683 document.body.scrollTop = scroll;682 // Restore scroll position 683 document.body.scrollTop = scroll; 684 684 685 // Return focus to the theme div686 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 }, 692 692 693 // Handles .disabled classes for next/previous buttons694 navigation: function() {693 // Handles .disabled classes for next/previous buttons 694 navigation: function() { 695 695 696 // Disable Left/Right when at the start or end of the collection697 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 }, 704 704 705 // Performs the actions to effectively close706 // the theme details overlay707 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 }, 713 713 714 // Confirmation dialog for deleting a theme715 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 }, 718 718 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 }, 724 724 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 }, 730 730 731 // Checks if the theme screenshot is the old 300px width version732 // and adds a corresponding class if it's true733 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; 735 735 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' ); 739 739 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 } 743 744 } 744 } 745 }); 745 }); 746 746 747 747 // Theme Preview view 748 748 // 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({ 750 750 751 className: 'wp-full-overlay expanded',752 el: '.theme-install-overlay',751 className: 'wp-full-overlay expanded', 752 el: '.theme-install-overlay', 753 753 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 }, 761 761 762 // The HTML template for the theme preview763 html: themes.template( 'theme-preview' ),762 // The HTML template for the theme preview 763 html: themes.template( 'theme-preview' ), 764 764 765 render: function() {766 var data = this.model.toJSON();765 render: function() { 766 var data = this.model.toJSON(); 767 767 768 this.$el.html( this.html( data ) );768 this.$el.html( this.html( data ) ); 769 769 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 } ); 771 771 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 }, 777 777 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' ); 781 781 782 // Return focus to the theme div783 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 }); 787 787 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 }, 794 794 795 collapse: function() {795 collapse: function() { 796 796 797 this.$el.toggleClass( 'collapsed' ).toggleClass( 'expanded' );798 return false;799 },797 this.$el.toggleClass( 'collapsed' ).toggleClass( 'expanded' ); 798 return false; 799 }, 800 800 801 keyEvent: function( event ) {802 // The escape key closes the preview803 if ( event.keyCode === 27 ) {804 this.undelegateEvents();805 this.close();806 }807 // The right arrow key, next theme808 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 } 811 811 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 } 815 816 } 816 } 817 }); 817 }); 818 818 819 819 // Controls the rendering of div.themes, 820 820 // 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({ 822 822 823 className: 'themes',824 $overlay: $( 'div.theme-overlay' ),823 className: 'themes', 824 $overlay: $( 'div.theme-overlay' ), 825 825 826 // Number to keep track of scroll position827 // while in theme-overlay mode828 index: 0,826 // Number to keep track of scroll position 827 // while in theme-overlay mode 828 index: 0, 829 829 830 // The theme count element831 count: $( '.wp-filter .theme-count' ),830 // The theme count element 831 count: $( '.wp-filter .theme-count' ), 832 832 833 initialize: function( options ) {834 var self = this;833 initialize: function( options ) { 834 var self = this; 835 835 836 // Set up parent837 this.parent = options.parent;836 // Set up parent 837 this.parent = options.parent; 838 838 839 // Set current view to [grid]840 this.setView( 'grid' );839 // Set current view to [grid] 840 this.setView( 'grid' ); 841 841 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 848 843 self.currentTheme(); 849 self.render( this );850 });851 844 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 }); 860 851 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 }); 864 860 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 }); 868 864 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 }); 874 868 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 } ); 880 874 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 } 885 880 886 // Pressing the left arrow key fires a theme:previousevent887 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 } 890 885 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 } 897 890 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 }, 903 897 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( '' ); 908 903 909 // Constructs the view910 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 ) { 913 908 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 }); 919 913 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 } 926 919 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 } 930 926 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 }, 935 930 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; 937 935 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 ); 944 937 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 } 949 944 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 } 956 949 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 }); 961 956 962 // Binds to theme:expand to show the modal box963 // with the theme details964 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 ); 966 961 967 // 'Add new theme' element shown at the end of the grid968 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 }); 971 966 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 } 974 971 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 }, 979 974 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; 981 979 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 }); 988 981 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 }, 993 988 994 // Renders the overlay with the ThemeDetailsview995 // Uses the current model data996 expand: function( id ) {997 var self = this;989 // Sets current view 990 setView: function( view ) { 991 return view; 992 }, 998 993 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; 1001 998 1002 // Trigger a route update for the currentmodel1003 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 ); 1004 1001 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 ) ); 1008 1004 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' ); 1013 1008 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 }); 1016 1013 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 ); 1025 1016 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 ] ); 1032 1025 1033 // This method renders the next theme on the overlay modal1034 // based on the current position in the collection1035 // @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 }, 1039 1032 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; 1044 1039 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 ); 1047 1044 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 ) { 1051 1047 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(); 1054 1051 1055 }1056 },1052 // Trigger a route update for the current model 1053 self.theme.trigger( 'theme:expand', nextModel.cid ); 1057 1054 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 }, 1064 1057 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; 1069 1064 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 ); 1071 1069 1072 // We have a new theme... 1073 // Close the overlay 1074 this.overlay.closeOverlay(); 1070 if ( previousModel !== undefined ) { 1075 1071 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(); 1078 1075 1076 // Trigger a route update for the current model 1077 self.theme.trigger( 'theme:expand', previousModel.cid ); 1078 1079 } 1079 1080 } 1080 } 1081 }); 1081 }); 1082 1082 1083 1083 // Search input view controller. 1084 themes.view.Search = wp.Backbone.View.extend({1084 themes.view.Search = wp.Backbone.View.extend({ 1085 1085 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, 1090 1090 1091 attributes: {1092 placeholder: l10n.searchPlaceholder,1093 type: 'search'1094 },1091 attributes: { 1092 placeholder: l10n.searchPlaceholder, 1093 type: 'search' 1094 }, 1095 1095 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 }, 1103 1103 1104 initialize: function( options ) {1104 initialize: function( options ) { 1105 1105 1106 this.parent = options.parent;1106 this.parent = options.parent; 1107 1107 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 } ); 1111 1111 1112 },1112 }, 1113 1113 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 = {}; 1117 1117 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 } 1122 1122 1123 // Lose input focus when pressing enter1124 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 } 1127 1127 1128 this.collection.doSearch( event.target.value );1128 this.collection.doSearch( event.target.value ); 1129 1129 1130 // if search is initiated and key is not return1131 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 } 1136 1136 1137 // Update the URL hash1138 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 }, 1144 1144 1145 pushState: function( event ) {1146 var url = themes.router.baseUrl( '' );1145 pushState: function( event ) { 1146 var url = themes.router.baseUrl( '' ); 1147 1147 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 } 1151 1151 1152 this.searching = false;1153 themes.router.navigate( url );1152 this.searching = false; 1153 themes.router.navigate( url ); 1154 1154 1155 }1156 });1155 } 1156 }); 1157 1157 1158 1158 // Sets up the routes events for relevant url queries 1159 1159 // Listens to [theme] and [search] params 1160 themes.Router = Backbone.Router.extend({1160 themes.Router = Backbone.Router.extend({ 1161 1161 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 }, 1169 1169 1170 baseUrl: function( url ) {1171 return 'themes.php' + url;1172 },1170 baseUrl: function( url ) { 1171 return 'themes.php' + url; 1172 }, 1173 1173 1174 themePath: '?theme=',1175 searchPath: '?search=',1174 themePath: '?theme=', 1175 searchPath: '?search=', 1176 1176 1177 search: function( query ) {1178 $( '.wp-filter-search' ).val( query );1179 },1177 search: function( query ) { 1178 $( '.wp-filter-search' ).val( query ); 1179 }, 1180 1180 1181 themes: function() {1182 $( '.wp-filter-search' ).val( '' );1183 },1181 themes: function() { 1182 $( '.wp-filter-search' ).val( '' ); 1183 }, 1184 1184 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 } 1188 1189 } 1189 }1190 1190 1191 });1191 }); 1192 1192 1193 1193 // Execute and setup the application 1194 themes.Run = {1195 init: function() {1196 // Initializes the blog's theme library view1197 // Create a new collection with data1198 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 ); 1199 1199 1200 // Set up the view1201 this.view = new themes.view.Appearance({1202 collection: this.themes1203 });1200 // Set up the view 1201 this.view = new themes.view.Appearance({ 1202 collection: this.themes 1203 }); 1204 1204 1205 this.render();1206 },1205 this.render(); 1206 }, 1207 1207 1208 render: function() {1208 render: function() { 1209 1209 1210 // Render results1211 this.view.render();1212 this.routes();1210 // Render results 1211 this.view.render(); 1212 this.routes(); 1213 1213 1214 Backbone.history.start({1215 root: themes.data.settings.adminUrl,1216 pushState: true,1217 hashChange: false1218 });1219 },1214 Backbone.history.start({ 1215 root: themes.data.settings.adminUrl, 1216 pushState: true, 1217 hashChange: false 1218 }); 1219 }, 1220 1220 1221 routes: function() {1222 var self = this;1223 // Bind to our global thx object1224 // so that the object is available to sub-views1225 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(); 1226 1226 1227 // Handles theme details route event1228 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 }); 1231 1231 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 }); 1236 1236 1237 // Handles search route event1238 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 }); 1241 1241 1242 this.extraRoutes();1243 },1242 this.extraRoutes(); 1243 }, 1244 1244 1245 extraRoutes: function() {1246 return false;1247 }1248 };1245 extraRoutes: function() { 1246 return false; 1247 } 1248 }; 1249 1249 1250 1250 // Extend the main Search view 1251 themes.view.InstallerSearch = themes.view.Search.extend({1251 themes.view.InstallerSearch = themes.view.Search.extend({ 1252 1252 1253 events: {1254 'keyup': 'search'1255 },1253 events: { 1254 'keyup': 'search' 1255 }, 1256 1256 1257 // Handles Ajax request for searching through themes in public repo1258 search: function( event ) {1257 // Handles Ajax request for searching through themes in public repo 1258 search: function( event ) { 1259 1259 1260 // Tabbing or reverse tabbing into the search input shouldn't trigger a search1261 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 } 1264 1264 1265 this.collection = this.options.parent.view.collection;1265 this.collection = this.options.parent.view.collection; 1266 1266 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 } 1271 1271 1272 _.debounce( _.bind( this.doSearch, this ), 300 )( event.target.value );1273 },1272 _.debounce( _.bind( this.doSearch, this ), 300 )( event.target.value ); 1273 }, 1274 1274 1275 doSearch: _.debounce( function( value ) {1276 var request = {};1275 doSearch: _.debounce( function( value ) { 1276 var request = {}; 1277 1277 1278 request.search = value;1278 request.search = value; 1279 1279 1280 // Intercept an [author] search.1281 //1282 // If input value starts with `author:` send a request1283 // 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 } 1288 1288 1289 // Intercept a [tag] search.1290 //1291 // If input value starts with `tag:` send a request1292 // 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 } 1297 1297 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' ); 1300 1300 1301 // Get the themes by sending Ajax POST request to api.wordpress.org/themes1302 // or searching the local cache1303 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 ); 1304 1304 1305 // Set route1306 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 }); 1309 1309 1310 themes.view.Installer = themes.view.Appearance.extend({1310 themes.view.Installer = themes.view.Appearance.extend({ 1311 1311 1312 el: '#wpbody-content .wrap',1312 el: '#wpbody-content .wrap', 1313 1313 1314 // Register events for sorting and filters in theme-navigation1315 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 }, 1324 1324 1325 // Initial render method1326 render: function() {1327 var self = this;1325 // Initial render method 1326 render: function() { 1327 var self = this; 1328 1328 1329 this.search();1330 this.uploader();1329 this.search(); 1330 this.uploader(); 1331 1331 1332 this.collection = new themes.Collection();1332 this.collection = new themes.Collection(); 1333 1333 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() { 1336 1336 1337 // Make sure we are not already loading1338 if ( self.collection.loadingThemes ) {1339 return;1340 }1337 // Make sure we are not already loading 1338 if ( self.collection.loadingThemes ) { 1339 return; 1340 } 1341 1341 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++; 1345 1345 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 }); 1350 1350 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 }); 1355 1355 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 }); 1361 1361 1362 if ( this.view ) {1363 this.view.remove();1364 }1362 if ( this.view ) { 1363 this.view.remove(); 1364 } 1365 1365 1366 // Set ups the view and passes the section argument1367 this.view = new themes.view.Themes({1368 collection: this.collection,1369 parent: this1370 });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 }); 1371 1371 1372 // Reset pagination every time the install view handler is run1373 this.page = 0;1372 // Reset pagination every time the install view handler is run 1373 this.page = 0; 1374 1374 1375 // Render and append1376 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 }, 1380 1380 1381 // Handles all the rendering of the public theme directory1382 browse: function( section ) {1383 // Create a new collection with the proper theme data1384 // for each section1385 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 }, 1387 1387 1388 // Sorting navigation1389 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' ); 1392 1392 1393 event.preventDefault();1393 event.preventDefault(); 1394 1394 1395 $( 'body' ).removeClass( 'filters-applied show-filters' );1395 $( 'body' ).removeClass( 'filters-applied show-filters' ); 1396 1396 1397 // Bail if this is already active1398 if ( $el.hasClass( this.activeClass ) ) {1399 return;1400 }1397 // Bail if this is already active 1398 if ( $el.hasClass( this.activeClass ) ) { 1399 return; 1400 } 1401 1401 1402 this.sort( sort );1402 this.sort( sort ); 1403 1403 1404 // Trigger a router.naviagte update1405 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 }, 1407 1407 1408 sort: function( sort ) {1409 this.clearSearch();1408 sort: function( sort ) { 1409 this.clearSearch(); 1410 1410 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 ); 1413 1413 1414 this.browse( sort );1415 },1414 this.browse( sort ); 1415 }, 1416 1416 1417 // Filters and Tags1418 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' ); 1422 1422 1423 // Bail if this is already active1424 if ( $el.hasClass( this.activeClass ) ) {1425 return;1426 }1423 // Bail if this is already active 1424 if ( $el.hasClass( this.activeClass ) ) { 1425 return; 1426 } 1427 1427 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 ); 1430 1430 1431 if ( ! filter ) {1432 return;1433 }1431 if ( ! filter ) { 1432 return; 1433 } 1434 1434 1435 // Construct the filter request1436 // using the default values1437 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 ] }; 1439 1439 1440 // Get the themes by sending Ajax POST request to api.wordpress.org/themes1441 // or searching the local cache1442 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 }, 1444 1444 1445 // Clicking on a checkbox to add another filter to the request1446 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 }, 1449 1449 1450 // Applying filters triggers a tag request1451 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' ); 1456 1456 1457 if ( event ) {1458 event.preventDefault();1459 }1457 if ( event ) { 1458 event.preventDefault(); 1459 } 1460 1460 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(); 1464 1464 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 }); 1469 1469 1470 // Get the themes by sending Ajax POST request to api.wordpress.org/themes1471 // or searching the local cache1472 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 }, 1474 1474 1475 // Get the checked filters1476 // @return {array} of tags or false1477 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 = []; 1480 1480 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 }); 1484 1484 1485 // When no filters are checked, restore initial state and return1486 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 } 1492 1492 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' ); 1495 1495 1496 return tags;1497 },1496 return tags; 1497 }, 1498 1498 1499 activeClass: 'current',1499 activeClass: 'current', 1500 1500 1501 // Overwrite search container class to append search1502 // in new location1503 searchContainer: $( '.wp-filter .search-form' ),1501 // Overwrite search container class to append search 1502 // in new location 1503 searchContainer: $( '.wp-filter .search-form' ), 1504 1504 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 ) { 1507 1520 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 1521 1518 // Toggle the full filters navigation1519 moreFilters: function( event ) {1520 event.preventDefault();1522 if ( $( 'body' ).hasClass( 'filters-applied' ) ) { 1523 return this.backToFilters(); 1524 } 1521 1525 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 } 1525 1531 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(); 1531 1533 1532 this.clearSearch(); 1534 themes.router.navigate( themes.router.baseUrl( '' ) ); 1535 $( 'body' ).toggleClass( 'show-filters' ); 1536 }, 1533 1537 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; 1537 1543 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(); 1543 1545 1544 event.preventDefault(); 1546 _.each( items.filter( ':checked' ), function( item ) { 1547 $( item ).prop( 'checked', false ); 1548 return self.filtersChecked(); 1549 }); 1550 }, 1545 1551 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 } 1551 1556 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( '' ); 1555 1562 } 1563 }); 1556 1564 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 }, 1559 1573 1560 clearSearch: function() { 1561 $( '#wp-filter-search-input').val( '' ); 1562 } 1563 }); 1574 baseUrl: function( url ) { 1575 return 'theme-install.php' + url; 1576 }, 1564 1577 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=', 1573 1581 1574 baseUrl: function( url) {1575 return 'theme-install.php' + url;1576 },1582 search: function( query ) { 1583 $( '.wp-filter-search' ).val( query ); 1584 }, 1577 1585 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 } 1589 1590 } 1590 } 1591 }); 1591 }); 1592 1592 1593 1593 1594 themes.RunInstaller = {1594 themes.RunInstaller = { 1595 1595 1596 init: function() {1597 // Set up the view1598 // Passes the default 'section' as an option1599 this.view = new themes.view.Installer({1600 section: 'featured',1601 SearchView: themes.view.InstallerSearch1602 });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 }); 1603 1603 1604 // Render results1605 this.render();1604 // Render results 1605 this.render(); 1606 1606 1607 },1607 }, 1608 1608 1609 render: function() {1609 render: function() { 1610 1610 1611 // Render results1612 this.view.render();1613 this.routes();1611 // Render results 1612 this.view.render(); 1613 this.routes(); 1614 1614 1615 Backbone.history.start({1616 root: themes.data.settings.adminUrl,1617 pushState: true,1618 hashChange: false1619 });1620 },1615 Backbone.history.start({ 1616 root: themes.data.settings.adminUrl, 1617 pushState: true, 1618 hashChange: false 1619 }); 1620 }, 1621 1621 1622 routes: function() {1623 var self = this,1624 request = {};1622 routes: function() { 1623 var self = this, 1624 request = {}; 1625 1625 1626 // Bind to our global `wp.themes` object1627 // so that the router is available to sub-views1628 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(); 1629 1629 1630 // Handles `theme` route event1631 // Queries the API for the passed theme slug1632 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 }); 1636 1636 1637 // Handles sorting / browsing routes1638 // Also handles the root URL triggering a sort request1639 // for `featured`, the default view1640 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 }); 1647 1647 1648 // Support the `upload` route by going straight to upload section1649 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 }); 1652 1652 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 }); 1657 1657 1658 this.extraRoutes();1659 },1658 this.extraRoutes(); 1659 }, 1660 1660 1661 extraRoutes: function() {1662 return false;1663 }1664 };1661 extraRoutes: function() { 1662 return false; 1663 } 1664 }; 1665 1665 1666 1666 // 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 } 1673 1673 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 }); 1676 1677 }); 1677 });1678 1678 1679 1679 })( jQuery ); 1680 1680 … … 1703 1703 }; 1704 1704 1705 1705 $(window).resize(function(){ tb_position(); }); 1706 }); 1706 }); 1707 No newline at end of file -
src/wp-admin/themes.php
43 43 // Help tab: Overview 44 44 if ( current_user_can( 'switch_themes' ) ) { 45 45 $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 47 48 49 50 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>'; 51 51 52 52 get_current_screen()->add_help_tab( array( 53 53 'id' => 'overview', … … 106 106 'confirmDelete' => __( "Are you sure you want to delete this theme?\n\nClick 'Cancel' to go back, 'OK' to confirm the delete." ), 107 107 'adminUrl' => parse_url( admin_url(), PHP_URL_PATH ), 108 108 ), 109 110 111 112 113 109 'l10n' => array( 110 'addNew' => __( 'Add New Theme' ), 111 'search' => __( 'Search Installed Themes' ), 112 'searchPlaceholder' => __( 'Search installed themes...' ), // placeholder (no ellipsis) 113 ), 114 114 ) ); 115 115 116 116 add_thickbox(); … … 120 120 require_once( ABSPATH . 'wp-admin/admin-header.php' ); 121 121 ?> 122 122 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> 145 131 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; 147 147 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(); 151 149 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 } 157 153 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 */ 177 159 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 } 183 186 } 184 }185 187 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 } 189 191 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 } 194 197 } 195 198 } 196 199 } 197 }198 200 199 ?>201 ?> 200 202 201 <div class="theme-browser">202 <div class="themes">203 <div class="theme-browser"> 204 <div class="themes"> 203 205 204 <?php205 /*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 */ 208 210 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> 223 225 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 } ?> 229 231 230 <div class="theme-actions">232 <div class="theme-actions"> 231 233 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 } ?> 243 245 244 </div>246 </div> 245 247 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> 255 257 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> 257 259 258 <?php259 // 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 ?> 262 264 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> 266 268 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' ); 291 271 ?> 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> 300 302 301 <?php302 }303 ?>304 </div><!-- .wrap -->303 <?php 304 } 305 ?> 306 </div><!-- .wrap --> 305 307 306 308 <?php 307 309 /* … … 308 310 * The tmpl-theme template is synchronized with PHP above! 309 311 */ 310 312 ?> 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> 321 323 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 <# } #> 327 329 328 <div class="theme-actions">330 <div class="theme-actions"> 329 331 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 <# } #> 339 341 340 </div>342 </div> 341 343 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> 346 348 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> 362 356 </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> 363 365 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> 370 372 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> 378 380 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 <# } #> 382 384 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> 386 389 </div> 387 </div>388 390 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 <# } #> 393 407 </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 <# } #>405 408 </div> 406 </div> 407 </script> 409 </script> 408 410 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