diff --git a/src/wp-admin/css/customize-controls.css b/src/wp-admin/css/customize-controls.css index f2fbc86ef4..2921129a2b 100644 --- a/src/wp-admin/css/customize-controls.css +++ b/src/wp-admin/css/customize-controls.css @@ -308,17 +308,21 @@ body { } #customize-theme-controls .customize-pane-child.open, -#customize-theme-controls .customize-pane-child.current-panel { +#customize-theme-controls .customize-pane-child.current-panel, +#customize-theme-controls .customize-themes-panel.customize-pane-child.current-panel { -webkit-transform: none; -ms-transform: none; transform: none; } +#customize-theme-controls .customize-themes-panel.customize-pane-child, .section-open #customize-theme-controls .customize-pane-parent, .in-sub-panel #customize-theme-controls .customize-pane-parent, .section-open #customize-info, .in-sub-panel #customize-info, -.in-sub-panel.section-open #customize-theme-controls .customize-pane-child.current-panel { +.in-sub-panel.section-open #customize-theme-controls .customize-pane-child.current-panel, +.in-themes-panel #customize-theme-controls .customize-pane-parent, +.in-themes-panel #customize-info { visibility: hidden; height: 0; overflow: hidden; @@ -329,8 +333,10 @@ body { .section-open #customize-theme-controls .customize-pane-parent.busy, .in-sub-panel #customize-theme-controls .customize-pane-parent.busy, +.in-themes-panel #customize-theme-controls .customize-pane-parent.busy, .section-open #customize-info.busy, .in-sub-panel #customize-info.busy, +.in-themes-panel #customize-info.busy, .busy.section-open.in-sub-panel #customize-theme-controls .customize-pane-child.current-panel, #customize-theme-controls .customize-pane-child.open, #customize-theme-controls .customize-pane-child.current-panel, @@ -340,6 +346,13 @@ body { overflow: auto; } +.in-themes-panel #customize-theme-controls .customize-pane-parent, +.in-themes-panel #customize-info { + -webkit-transform: translateX(100%); + -ms-transform: translateX(100%); + transform: translateX(100%); +} + #customize-theme-controls .customize-pane-child.accordion-section-content, #customize-theme-controls .customize-pane-child.accordion-sub-container { display: block; @@ -1150,21 +1163,15 @@ p.customize-section-description { animation: customize-reload .75s; } -#customize-theme-controls .control-panel-themes { - border-bottom: none; -} - -#customize-theme-controls .control-panel-themes > .accordion-section-title:hover, /* Not a focusable element. */ -#customize-theme-controls .control-panel-themes > .accordion-section-title { +#customize-theme-controls .control-section-themes .accordion-section-title:hover, /* Not a focusable element. */ +#customize-theme-controls .control-section-themes .accordion-section-title { cursor: default; background: #fff; color: #555; border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; border-left: none; - border-right: none; - margin: 0 0 15px 0; - padding-right: 100px; /* Space for the button */ + margin-top: 0; } #customize-theme-controls .control-section-themes .customize-themes-panel .accordion-section-title:first-child:hover, /* Not a focusable element. */ @@ -1172,14 +1179,29 @@ p.customize-section-description { border-top: 0; } -.control-panel-themes .accordion-section-title span.customize-action, +#customize-theme-controls .control-section-themes > .accordion-section-title:hover, /* Not a focusable element. */ +#customize-theme-controls .control-section-themes > .accordion-section-title { + margin: 0 0 15px; +} + +#customize-controls .customize-themes-panel .accordion-section-title { + margin: 15px -8px; +} + +#customize-controls .control-section-themes .accordion-section-title, +#customize-controls .customize-themes-panel .accordion-section-title { + padding-right: 100px; /* Space for the button */ +} + +#customize-controls .control-section-themes .accordion-section-title span.customize-action, #customize-controls .customize-section-title span.customize-action { font-size: 13px; display: block; font-weight: 400; } -.control-panel-themes .accordion-section-title .change-theme { +#customize-controls .control-section-themes .accordion-section-title .change-theme, +#customize-controls .customize-themes-panel .accordion-section-title .customize-theme { position: absolute; right: 10px; top: 50%; @@ -1187,391 +1209,38 @@ p.customize-section-description { font-weight: 400; } -#customize-theme-controls .control-panel-themes > .accordion-section-title:after { +#customize-controls .control-section-themes .accordion-section-title:before { display: none; } -.control-panel-themes .customize-themes-full-container { - position: fixed; - top: 0; - left: 0; - -webkit-transition: .18s left ease-in-out; - transition: .18s left ease-in-out; - margin: 0 0 0 300px; - padding:25px; - overflow-y: scroll; - width: -webkit-calc(100% - 350px); - width: calc(100% - 350px); - height: -webkit-calc(100% - 50px); - height: calc(100% - 50px); - background: #eee; - z-index: 20; -} - -/* Animations for opening the themes panel */ -#customize-header-actions .save, -#customize-header-actions .spinner, -#customize-header-actions .customize-controls-preview-toggle { - position: relative; - top: 0; - -webkit-transition: .18s top ease-in-out; - transition: .18s top ease-in-out; -} - -#customize-footer-actions, -#customize-footer-actions .collapse-sidebar { - bottom: 0; - -webkit-transition: .18s bottom ease-in-out; - transition: .18s bottom ease-in-out; -} - -.in-themes-panel:not(.animating) #customize-header-actions .save, -.in-themes-panel:not(.animating) #customize-header-actions .spinner, -.in-themes-panel:not(.animating) #customize-header-actions .customize-controls-preview-toggle, -.in-themes-panel:not(.animating) #customize-preview, -.in-themes-panel:not(.animating) #customize-footer-actions { - visibility: hidden; -} - -.wp-full-overlay.in-themes-panel { - background: #eee; /* Prevents a black flash when fading in the panel */ -} - -.in-themes-panel #customize-header-actions .save, -.in-themes-panel #customize-header-actions .spinner, -.in-themes-panel #customize-header-actions .customize-controls-preview-toggle { - top: -45px; -} - -.in-themes-panel #customize-footer-actions, -.in-themes-panel #customize-footer-actions .collapse-sidebar { - bottom: -45px; -} - -/* Don't show the theme count while the panel opens, as it's in the wrong place during the animation */ -.in-themes-panel.animating .control-panel-themes .filter-themes-count { - display: none; -} - -.in-themes-panel.wp-full-overlay .wp-full-overlay-sidebar-content { - bottom: 0; -} - -/* Adds a delay before fading in to avoid it "jumping" */ -@-webkit-keyframes themes-fade-in { - 0% { - opacity: 0; - } - 50% { - opacity: 0; - } - 100% { - opacity: 1; - } -} -@keyframes themes-fade-in { - 0% { - opacity: 0; - } - 50% { - opacity: 0; - } - 100% { - opacity: 1; - } -} - -.control-panel-themes .customize-themes-full-container.animate { - -webkit-animation: .6s themes-fade-in 1; - animation: .6s themes-fade-in 1; -} - -.in-themes-panel:not(.animating) .control-panel-themes .filter-themes-count { - -webkit-animation: .6s themes-fade-in 1; - animation: .6s themes-fade-in 1; -} - -.control-panel-themes .filter-themes-count { - position: fixed; - top: 0; - left: 48px; - width: 222px; - padding: 6px 15px; - margin: 0; - line-height: 32px; - text-align: right; - z-index: 10; -} - -.control-panel-themes .filter-themes-count .themes-displayed { - font-weight: 600; - color: #555d66; -} - -.control-panel-themes .filter-themes-count .see-themes, -.control-panel-themes .filter-themes-count .filter-themes { - display: none; -} - - -/* Mobile - toggle between themes and filters */ -@media screen and (max-width:600px) { - - /* Show a spinner in the filters view also, reusing the main customize spinner */ - .in-themes-panel.loading #customize-header-actions .spinner { - position: fixed; - top: 0; - left: 48px; - visibility: visible; - } - - .in-themes-panel.loading.showing-themes #customize-header-actions .spinner { - visibility: hidden; - } - - .control-panel-themes .filter-themes-count { - width: -webkit-calc(100% - 93px); - width: calc(100% - 93px); - } - - .control-panel-themes .filter-themes-count .themes-displayed { - display: none; - } - - .wp-full-overlay:not(.showing-themes) .control-panel-themes .filter-themes-count .see-themes { - display: block; - float: right; - } - - .wp-full-overlay.showing-themes .control-panel-themes .filter-themes-count .filter-themes { - display: block; - float: right; - } - - .in-themes-panel.showing-themes .control-panel-themes .customize-panel-back { - position: fixed; - top: 0; - left: 0; - z-index: 10; - height: 45px; - background: #eee; - } - - .in-themes-panel.showing-themes .control-panel-themes .customize-panel-back:before { - line-height: 45px; - } - - .control-panel-themes .customize-themes-full-container { - width: -webkit-calc(100% - 50px); - width: calc(100% - 50px); - margin: 0; - top: 46px; - height: -webkit-calc(100% - 96px); - height: calc(100% - 96px); - z-index: 1; - display: none; - } - - .showing-themes .control-panel-themes .customize-themes-full-container { - display: block; - } -} - -.control-panel-themes .customize-themes-notifications .notice { - margin: 0 0 25px 0; -} - -.customize-themes-full-container .customize-themes-section { - display: none !important; /* There is unknown JS that perpetually tries to show all theme sections when more items are added. */ - overflow: hidden; -} - -.customize-themes-full-container .customize-themes-section.current-section { - display: list-item !important; /* There is unknown JS that perpetually tries to show all theme sections when more items are added. */ -} - -.theme-section .customize-themes-text-before { - padding: 0 0 8px 15px; - margin: 15px 0 0 0; - line-height: 16px; - border-bottom: 1px solid #ddd; - color: #555d66; -} - -.control-panel-themes .customize-themes-section-title { - width: 100%; - background: #fff; - -webkit-box-shadow: none; - box-shadow: none; - outline: none; - border-top: none; - border-bottom: 1px solid #ddd; - border-left: 4px solid #fff; - border-right: none; - cursor: pointer; - padding: 10px 15px; - position: relative; - text-align: left; - font-size: 14px; - font-weight: 600; - color: #555d66; - text-shadow: none; -} - -.control-panel-themes .theme-section { - margin: 0; - position: relative; -} - -.control-panel-themes .customize-themes-section-title:focus, -.control-panel-themes .customize-themes-section-title:hover { - border-left-color: #0073aa; - color: #0073aa; - background: #f5f5f5; -} - -.control-panel-themes .theme-section .customize-themes-section-title.selected:after { - content: "\f147"; - font: 16px/1 dashicons; +#customize-controls .customize-themes-panel { + padding: 0 8px; + background: #f1f1f1; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; - width: 20px; - height: 20px; - padding: 3px 3px 1px 1px; /* Re-align the icon to the smaller grid */ - -webkit-border-radius: 100%; - border-radius: 100%; - position: absolute; - top: 9px; - right: 15px; - background: #0073aa; - color: #fff; } -.control-panel-themes .customize-themes-section-title.selected { - color: #0073aa; +#customize-controls .customize-themes-panel .accordion-section-title:first-child { + margin-top: 0; } -.control-panel-themes .customize-themes-section-title.themes-section-search_themes { - border-left: none; - padding: 5px 10px 5px 15px; - width: auto; -} - -.control-panel-themes .customize-themes-section-title.themes-section-feature_filter_themes:after, -.control-panel-themes .customize-themes-section-title.themes-section-favorites_themes:after { - content: "\f140"; - font: 20px/1 dashicons; - position: absolute; - right: 15px; - top: 8px; -} - -.control-panel-themes .customize-themes-section-title.themes-section-search_themes .wp-filter-search { - width: 100%; -} - -.control-panel-themes .customize-themes-section-title.themes-section-search_themes.selected, -.control-panel-themes .customize-themes-section-title.themes-section-search_themes:hover { - background: #fff; - cursor: default; -} - -.control-panel-themes .customize-themes-section-title.themes-section-feature_filter_themes { - margin-top: 15px; - border-top: 1px solid #ddd; -} - -.control-panel-themes .filter-details { - background: #f5f5f5; - margin: 0; - padding: 8px 15px; - border-top: none; - border-bottom: 1px solid #ddd; - display: none; -} - -.control-panel-themes .customize-themes-section-title.selected.details-open { - border-bottom-color: #f5f5f5; - border-left-color: #f5f5f5; - background: #f5f5f5; -} - -.control-panel-themes .favorites-form.filter-details label { - padding-bottom: 6px; - display: inline-block; -} - -.control-panel-themes .filter-details .filter-group { - float: none; - width: 100%; - background: transparent; - border: none; - padding: 0; - -webkit-box-shadow: none; - box-shadow: none; -} - -.control-panel-themes .filter-details .filter-group legend button { - padding: 18px 15px 8px 10px; - line-height: 14px; - border-bottom: 1px solid #ddd; - width: 100%; - text-align: left; -} - -.control-panel-themes .filter-details .filter-group legend { - position: relative; - top: 0; - width: 100%; -} - -.control-panel-themes .filter-details .filter-group legend button:after { - content: "\f140"; - font: 20px/1 dashicons; - position: absolute; - bottom: 6px; - right: 5px; -} - -.control-panel-themes .filter-details .filter-group legend button:hover, -.control-panel-themes .filter-details .filter-group legend button:focus { - color: #0073aa; - border-bottom-color: #0073aa; /* Color change for focus style should be acceptable because border-bottom is barely visible previously. */ - outline: none; - -webkit-box-shadow: none; - box-shadow: none; -} - -.control-panel-themes .filter-details .filter-group legend button.open:after { - content: "\f142"; -} - -.control-panel-themes .filter-details .filter-group .filter-group-feature { - display: none; - margin: 0; -} - -.control-panel-themes .filter-details .filter-group-feature label { - border: 1px solid #ddd; - border-top: 0; - background: #fff; - color: #555d66; - margin: 0; - padding: 12px 10px 12px 34px; - width: -webkit-calc(100% - 46px); - width: calc(100% - 46px); - line-height: 16px; +#customize-controls .customize-themes-panel .accordion-section-title:nth-child(2) { + font-size: 14px; font-weight: 600; } -.control-panel-themes .filter-details .filter-group-feature input { - position: absolute; - margin: 12px 10px; +#customize-controls .customize-themes-panel > h2 { + padding: 15px 8px 0 8px; } -.control-panel-themes .filter-details .filter-group-feature label:hover { - color: #0073aa; +#customize-theme-controls .customize-themes-panel .accordion-section-content { + background: transparent; + display: block; +} + +.customize-control.customize-control-theme { + margin-bottom: 8px; } #customize-theme-controls .themes.accordion-section-content { @@ -1581,111 +1250,17 @@ p.customize-section-description { width: 100%; } -.loading .customize-themes-section .spinner { - display: block; - visibility: visible; - position: relative; - clear: both; - width: 20px; - height: 20px; - left: -webkit-calc(50% - 10px); - left: calc(50% - 10px); - float: none; - margin-top: 50px; -} - -.customize-themes-section .filter-drawer { - border-top: none; - display: block; - background: transparent; - padding-top: 5px; -} - -.customize-themes-section .clear-filters { - margin-left: 8px; - display: none; -} - -.customize-themes-section .no-themes { - display: none; -} - -.themes-section-installed_themes .theme .notice-success { - display: none; /* Hide "installed" notice on installed themes tab. */ -} - -.control-panel-themes .theme-browser .theme .theme-actions .button-primary { - margin: 0 0 0 8px; -} - -.customize-control-theme .theme { - width: 100%; - margin: 0; -} - -.customize-control.customize-control-theme { /* override most properties on .customize-control */ - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - width: 18.4%; - margin: 0 2% 2% 0; - padding: 0; - clear: none; -} - -/* 5 columns above 2100px */ -@media screen and (min-width: 2101px) { - .customize-control.customize-control-theme:nth-child(5n) { - margin-right: 0; - } -} - -/* 4 columns up to 2100px */ -@media screen and (min-width: 1601px) and (max-width: 2100px) { - .customize-control.customize-control-theme { - width: 23.5%; - } - - .customize-control.customize-control-theme:nth-child(4n) { - margin-right: 0; - } -} - -/* 3 columns up to 1600px */ -@media screen and (min-width: 1201px) and (max-width: 1600px) { - .customize-control.customize-control-theme { - width: 32%; - } - - .customize-control.customize-control-theme:nth-child(3n) { - margin-right: 0; - } -} - -/* 2 columns up to 1200px */ -@media screen and (min-width: 851px) and (max-width: 1200px) { - .customize-control.customize-control-theme { - width: 49%; - } - - .customize-control.customize-control-theme:nth-child(even) { - margin-right: 0; - } -} - -/* 1 column up to 850 px */ -@media screen and (max-width: 850px) { - .customize-control.customize-control-theme { - width: 100%; - margin: 0 0 3% 0; - } -} - .wp-customizer .theme-browser .themes { padding-bottom: 8px; } +.wp-customizer .theme-browser .theme { + margin: 0; + width: 100%; +} + .wp-customizer .theme-browser .theme .theme-actions { + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; opacity: 1; } @@ -1704,6 +1279,15 @@ p.customize-section-description { width: 100%; } +.control-section-themes .accordion-section-title:after, +.customize-themes-panel .accordion-section-title:after { + display: none; +} + +.customize-themes-panel.control-panel-content { + border-top: 1px solid #ddd; +} + /* Details View */ .wp-customizer .theme-overlay { display: none; @@ -1718,58 +1302,31 @@ p.customize-section-description { z-index: 109; } -/* Avoid a z-index war by resetting elements that should be under the overlay. - This is likely required because of the way that sections and panels are positioned. */ -.wp-customizer.modal-open #customize-header-actions, -.wp-customizer.modal-open .control-panel-themes .filter-themes-count, -.wp-customizer.modal-open .control-panel-themes .customize-themes-section-title.selected:after { - z-index: -1; -} - .wp-customizer .theme-overlay .theme-backdrop { background: rgba( 238, 238, 238, 0.75 ); position: fixed; z-index: 110; } -.wp-customizer .theme-overlay .star-rating { - float: left; - margin-right: 8px; -} - -.wp-customizer .theme-rating .num-ratings { - line-height: 20px; -} - .wp-customizer .theme-overlay .theme-wrap { left: 90px; right: 90px; top: 45px; bottom: 45px; z-index: 120; + max-width: 1740px; /* To ensure that theme screenshots are not displayed larger than 880px wide. */ } .wp-customizer .theme-overlay .theme-actions { - text-align: right; /* Because there're only one or two actions, match the UI pattern of media modals and right-align the action. */ - padding: 10px 15px; + text-align: right; /* Because there's only one action, match the pattern of media modals and right-align the action. */ } -.wp-customizer .theme-overlay .theme-actions .theme-install.preview { - margin-left: 8px; +.ie8 .wp-customizer .theme-overlay .theme-header, +.ie8 .wp-customizer .theme-overlay .theme-about, +.ie8 .wp-customizer .theme-overlay .theme-actions { + position: static; } -.control-panel-themes .theme-actions .delete-theme { - left: 15px; /* these override themes.css on mobile */ - right: auto; - bottom: auto; - position: absolute; -} - -.modal-open .in-themes-panel #customize-controls .wp-full-overlay-sidebar-content { - overflow: visible; /* Prevent the top-level Customizer controls from becoming visible when elements on the right of the details modal are focused. */ -} - - /* Small Screens */ @media (max-width:850px), (max-height:472px) { .wp-customizer .theme-overlay .theme-wrap { diff --git a/src/wp-admin/css/themes.css b/src/wp-admin/css/themes.css index 42721d2bee..5019c1cd18 100644 --- a/src/wp-admin/css/themes.css +++ b/src/wp-admin/css/themes.css @@ -570,7 +570,7 @@ body.folded .theme-browser ~ .theme-overlay .theme-wrap { float: left; margin: 0 30px 0 0; width: 55%; - max-width: 1200px; /* Recommended theme screenshot width, set here to avoid stretching */ + max-width: 880px; text-align: center; } diff --git a/src/wp-admin/customize.php b/src/wp-admin/customize.php index eea5ddcdbb..0a1f9b3efa 100644 --- a/src/wp-admin/customize.php +++ b/src/wp-admin/customize.php @@ -109,8 +109,7 @@ $admin_title = sprintf( $wp_customize->get_document_title_template(), __( 'Loadi ?><?php echo $admin_title; ?> ', - section: section.params.id, - active: true, - theme: theme, - priority: section.loaded + 1 - }, - previewer: api.previewer - } ); - - api.control.add( customizeId, themeControl ); - newThemeControls.push( themeControl ); - section.loaded = section.loaded + 1; - }); - - if ( 1 === page ) { - // Pre-load the first 3 theme screenshots. - _.each( section.controls().slice( 0, 3 ), function ( control ) { - var img, src = control.params.theme.screenshot[0]; - if ( src ) { - img = new Image(); - img.src = src; - } - }); - if ( 'search' === section.params.action ) { - wp.a11y.speak( api.settings.l10n.themeSearchResults.replace( '%d', data.info.results ) ); - } - } else { - Array.prototype.push.apply( section.screenshotQueue, newThemeControls ); // Add new themes to the screenshot queue. + if ( args.completeCallback ) { + args.completeCallback(); } - _.delay( section.renderScreenshots, 100 ); // Wait for the controls to become visible. + } ); - if ( 'installed' === section.action || 100 > themes.length ) { // If we have less than the requested 100 themes, it's the end of the list. - section.fullyLoaded = true; + overlay.addClass( 'in-themes-panel' ); + section.addClass( 'current-panel' ); + + } else if ( ! expanded && section.hasClass( 'current-panel' ) ) { + panel._animateChangeExpanded( function() { + changeBtn.attr( 'tabindex', '0' ); + customizeBtn.attr( 'tabindex', '-1' ); + + changeBtn.focus(); + section.css( 'top', '' ); + + if ( args.completeCallback ) { + args.completeCallback(); } - } else { - if ( 0 === section.loaded ) { - section.container.find( '.no-themes' ).show(); - wp.a11y.speak( section.container.find( '.no-themes' ).text() ); - } else { - section.fullyLoaded = true; - } - } - if ( 'installed' === section.params.action ) { - section.updateCount(); - } else { - section.updateCount( data.info.results ); - } - section.container.find( '.unexpected-error' ).hide(); // Hide error notice in case it was previously shown. + } ); - // This cannot run on request.always, as section.loading may turn false before the new controls load in the success case. - section.headContainer.closest( '.wp-full-overlay' ).removeClass( 'loading' ); - section.loading = false; - }); - request.fail(function( data ) { - if ( 'undefined' === typeof data ) { - section.container.find( '.unexpected-error' ).show(); - wp.a11y.speak( section.container.find( '.unexpected-error' ).text() ); - } else if ( typeof console !== 'undefined' && console.error ) { - console.error( data ); - } - - // This cannot run on request.always, as section.loading may turn false before the new controls load in the success case. - section.headContainer.closest( '.wp-full-overlay' ).removeClass( 'loading' ); - section.loading = false; - }); - }, - - /** - * Determines whether more themes should be loaded, and loads them. - * - * @since 4.7.0 - */ - loadMore: function() { - var section = this, container, bottom, threshold; - if ( ! section.fullyLoaded && ! section.loading ) { - container = section.container.closest( '.customize-themes-full-container' ); - - bottom = container.scrollTop() + container.height(); - threshold = container.prop( 'scrollHeight' ) - 3000; // Use a fixed distance to the bottom of loaded results to avoid unnecessarily loading results sooner when using a percentage of scroll distance. - - if ( bottom > threshold ) { - section.loadControls(); - } - } - }, - - /** - * Event handler for search, feature filter, and favorites input that determines if the term has changed and loads new controls as needed. - * - * @since 4.7.0 - * - * @param {wp.customize.ThemesSection} section The current theme section, passed through the debouncer. - */ - checkTerm: function( section ) { - var newTerm; - - // Find term. - if ( 'search' === section.params.action ) { - newTerm = $( '#wp-filter-search-input' ).val(); - } else if ( 'favorites' === section.params.action ) { - newTerm = $( '#wporg-username-input' ).val(); - } else if ( 'feature_filter' === section.params.action ) { - newTerm = section.term; // Set separately by filtersChecked(), as they're changed. - if ( '' === newTerm ) { - return; - } - } else { - return; - } - - if ( section.term === newTerm && 'feature_filter' !== section.params.action ) { - return; - } - - // Clear the controls in the section. - _.each( section.controls(), function( control ) { - control.container.remove(); - api.control.remove( control.id ); - }); - section.loaded = 0; - section.fullyLoaded = false; - section.screenshotQueue = null; - - if ( '' !== newTerm ) { // Empty term should not show any results. - // Run a new query, with loadControls handling paging, etc. - section.term = newTerm; - section.loadControls(); - if ( ! section.expanded() ) { - section.expand(); // Expand the section if it isn't expanded. - } - } - }, - - /** - * Check for filters checked in the feature filter list. - * - * @since 4.7.0 - */ - filtersChecked: function() { - var section = this, - items = section.container.find( '.filter-group' ).find( ':checkbox' ), - tags = []; - - if ( 'feature_filter' !== section.params.action ) { - return false; - } - - _.each( items.filter( ':checked' ), function( item ) { - tags.push( $( item ).prop( 'value' ) ); - }); - - // When no filters are checked, restore initial state and return - if ( tags.length === 0 ) { - section.term = ''; - } else { - section.term = tags; + overlay.removeClass( 'in-themes-panel' ); + section.removeClass( 'current-panel' ); } }, @@ -1586,15 +1294,12 @@ renderScreenshots: function( ) { var section = this; - // Fill queue initially, or check for more if empty. - if ( section.screenshotQueue === null || 0 === section.screenshotQueue.length ) { - // Add controls that haven't had their screenshots rendered. - section.screenshotQueue = _.filter( section.controls(), function( control ) { - return ! control.screenshotRendered; - }); + // Fill queue initially. + if ( section.screenshotQueue === null ) { + section.screenshotQueue = section.controls(); } - // Are all screenshots rendered (for now)? + // Are all screenshots rendered? if ( ! section.screenshotQueue.length ) { return; } @@ -1629,31 +1334,6 @@ } ); }, - /** - * Update the number of themes in the section. - * - * @since 4.7.0 - */ - updateCount: function ( count ) { - if ( ! count ) { - count = this.loaded; - } - - var displayed = this.container.closest( '.control-panel-content' ).find( '.themes-displayed' ), - countEl = this.container.closest( '.control-panel-content' ).find( '.theme-count' ); - - if ( 0 === count ) { - countEl.text( count ); - } else { - // Animate the count change for emphasis. - displayed.fadeOut( 180, function() { - countEl.text( count ); - displayed.fadeIn( 180 ); - } ); - wp.a11y.speak( api.settings.l10n.announceThemeCount.replace( '%d', count ) ); - } - }, - /** * Advance the modal to the next theme. * @@ -1674,13 +1354,13 @@ * @since 4.2.0 */ getNextTheme: function () { - var section = this, control, next; - control = api.control( section.params.action + '_theme_' + this.currentTheme ); + var control, next; + control = api.control( 'theme_' + this.currentTheme ); next = control.container.next( 'li.customize-control-theme' ); if ( ! next.length ) { return false; } - next = next[0].id.replace( 'customize-control-theme-' + section.params.action, section.params.action + '_theme' ); + next = next[0].id.replace( 'customize-control-', '' ); control = api.control( next ); return control.params.theme; @@ -1706,13 +1386,13 @@ * @since 4.2.0 */ getPreviousTheme: function () { - var section = this, control, previous; - control = api.control( section.params.action + '_theme_' + this.currentTheme ); + var control, previous; + control = api.control( 'theme_' + this.currentTheme ); previous = control.container.prev( 'li.customize-control-theme' ); if ( ! previous.length ) { return false; } - previous = previous[0].id.replace( 'customize-control-theme-' + section.params.action, section.params.action + '_theme' ); + previous = previous[0].id.replace( 'customize-control-', '' ); control = api.control( previous ); return control.params.theme; @@ -1732,6 +1412,57 @@ } }, + /** + * Load theme preview. + * + * @since 4.7.0 + * + * @param {string} themeId Theme ID. + * @returns {jQuery.promise} Promise. + */ + loadThemePreview: function( themeId ) { + var deferred = $.Deferred(), onceProcessingComplete, overlay, urlParser; + + urlParser = document.createElement( 'a' ); + urlParser.href = location.href; + urlParser.search = $.param( _.extend( + api.utils.parseQueryString( urlParser.search.substr( 1 ) ), + { + theme: themeId, + changeset_uuid: api.settings.changeset.uuid + } + ) ); + + overlay = $( '.wp-full-overlay' ); + overlay.addClass( 'customize-loading' ); + + onceProcessingComplete = function() { + var request; + if ( api.state( 'processing' ).get() > 0 ) { + return; + } + + api.state( 'processing' ).unbind( onceProcessingComplete ); + + request = api.requestChangesetUpdate(); + request.done( function() { + $( window ).off( 'beforeunload.customize-confirm' ); + window.location.href = urlParser.href; + } ); + request.fail( function() { + overlay.removeClass( 'customize-loading' ); + } ); + }; + + if ( 0 === api.state( 'processing' ).get() ) { + onceProcessingComplete(); + } else { + api.state( 'processing' ).bind( onceProcessingComplete ); + } + + return deferred.promise(); + }, + /** * Render & show the theme details for a given theme model. * @@ -1740,7 +1471,7 @@ * @param {Object} theme */ showDetails: function ( theme, callback ) { - var section = this; + var section = this, link; callback = callback || function(){}; section.currentTheme = theme.id; section.overlay.html( section.template( theme ) ) @@ -1749,7 +1480,22 @@ $( 'body' ).addClass( 'modal-open' ); section.containFocus( section.overlay ); section.updateLimits(); - wp.a11y.speak( api.settings.l10n.announceThemeDetails.replace( '%s', theme.name ) ); + + link = section.overlay.find( '.inactive-theme > a' ); + + link.on( 'click', function( event ) { + event.preventDefault(); + + // Short-circuit if request is currently being made. + if ( link.hasClass( 'disabled' ) ) { + return; + } + link.addClass( 'disabled' ); + + section.loadThemePreview( theme.id ).fail( function() { + link.removeClass( 'disabled' ); + } ); + } ); callback(); }, @@ -1761,7 +1507,7 @@ closeDetails: function () { $( 'body' ).removeClass( 'modal-open' ); this.overlay.fadeOut( 'fast' ); - api.control( this.params.action + '_theme_' + this.currentTheme ).container.find( '.theme' ).focus(); + api.control( 'theme_' + this.currentTheme ).focus(); }, /** @@ -1841,8 +1587,8 @@ } if ( ! panel.contentContainer.parent().is( panel.headContainer ) ) { container.append( panel.contentContainer ); + panel.renderContent(); } - panel.renderContent(); panel.deferred.embedded.resolve(); }, @@ -2035,301 +1781,6 @@ } }); - - /** - * wp.customize.ThemesPanel - * - * Custom section for themes that displays without the customize preview. - * - * @constructor - * @augments wp.customize.Panel - * @augments wp.customize.Container - */ - api.ThemesPanel = api.Panel.extend({ - installingThemes: [], - - /** - * @since 4.7.0 - */ - attachEvents: function () { - var panel = this; - - // Attach regular panel events. - api.Panel.prototype.attachEvents.apply( this ); - - // Collapse panel to customize the current theme. - panel.contentContainer.on( 'click', '.customize-theme', function() { - panel.collapse(); - }); - - // Toggle between filtering and browsing themes on mobile. - panel.contentContainer.on( 'click', '.see-themes, .filter-themes', function() { - $( '.wp-full-overlay' ).toggleClass( 'showing-themes' ); - }); - - // Install (and maybe preview) a theme. - panel.contentContainer.on( 'click', '.theme-install', function( event ) { - panel.installTheme( event ); - }); - - // Update a theme. Theme cards have the class, the details modal has the id. - panel.contentContainer.on( 'click', '.update-theme, #update-theme', function( event ) { - // #update-theme is a link. - event.preventDefault(); - event.stopPropagation(); - - panel.updateTheme( event ); - }); - - // Delete a theme. - panel.contentContainer.on( 'click', '.delete-theme', function( event ) { - panel.deleteTheme( event ); - }); - - _.bindAll( this, 'installTheme', 'updateTheme' ); - }, - - /** - * Update UI to reflect expanded state - * - * @since 4.7.0 - * - * @param {Boolean} expanded - * @param {Object} args - * @param {Boolean} args.unchanged - * @param {Function} args.completeCallback - */ - onChangeExpanded: function ( expanded, args ) { - - // Expand/collapse the panel normally. - api.Panel.prototype.onChangeExpanded.apply( this, [ expanded, args ] ); - - // Immediately call the complete callback if there were no changes - if ( args.unchanged ) { - if ( args.completeCallback ) { - args.completeCallback(); - } - return; - } - - // Note: there is a second argument 'args' passed - var panel = this, - overlay = panel.headContainer.closest( '.wp-full-overlay' ); - - if ( expanded ) { - overlay - .addClass( 'in-themes-panel' ).addClass( 'showing-themes' ) - .delay( 200 ).find( '.customize-themes-full-container' ).addClass( 'animate' ); - - // Automatically open the installed themes section. - api.section( 'installed_themes' ).expand(); - } else { - overlay - .removeClass( 'in-themes-panel' ) - .find( '.customize-themes-full-container' ).removeClass( 'animate' ); - } - }, - - /** - * Install a theme via wp.updates. - * - * @since 4.7.0 - */ - installTheme: function( event ) { - var panel = this, preview = false, slug = $( event.target ).data( 'slug' ); - - if ( -1 !== $.inArray( this.installingThemes, slug ) ) { - return; // Theme is already being installed. - } - - wp.updates.maybeRequestFilesystemCredentials( event ); - - $( document ).one( 'wp-theme-install-success', function( event, response ) { - var theme = false, customizeId, themeControl; - if ( preview ) { - - panel.loadThemePreview( slug ).fail( function() { - $( '.wp-full-overlay' ).removeClass( 'customize-loading' ); - } ); - - } else { - api.control.each( function( control ) { - if ( 'theme' === control.params.type && control.params.theme.id === response.slug ) { - theme = control.params.theme; // Used below to add theme control. - control.rerenderAsInstalled( true ); - } - }); - - // Don't add the same theme more than once. - if ( ! theme || 'undefined' !== typeof api.control( 'installed_theme_' + theme.id ) ) { - return; - } - - // Add theme control to installed section. - theme.type = 'installed'; - customizeId = 'installed_theme_' + theme.id; - themeControl = new api.controlConstructor.theme( customizeId, { - params: { - type: 'theme', - content: $( '
  • ' ).attr( 'id', 'customize-control-theme-installed_' + theme.id ).prop( 'outerHTML' ), - section: 'installed_themes', - active: true, - theme: theme, - priority: 0 // Add all newly-installed themes to the top. - }, - previewer: api.previewer - } ); - - api.control.add( customizeId, themeControl ); - api.control( customizeId ).container.trigger( 'render-screenshot' ); - - // Close the details modal if it's open to the installed theme. - api.section.each( function( section ) { - if ( 'themes' === section.params.type ) { - if ( theme.id === section.currentTheme ) { // Don't close the modal if the user has navigated elsewhere. - section.closeDetails(); - } - } - }); - } - } ); - - this.installingThemes.push( $( event.target ).data( 'slug' ) ); // Note: we don't remove elements from installingThemes, since they shouldn't be installed again. - wp.updates.installTheme( { - slug: slug - } ); - - // Also preview the theme as the event is triggered on Install & Preview. - if ( $( event.target ).hasClass( 'preview' ) ) { - preview = true; - $( '.wp-full-overlay' ).addClass( 'customize-loading' ); - } - }, - - /** - * Load theme preview. - * - * @since 4.7.0 - * - * @param {string} themeId Theme ID. - * @returns {jQuery.promise} Promise. - */ - loadThemePreview: function( themeId ) { - var deferred = $.Deferred(), onceProcessingComplete, overlay, urlParser; - - urlParser = document.createElement( 'a' ); - urlParser.href = location.href; - urlParser.search = $.param( _.extend( - api.utils.parseQueryString( urlParser.search.substr( 1 ) ), - { - theme: themeId, - changeset_uuid: api.settings.changeset.uuid - } - ) ); - - overlay = $( '.wp-full-overlay' ); - overlay.addClass( 'customize-loading' ); - - onceProcessingComplete = function() { - var request; - if ( api.state( 'processing' ).get() > 0 ) { - return; - } - - api.state( 'processing' ).unbind( onceProcessingComplete ); - - request = api.requestChangesetUpdate(); - request.done( function() { - $( window ).off( 'beforeunload.customize-confirm' ); - window.location.href = urlParser.href; - } ); - request.fail( function() { - overlay.removeClass( 'customize-loading' ); - } ); - }; - - if ( 0 === api.state( 'processing' ).get() ) { - onceProcessingComplete(); - } else { - api.state( 'processing' ).bind( onceProcessingComplete ); - } - - return deferred.promise(); - }, - - /** - * Update a theme via wp.updates. - * - * @since 4.7.0 - */ - updateTheme: function( event ) { - wp.updates.maybeRequestFilesystemCredentials( event ); - - $( document ).one( 'wp-theme-update-success', function( event, response ) { - // Rerender the control to reflect the update. - api.control.each( function( control ) { - if ( 'theme' === control.params.type && control.params.theme.id === response.slug ) { - control.params.theme.hasUpdate = false; - control.rerenderAsInstalled( true ); - } - }); - } ); - - wp.updates.updateTheme( { - slug: $( event.target ).closest( '.notice' ).data( 'slug' ) - } ); - }, - - /** - * Delete a theme via wp.updates. - * - * @since 4.7.0 - */ - deleteTheme: function( event ) { - var theme, section; - theme = $( event.target ).data( 'slug' ); - section = api.section( 'installed_themes' ); - - event.preventDefault(); - - // Confirmation dialog for deleting a theme. - if ( ! window.confirm( api.settings.l10n.confirmDeleteTheme ) ) { - return; - } - - wp.updates.maybeRequestFilesystemCredentials( event ); - - $( document ).one( 'wp-theme-delete-success', function() { - var control = api.control( 'installed_theme_' + theme ); - - // Remove theme control. - control.container.remove(); - api.control.remove( control.id ); - - // Update installed count. - section.loaded = section.loaded - 1; - section.updateCount(); - - // Rerender any other theme controls as uninstalled. - api.control.each( function( control ) { - if ( 'theme' === control.params.type && control.params.theme.id === theme ) { - control.rerenderAsInstalled( false ); - } - }); - } ); - - wp.updates.deleteTheme( { - slug: theme - } ); - - // Close modal and focus the section. - section.closeDetails(); - section.focus(); - } - - }); - - /** * A Customizer Control. * @@ -2644,7 +2095,7 @@ * @param {Boolean} active * @param {Object} args * @param {Number} args.duration - * @param {Function} args.completeCallback + * @param {Callback} args.completeCallback */ onChangeActive: function ( active, args ) { if ( args.unchanged ) { @@ -3815,7 +3266,31 @@ api.ThemeControl = api.Control.extend({ touchDrag: false, - screenshotRendered: false, + isRendered: false, + + /** + * Defer rendering the theme control until the section is displayed. + * + * @since 4.2.0 + */ + renderContent: function () { + var control = this, + renderContentArgs = arguments; + + api.section( control.section(), function( section ) { + if ( section.expanded() ) { + api.Control.prototype.renderContent.apply( control, renderContentArgs ); + control.isRendered = true; + } else { + section.expanded.bind( function( expanded ) { + if ( expanded && ! control.isRendered ) { + api.Control.prototype.renderContent.apply( control, renderContentArgs ); + control.isRendered = true; + } + } ); + } + } ); + }, /** * @since 4.2.0 @@ -3839,11 +3314,20 @@ } // Prevent the modal from showing when the user clicks the action button. - if ( $( event.target ).is( '.theme-actions .button, .update-theme' ) ) { + if ( $( event.target ).is( '.theme-actions .button' ) ) { + return; + } + + api.section( control.section() ).loadThemePreview( control.params.theme.id ); + }); + + control.container.on( 'click keydown', '.theme-actions .theme-details', function( event ) { + if ( api.utils.isKeydownButNotEnterEvent( event ) ) { return; } event.preventDefault(); // Keep this AFTER the key filter above + api.section( control.section() ).showDetails( control.params.theme ); }); @@ -3854,12 +3338,11 @@ if ( source ) { $screenshot.attr( 'src', source ); } - control.screenshotRendered = true; }); }, /** - * Show or hide the theme based on the presence of the term in the title, description, tags, and author. + * Show or hide the theme based on the presence of the term in the title, description, and author. * * @since 4.2.0 */ @@ -3875,23 +3358,6 @@ } else { control.deactivate(); } - }, - - /** - * Rerender the theme from its JS template with the installed type. - * - * @since 4.7.0 - */ - rerenderAsInstalled: function( installed ) { - var control = this, section; - if ( installed ) { - control.params.theme.type = 'installed'; - } else { - section = api.section( control.params.section ); - control.params.theme.type = section.params.action; - } - control.renderContent(); // replaces existing content - control.container.trigger( 'render-screenshot' ); } }); @@ -4584,9 +4050,7 @@ background_position: api.BackgroundPositionControl, theme: api.ThemeControl }; - api.panelConstructor = { - themes: api.ThemesPanel - }; + api.panelConstructor = {}; api.sectionConstructor = { themes: api.ThemesSection }; @@ -4704,10 +4168,6 @@ // Sort the sections within each panel api.panel.each( function ( panel ) { - if ( 'themes' === panel.id ) { - return; // Don't reflow theme sections, as doing so moves them after the themes container. - } - var sections = panel.sections(), sectionHeadContainers = _.pluck( sections, 'headContainer' ); rootNodes.push( panel ); @@ -5349,16 +4809,6 @@ // Collapse the most granular expanded object. collapsedObject = expandedControls[0] || expandedSections[0] || expandedPanels[0]; if ( collapsedObject ) { - if ( 'themes' === collapsedObject.params.type ) { - // Themes panel or section. - if ( $( 'body' ).hasClass( 'modal-open' ) ) { - collapsedObject.closeDetails(); - } else { - // If we're collapsing a section, collapse the panel also. - wp.customize.panel( 'themes' ).collapse(); - } - return; - } collapsedObject.collapse(); event.preventDefault(); } diff --git a/src/wp-admin/js/updates.js b/src/wp-admin/js/updates.js index d15a79a620..400842ebd4 100644 --- a/src/wp-admin/js/updates.js +++ b/src/wp-admin/js/updates.js @@ -181,11 +181,7 @@ if ( $notice.length ) { $notice.replaceWith( $adminNotice ); } else { - if ( 'customize' === pagenow ) { - $( '.customize-themes-notifications' ).append( $adminNotice ); - } else { - $( '.wrap' ).find( '> h1' ).after( $adminNotice ); - } + $( '.wrap' ).find( '> h1' ).after( $adminNotice ); } $document.trigger( 'wp-updates-notice-added' ); @@ -930,17 +926,6 @@ if ( 'themes-network' === pagenow ) { $notice = $( '[data-slug="' + args.slug + '"]' ).find( '.update-message' ).removeClass( 'notice-error' ).addClass( 'updating-message notice-warning' ).find( 'p' ); - } else if ( 'customize' === pagenow ) { - - // Update the theme details UI. - $notice = $( '#update-theme' ).closest( '.notice' ).removeClass( 'notice-large' ); - - $notice.find( 'h3' ).remove(); - - // Add the top-level UI, and update both. - $notice = $notice.add( $( '#customize-control-theme-installed_' + args.slug ).find( '.update-message' ) ); - $notice = $notice.addClass( 'updating-message' ).find( 'p' ); - } else { $notice = $( '#update-theme' ).closest( '.notice' ).removeClass( 'notice-large' ); @@ -983,10 +968,6 @@ }, $notice, newText; - if ( 'customize' === pagenow ) { - $theme = wp.customize.control( 'installed_theme_' + response.slug ).container; - } - if ( 'themes-network' === pagenow ) { $notice = $theme.find( '.update-message' ); @@ -1041,10 +1022,6 @@ return; } - if ( 'customize' === pagenow ) { - $theme = wp.customize.control( 'installed_theme_' + response.slug ).container; - } - if ( 'themes-network' === pagenow ) { $notice = $theme.find( '.update-message ' ); } else { @@ -1181,23 +1158,12 @@ return; } - if ( 'customize' === pagenow ) { - if ( $document.find( 'body' ).hasClass( 'modal-open' ) ) { - $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); - $card = $( '.theme-overlay .theme-info' ).prepend( $message ); - } else { - $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); - $card = $button.closest( '.theme' ).addClass( 'theme-install-failed' ).append( $message ); - } - $( '.wp-full-overlay' ).removeClass( 'customize-loading' ); + if ( $document.find( 'body' ).hasClass( 'full-overlay-active' ) ) { + $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); + $card = $( '.install-theme-info' ).prepend( $message ); } else { - if ( $document.find( 'body' ).hasClass( 'full-overlay-active' ) ) { - $button = $( '.theme-install[data-slug="' + response.slug + '"]' ); - $card = $( '.install-theme-info' ).prepend( $message ); - } else { - $card = $( '[data-slug="' + response.slug + '"]' ).removeClass( 'focus' ).addClass( 'theme-install-failed' ).append( $message ); - $button = $card.find( '.theme-install' ); - } + $card = $( '[data-slug="' + response.slug + '"]' ).removeClass( 'focus' ).addClass( 'theme-install-failed' ).append( $message ); + $button = $card.find( '.theme-install' ); } $button diff --git a/src/wp-includes/class-wp-customize-manager.php b/src/wp-includes/class-wp-customize-manager.php index a67ccfbb90..30581388d9 100644 --- a/src/wp-includes/class-wp-customize-manager.php +++ b/src/wp-includes/class-wp-customize-manager.php @@ -295,7 +295,6 @@ final class WP_Customize_Manager { require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menus-panel.php' ); - require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-panel.php' ); require_once( ABSPATH . WPINC . '/customize/class-wp-customize-themes-section.php' ); require_once( ABSPATH . WPINC . '/customize/class-wp-customize-sidebar-section.php' ); require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-section.php' ); @@ -351,7 +350,6 @@ final class WP_Customize_Manager { add_action( 'wp_ajax_customize_save', array( $this, 'save' ) ); add_action( 'wp_ajax_customize_refresh_nonces', array( $this, 'refresh_nonces' ) ); - add_action( 'wp_ajax_customize-load-themes', array( $this, 'load_themes_ajax' ) ); add_action( 'customize_register', array( $this, 'register_controls' ) ); add_action( 'customize_register', array( $this, 'register_dynamic_settings' ), 11 ); // allow code to create settings first @@ -368,12 +366,6 @@ final class WP_Customize_Manager { // Export the settings to JS via the _wpCustomizeSettings variable. add_action( 'customize_controls_print_footer_scripts', array( $this, 'customize_pane_settings' ), 1000 ); - - // Add theme update notices. - if ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) ) { - require_once( ABSPATH . '/wp-admin/includes/update.php' ); - add_action( 'customize_controls_print_footer_scripts', 'wp_print_admin_notice_templates' ); - } } /** @@ -2758,9 +2750,6 @@ final class WP_Customize_Manager { foreach ( $this->controls as $control ) { $control->enqueue(); } - if ( ! is_multisite() && ( current_user_can( 'install_themes' ) || current_user_can( 'update_themes' ) || current_user_can( 'delete_themes' ) ) ) { - wp_enqueue_script( 'updates' ); - } } /** @@ -2975,7 +2964,6 @@ final class WP_Customize_Manager { $nonces = array( 'save' => wp_create_nonce( 'save-customize_' . $this->get_stylesheet() ), 'preview' => wp_create_nonce( 'preview-customize_' . $this->get_stylesheet() ), - 'switch-themes' => wp_create_nonce( 'switch-themes' ), ); /** @@ -3049,14 +3037,6 @@ final class WP_Customize_Manager { 'autofocus' => $this->get_autofocus(), 'documentTitleTmpl' => $this->get_document_title_template(), 'previewableDevices' => $this->get_previewable_devices(), - 'l10n' => array( - 'confirmDeleteTheme' => __( 'Are you sure you want to delete this theme?' ), - /* translators: %d is the number of theme search results, which cannot consider singular vs. plural forms */ - 'themeSearchResults' => __( '%d themes found' ), - /* translators: %d is the number of themes being displayed, which cannot consider singular vs. plural forms */ - 'announceThemeCount' => __( 'Displaying %d themes' ), - 'announceThemeDetails' => __( 'Showing details for theme: %s' ), - ), ); // Prepare Customize Section objects to pass to JavaScript. @@ -3160,10 +3140,8 @@ final class WP_Customize_Manager { /* Panel, Section, and Control Types */ $this->register_panel_type( 'WP_Customize_Panel' ); - $this->register_panel_type( 'WP_Customize_Themes_Panel' ); $this->register_section_type( 'WP_Customize_Section' ); $this->register_section_type( 'WP_Customize_Sidebar_Section' ); - $this->register_section_type( 'WP_Customize_Themes_Section' ); $this->register_control_type( 'WP_Customize_Color_Control' ); $this->register_control_type( 'WP_Customize_Media_Control' ); $this->register_control_type( 'WP_Customize_Upload_Control' ); @@ -3174,80 +3152,50 @@ final class WP_Customize_Manager { $this->register_control_type( 'WP_Customize_Site_Icon_Control' ); $this->register_control_type( 'WP_Customize_Theme_Control' ); - /* Themes (controls are loaded via ajax) */ + /* Themes */ - $this->add_panel( new WP_Customize_Themes_Panel( $this, 'themes', array( - 'title' => $this->theme()->display( 'Name' ), - 'description' => __( 'Once themes are installed, you can live-preview them on your site, customize them, and publish your new design. Browse available themes via the filters in this menu.' ), - 'capability' => 'switch_themes', - 'priority' => 0, + $this->add_section( new WP_Customize_Themes_Section( $this, 'themes', array( + 'title' => $this->theme()->display( 'Name' ), + 'capability' => 'switch_themes', + 'priority' => 0, ) ) ); - $this->add_section( new WP_Customize_Themes_Section( $this, 'installed_themes', array( - 'title' => __( 'Installed' ), - 'text_before' => __( 'Your local site' ), - 'action' => 'installed', - 'capability' => 'switch_themes', - 'panel' => 'themes', - 'priority' => 0, - ) ) ); - - if ( ! is_multisite() ) { - $this->add_section( new WP_Customize_Themes_Section( $this, 'search_themes', array( - 'title' => __( 'Search themes…' ), - 'text_before' => __( 'Browse all WordPress.org themes' ), - 'action' => 'search', - 'capability' => 'install_themes', - 'panel' => 'themes', - 'priority' => 5, - ) ) ); - - $this->add_section( new WP_Customize_Themes_Section( $this, 'featured_themes', array( - 'title' => __( 'Featured' ), - 'action' => 'featured', - 'capability' => 'install_themes', - 'panel' => 'themes', - 'priority' => 10, - ) ) ); - - $this->add_section( new WP_Customize_Themes_Section( $this, 'popular_themes', array( - 'title' => __( 'Popular' ), - 'action' => 'popular', - 'capability' => 'install_themes', - 'panel' => 'themes', - 'priority' => 15, - ) ) ); - - $this->add_section( new WP_Customize_Themes_Section( $this, 'latest_themes', array( - 'title' => __( 'Latest' ), - 'action' => 'latest', - 'capability' => 'install_themes', - 'panel' => 'themes', - 'priority' => 20, - ) ) ); - - $this->add_section( new WP_Customize_Themes_Section( $this, 'feature_filter_themes', array( - 'title' => __( 'Feature Filter' ), - 'action' => 'feature_filter', - 'capability' => 'install_themes', - 'panel' => 'themes', - 'priority' => 25, - ) ) ); - - $this->add_section( new WP_Customize_Themes_Section( $this, 'favorites_themes', array( - 'title' => __( 'Favorites' ), - 'action' => 'favorites', - 'capability' => 'install_themes', - 'panel' => 'themes', - 'priority' => 30, - ) ) ); - } - // Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience). $this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array( 'capability' => 'switch_themes', ) ) ); + require_once( ABSPATH . 'wp-admin/includes/theme.php' ); + + // Theme Controls. + + // Add a control for the active/original theme. + if ( ! $this->is_theme_active() ) { + $themes = wp_prepare_themes_for_js( array( wp_get_theme( $this->original_stylesheet ) ) ); + $active_theme = current( $themes ); + $active_theme['isActiveTheme'] = true; + $this->add_control( new WP_Customize_Theme_Control( $this, $active_theme['id'], array( + 'theme' => $active_theme, + 'section' => 'themes', + 'settings' => 'active_theme', + ) ) ); + } + + $themes = wp_prepare_themes_for_js(); + foreach ( $themes as $theme ) { + if ( $theme['active'] || $theme['id'] === $this->original_stylesheet ) { + continue; + } + + $theme_id = 'theme_' . $theme['id']; + $theme['isActiveTheme'] = false; + $this->add_control( new WP_Customize_Theme_Control( $this, $theme_id, array( + 'theme' => $theme, + 'section' => 'themes', + 'settings' => 'active_theme', + ) ) ); + } + /* Site Identity */ $this->add_section( 'title_tagline', array( @@ -3700,150 +3648,6 @@ final class WP_Customize_Manager { $this->add_dynamic_settings( $setting_ids ); } - /** - * Load themes into the theme browsing/installation UI. - * - * @since 4.7.0 - * @access public - */ - public function load_themes_ajax() { - check_ajax_referer( 'switch-themes', 'switch-themes-nonce' ); - - if ( ! current_user_can( 'switch_themes' ) ) { - wp_die( -1 ); - } - - if ( empty( $_POST['theme_action'] ) ) { - wp_send_json_error( 'missing_theme_action' ); - } - - if ( 'search' === $_POST['theme_action'] && ! array_key_exists( 'search', $_POST ) ) { - wp_send_json_error( 'empty_search' ); - } elseif ( 'favorites' === $_POST['theme_action'] && ! array_key_exists( 'user', $_POST ) ) { - wp_send_json_error( 'empty_user' ); - } elseif ( 'feature_filter' === $_POST['theme_action'] && ! array_key_exists( 'tags', $_POST ) ) { - wp_send_json_error( 'no_features' ); - } - - require_once( ABSPATH . 'wp-admin/includes/theme.php' ); - if ( 'installed' === $_POST['theme_action'] ) { - $themes = array( 'themes' => wp_prepare_themes_for_js() ); - foreach ( $themes['themes'] as &$theme ) { - $theme['type'] = 'installed'; - // Set active based on customized theme. - if ( $_POST['customized_theme'] === $theme['id'] ) { - $theme['active'] = true; - } else { - $theme['active'] = false; - } - } - } else { - if ( ! current_user_can( 'install_themes' ) ) { - wp_die( -1 ); - } - - // Arguments for all queries. - $args = array( - 'per_page' => 100, - 'page' => absint( $_POST['page'] ), - 'fields' => array( - 'slug' => true, - 'screenshot' => true, - 'description' => true, - 'requires' => true, - 'rating' => true, - 'downloaded' => true, - 'downloadLink' => true, - 'last_updated' => true, - 'homepage' => true, - 'num_ratings' => true, - 'tags' => true, - ), - ); - - // Specialized handling for each query. - switch ( $_POST['theme_action'] ) { - case 'search': - $args['search'] = wp_unslash( $_POST['search'] ); - break; - case 'favorites': - $args['user'] = wp_unslash( $_POST['user'] ); - case 'featured': - case 'popular': - $args['browse'] = wp_unslash( $_POST['theme_action'] ); - break; - case 'latest': - $args['browse'] = 'new'; - break; - case 'feature_filter': - $args['tag'] = wp_unslash( $_POST['tags'] ); - break; - } - - // Load themes from the .org API. - $themes = themes_api( 'query_themes', $args ); - if ( is_wp_error( $themes ) ) { - wp_send_json_error(); - } - - // This list matches the allowed tags in wp-admin/includes/theme-install.php. - $themes_allowedtags = array('a' => array('href' => array(), 'title' => array(), 'target' => array()), - 'abbr' => array('title' => array()), 'acronym' => array('title' => array()), - 'code' => array(), 'pre' => array(), 'em' => array(), 'strong' => array(), - 'div' => array(), 'p' => array(), 'ul' => array(), 'ol' => array(), 'li' => array(), - 'h1' => array(), 'h2' => array(), 'h3' => array(), 'h4' => array(), 'h5' => array(), 'h6' => array(), - 'img' => array('src' => array(), 'class' => array(), 'alt' => array()) - ); - - // Prepare a list of installed themes to check against before the loop. - $installed_themes = array(); - $wp_themes = wp_get_themes(); - foreach ( $wp_themes as $theme ) { - $installed_themes[] = $theme->get_stylesheet(); - } - $update_php = network_admin_url( 'update.php?action=install-theme' ); - foreach ( $themes->themes as &$theme ) { - $theme->install_url = add_query_arg( array( - 'theme' => $theme->slug, - '_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug ), - ), $update_php ); - - $theme->name = wp_kses( $theme->name, $themes_allowedtags ); - $theme->author = wp_kses( $theme->author, $themes_allowedtags ); - $theme->version = wp_kses( $theme->version, $themes_allowedtags ); - $theme->description = wp_kses( $theme->description, $themes_allowedtags ); - $theme->tags = implode( ', ', $theme->tags ); - $theme->stars = wp_star_rating( array( 'rating' => $theme->rating, 'type' => 'percent', 'number' => $theme->num_ratings, 'echo' => false ) ); - $theme->num_ratings = number_format_i18n( $theme->num_ratings ); - $theme->preview_url = set_url_scheme( $theme->preview_url ); - - // Handle themes that are already installed as installed themes. - if ( in_array( $theme->slug, $installed_themes, true ) ) { - $theme->type = 'installed'; - } else { - $theme->type = $_POST['theme_action']; - } - - // Set active based on customized theme. - if ( $_POST['customized_theme'] === $theme->slug ) { - $theme->active = true; - } else { - $theme->active = false; - } - - // Map available theme properties to installed theme properties. - $theme->id = $theme->slug; - $theme->screenshot = array( $theme->screenshot_url ); - $theme->authorAndUri = $theme->author; - unset( $theme->slug ); - unset( $theme->screenshot_url ); - unset( $theme->author ); - } // End foreach(). - } // End if(). - wp_send_json_success( $themes ); - } - - /** * Callback for validating the header_textcolor value. * diff --git a/src/wp-includes/customize/class-wp-customize-theme-control.php b/src/wp-includes/customize/class-wp-customize-theme-control.php index 1401d9fec0..fdd8f131da 100644 --- a/src/wp-includes/customize/class-wp-customize-theme-control.php +++ b/src/wp-includes/customize/class-wp-customize-theme-control.php @@ -62,22 +62,18 @@ class WP_Customize_Theme_Control extends WP_Customize_Control { * @access public */ public function content_template() { - /* translators: %s: theme name */ - $details_label = sprintf( __( 'Details for theme: %s' ), '{{ data.theme.name }}' ); - /* translators: %s: theme name */ - $customize_label = sprintf( __( 'Customize theme: %s' ), '{{ data.theme.name }}' ); - /* translators: %s: theme name */ - $preview_label = sprintf( __( 'Live preview theme: %s' ), '{{ data.theme.name }}' ); - /* translators: %s: theme name */ - $install_label = sprintf( __( 'Install and preview theme: %s' ), '{{ data.theme.name }}' ); + $current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); + $active_url = esc_url( remove_query_arg( 'customize_theme', $current_url ) ); + $preview_url = esc_url( add_query_arg( 'customize_theme', '__THEME__', $current_url ) ); // Token because esc_url() strips curly braces. + $preview_url = str_replace( '__THEME__', '{{ data.theme.id }}', $preview_url ); ?> - <# if ( data.theme.active ) { #> -
    + <# if ( data.theme.isActiveTheme ) { #> +
    <# } else { #> -
    +
    <# } #> - <# if ( data.theme.screenshot && data.theme.screenshot[0] ) { #> + <# if ( data.theme.screenshot[0] ) { #>
    @@ -85,34 +81,25 @@ class WP_Customize_Theme_Control extends WP_Customize_Control {
    <# } #> - - - - <# if ( 'installed' === data.theme.type && data.theme.hasUpdate ) { #> -

    ' . __( 'Update now' ) . '' ); ?>

    + <# if ( data.theme.isActiveTheme ) { #> + + <# } else { #> + <# } #> - <# if ( data.theme.active ) { #> -

    +
    + + <# if ( data.theme.isActiveTheme ) { #> +

    Current: %s' ), '{{ data.theme.name }}' ); + printf( __( 'Active: %s' ), '{{{ data.theme.name }}}' ); ?>

    -
    - -
    -

    - <# } else if ( 'installed' === data.theme.type ) { #> -

    {{ data.theme.name }}

    -
    -
    -

    <# } else { #> -

    {{ data.theme.name }}

    +

    {{{ data.theme.name }}}

    - +
    <# } #>
    diff --git a/src/wp-includes/customize/class-wp-customize-themes-panel.php b/src/wp-includes/customize/class-wp-customize-themes-panel.php deleted file mode 100644 index 4d83e3078b..0000000000 --- a/src/wp-includes/customize/class-wp-customize-themes-panel.php +++ /dev/null @@ -1,113 +0,0 @@ - -
  • -

    - manager->is_theme_active() ) { - echo '' . __( 'Active theme' ) . ' {{ data.title }}'; - } else { - echo '' . __( 'Previewing theme' ) . ' {{ data.title }}'; - } - ?> - - - - -

    -
      -
    • - -
    • - 0' ); - ?> - - -
    • -
    • - -
      - ' . __( 'Themes' ) . '' ); // Separate strings for consistency with other panels. - ?> - - <# if ( data.description ) { #> - - <# } #> - -
      - - <# if ( data.description ) { #> -
      - {{{ data.description }}} -
      - <# } #> - -
    • -
    • -
    • -
        -
      • -
      -
    • - action; - $exported['text_before'] = $this->text_before; - - return $exported; - } - - /** - * Render a themes section as a JS template. - * - * The template is only rendered by PHP once, so all actions are prepared at once on the server side. - * - * @since 4.7.0 + * @since 4.2.0 * @access protected */ - protected function render_template() { + protected function render() { + $classes = 'accordion-section control-section control-section-' . $this->type; ?> -
    • - <# if ( '' !== data.text_before ) { #> -

      {{ data.text_before }}

      - <# } #> - <# if ( 'search' === data.action ) { #> -
      - - - -
      - <# } else { #> - <# if ( 'favorites' === data.action || 'feature_filter' === data.action ) { - var attr = ' aria-expanded="false"'; +
    • +

      + manager->is_theme_active() ) { + echo '' . __( 'Active theme' ) . ' ' . $this->title; } else { - var attr = ''; - } #> - - <# } #> - - <# if ( 'installed' === data.action ) { #> -

      - -

      - <# } #> - - <# if ( 'favorites' === data.action ) { #> -
      -

      -

      - - - -

      -
      - <# } else if ( 'feature_filter' === data.action ) { #> -
      + echo '' . __( 'Previewing theme' ) . ' ' . $this->title; + } + ?> + + controls ) > 0 ) : ?> + + +

      +
      +

      + + + controls ) + 1 /* Active theme */; ?> +

      +

      $features ) { - echo '
      '; - $feature_name = esc_html( $feature_name ); - echo ''; - echo '
      '; - foreach ( $features as $feature => $feature_name ) { - $feature = esc_attr( $feature ); - echo ' '; - echo '
      '; - } - echo '
      '; - echo '
      '; + if ( $this->manager->is_theme_active() ) { + echo '' . __( 'Active theme' ) . ' ' . $this->title; + } else { + echo '' . __( 'Previewing theme' ) . ' ' . $this->title; } ?> -

      - <# } #> -
      + + + + +
      + controls ) > 4 ) : ?> +

      +
      - -
        +
        -

        -

    • diff --git a/tests/phpunit/tests/customize/manager.php b/tests/phpunit/tests/customize/manager.php index f21622647d..a28d83400b 100644 --- a/tests/phpunit/tests/customize/manager.php +++ b/tests/phpunit/tests/customize/manager.php @@ -1506,7 +1506,7 @@ class Tests_WP_Customize_Manager extends WP_UnitTestCase { $data = json_decode( $json, true ); $this->assertNotEmpty( $data ); - $this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices', 'changeset', 'timeouts', 'l10n' ), array_keys( $data ) ); + $this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices', 'changeset', 'timeouts' ), array_keys( $data ) ); $this->assertEquals( $autofocus, $data['autofocus'] ); $this->assertArrayHasKey( 'save', $data['nonce'] ); $this->assertArrayHasKey( 'preview', $data['nonce'] );