Add theme browsing and theme switching to the Customizer

* Brings into core the Customizer Theme Switcher feature plugin
* You can now browse, preview, and activate themes right from the Customizer

fixes #31303.
props celloexpressions, afercia, westonruter, folletto, designsimply
Built from https://develop.svn.wordpress.org/trunk@31533


git-svn-id: http://core.svn.wordpress.org/trunk@31514 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Mark Jaquith 2015-02-24 20:31:24 +00:00
parent 0d9275930b
commit 8b180b9a46
13 changed files with 1028 additions and 46 deletions

View File

@ -827,6 +827,176 @@ p.customize-section-description {
float: left;
}
/**
* Themes
*/
@-webkit-keyframes customize-reload {
0% { opacity: 0; }
100% { opacity: 1; }
}
@-moz-keyframes customize-reload {
0% { opacity: 0; }
100% { opacity: 1; }
}
@keyframes customize-reload {
0% { opacity: 0; }
100% { opacity: 1; }
}
/* #customize-container is reused from customize-loader.js, hence the naming. */
.wp-customizer .customize-loading #customize-container {
display: block;
-webkit-animation: customize-reload .75s; /* Can't use %60transition` because `display` changes here. */
-moz-animation: customize-reload .75s;
animation: customize-reload .75s;
}
.customize-themes-panel {
display: none;
padding: 0 8px;
background: #f1f1f1;
box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
}
.control-section.open .customize-themes-panel {
display: block;
}
#customize-theme-controls .customize-themes-panel .accordion-section-content {
background: transparent;
display: block;
}
.customize-control.customize-control-theme {
margin-bottom: 8px;
}
.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;
}
#customize-controls h3.theme-name {
font-size: 15px;
}
.wp-customizer .theme-browser .theme.active .theme-name {
padding-left: 15px;
}
.wp-customizer #themes-filter {
width: 100%;
}
/* Panel-like behavior */
#accordion-section-themes .accordion-section-title:after {
content: "\f148";
}
.rtl #accordion-section-themes .accordion-section-title:after {
-webkit-transform: rotate(180deg);
-ms-transform: rotate(180deg);
transform: rotate(180deg);
}
#customize-theme-controls .control-section.current-panel > h3.accordion-section-title {
right: 0;
}
.customize-themes-panel.control-panel-content {
position: absolute;
right: -100%;
top: 0;
width: 100%;
border-top: 1px solid #ddd;
}
.in-themes-panel #customize-info,
.in-themes-panel #customize-theme-controls > ul > .accordion-section {
right: 100%;
}
.themes-panel-back:before {
top: 13px;
right: 14px;
}
.in-themes-panel .themes-panel-back {
right: 0;
}
.in-sub-panel .themes-panel-back {
display: none;
}
.control-panel-back.themes-panel-back:before {
content: "\f345";
}
.rtl .control-panel-back.themes-panel-back:before {
content: "\f341";
}
/* Details View */
.wp-customizer .theme-overlay {
display: none;
}
.wp-customizer.modal-open .theme-overlay {
position: fixed;
right: 0;
top: 0;
left: 0;
bottom: 0;
z-index: 109;
}
.wp-customizer .theme-overlay .theme-backdrop {
background: rgba( 238, 238, 238, 0.75 );
position: fixed;
z-index: 110;
}
.wp-customizer .theme-overlay .theme-wrap {
right: 90px;
left: 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: left; /* Because there's only one action, match the pattern of media modals and right-align the action. */
}
.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 {
right: 0;
left: 0;
top: 0;
bottom: 0;
}
}
/** Handle cheaters. */
body.cheatin {

File diff suppressed because one or more lines are too long

View File

@ -827,6 +827,176 @@ p.customize-section-description {
float: right;
}
/**
* Themes
*/
@-webkit-keyframes customize-reload {
0% { opacity: 0; }
100% { opacity: 1; }
}
@-moz-keyframes customize-reload {
0% { opacity: 0; }
100% { opacity: 1; }
}
@keyframes customize-reload {
0% { opacity: 0; }
100% { opacity: 1; }
}
/* #customize-container is reused from customize-loader.js, hence the naming. */
.wp-customizer .customize-loading #customize-container {
display: block;
-webkit-animation: customize-reload .75s; /* Can't use `transition` because `display` changes here. */
-moz-animation: customize-reload .75s;
animation: customize-reload .75s;
}
.customize-themes-panel {
display: none;
padding: 0 8px;
background: #f1f1f1;
box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
}
.control-section.open .customize-themes-panel {
display: block;
}
#customize-theme-controls .customize-themes-panel .accordion-section-content {
background: transparent;
display: block;
}
.customize-control.customize-control-theme {
margin-bottom: 8px;
}
.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;
}
#customize-controls h3.theme-name {
font-size: 15px;
}
.wp-customizer .theme-browser .theme.active .theme-name {
padding-right: 15px;
}
.wp-customizer #themes-filter {
width: 100%;
}
/* Panel-like behavior */
#accordion-section-themes .accordion-section-title:after {
content: "\f148";
}
.rtl #accordion-section-themes .accordion-section-title:after {
-webkit-transform: rotate(180deg);
-ms-transform: rotate(180deg);
transform: rotate(180deg);
}
#customize-theme-controls .control-section.current-panel > h3.accordion-section-title {
left: 0;
}
.customize-themes-panel.control-panel-content {
position: absolute;
left: -100%;
top: 0;
width: 100%;
border-top: 1px solid #ddd;
}
.in-themes-panel #customize-info,
.in-themes-panel #customize-theme-controls > ul > .accordion-section {
left: 100%;
}
.themes-panel-back:before {
top: 13px;
left: 14px;
}
.in-themes-panel .themes-panel-back {
left: 0;
}
.in-sub-panel .themes-panel-back {
display: none;
}
.control-panel-back.themes-panel-back:before {
content: "\f345";
}
.rtl .control-panel-back.themes-panel-back:before {
content: "\f341";
}
/* Details View */
.wp-customizer .theme-overlay {
display: none;
}
.wp-customizer.modal-open .theme-overlay {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 109;
}
.wp-customizer .theme-overlay .theme-backdrop {
background: rgba( 238, 238, 238, 0.75 );
position: fixed;
z-index: 110;
}
.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's only one action, match the pattern of media modals and right-align the action. */
}
.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 {
left: 0;
right: 0;
top: 0;
bottom: 0;
}
}
/** Handle cheaters. */
body.cheatin {

File diff suppressed because one or more lines are too long

View File

@ -136,40 +136,17 @@ do_action( 'customize_controls_print_scripts' );
<span class="control-panel-back" tabindex="-1"><span class="screen-reader-text"><?php _e( 'Back' ); ?></span></span>
</div>
<?php
$screenshot = $wp_customize->theme()->get_screenshot();
$cannot_expand = ! ( $wp_customize->is_theme_active() || $screenshot || $wp_customize->theme()->get('Description') );
?>
<div id="widgets-right"><!-- For Widget Customizer, many widgets try to look for instances under div#widgets-right, so we have to add that ID to a container div in the Customizer for compat -->
<div class="wp-full-overlay-sidebar-content" tabindex="-1">
<div id="customize-info" class="accordion-section <?php if ( $cannot_expand ) echo ' cannot-expand'; ?>">
<div id="customize-info" class="accordion-section">
<div class="accordion-section-title" aria-label="<?php esc_attr_e( 'Customizer Options' ); ?>" tabindex="0">
<span class="preview-notice"><?php
if ( ! $wp_customize->is_theme_active() ) {
/* translators: %s is the theme name in the Customize/Live Preview pane */
echo sprintf( __( 'You are previewing %s' ), '<strong class="theme-name">' . $wp_customize->theme()->display('Name') . '</strong>' );
} else {
/* translators: %s is the site/panel title in the Customize pane */
echo sprintf( __( 'You are customizing %s' ), '<strong class="theme-name site-title">' . get_bloginfo( 'name' ) . '</strong>' );
}
echo sprintf( __( 'You are customizing %s' ), '<strong class="theme-name site-title">' . get_bloginfo( 'name' ) . '</strong>' );
?></span>
</div>
<?php if ( ! $cannot_expand ) : ?>
<div class="accordion-section-content">
<?php if ( ! $wp_customize->is_theme_active() ) :
if ( $screenshot ) : ?>
<img class="theme-screenshot" src="<?php echo esc_url( $screenshot ); ?>" />
<?php endif; ?>
<?php if ( $wp_customize->theme()->get('Description') ): ?>
<div class="theme-description"><?php echo $wp_customize->theme()->display('Description'); ?></div>
<?php endif;
else:
echo __( 'The Customizer allows you to preview changes to your site before publishing them. You can also navigate to different pages on your site to preview them.' );
endif; ?>
</div>
<?php endif; ?>
<div class="accordion-section-content"><?php
echo __( 'The Customizer allows you to preview changes to your site before publishing them. You can also navigate to different pages on your site to preview them.' );
?></div>
</div>
<div id="customize-theme-controls">
@ -246,7 +223,7 @@ do_action( 'customize_controls_print_scripts' );
'url' => array(
'preview' => esc_url_raw( $url ? $url : home_url( '/' ) ),
'parent' => esc_url_raw( admin_url() ),
'activated' => esc_url_raw( admin_url( 'themes.php?activated=true&previewed' ) ),
'activated' => esc_url_raw( home_url( '/' ) ),
'ajax' => esc_url_raw( admin_url( 'admin-ajax.php', 'relative' ) ),
'allowed' => array_map( 'esc_url_raw', $allowed_urls ),
'isCrossDomain' => $cross_domain,

View File

@ -486,3 +486,58 @@ function wp_prepare_themes_for_js( $themes = null ) {
$prepared_themes = apply_filters( 'wp_prepare_themes_for_js', $prepared_themes );
return array_values( $prepared_themes );
}
/**
* Print JS templates for the theme-browsing UI in the Customizer.
*
* @since 4.2.0
*/
function customize_themes_print_templates() {
?>
<script type="text/html" id="tmpl-customize-themes-details-view">
<div class="theme-backdrop"></div>
<div class="theme-wrap">
<div class="theme-header">
<button type="button" class="left dashicons dashicons-no"><span class="screen-reader-text"><?php _e( 'Show previous theme' ); ?></span></button>
<button type="button" class="right dashicons dashicons-no"><span class="screen-reader-text"><?php _e( 'Show next theme' ); ?></span></button>
<button type="button" class="close dashicons dashicons-no"><span class="screen-reader-text"><?php _e( 'Close details dialog' ); ?></span></button>
</div>
<div class="theme-about">
<div class="theme-screenshots">
<# if ( data.screenshot[0] ) { #>
<div class="screenshot"><img src="{{ data.screenshot[0] }}" alt="" /></div>
<# } else { #>
<div class="screenshot blank"></div>
<# } #>
</div>
<div class="theme-info">
<# if ( data.active ) { #>
<span class="current-label"><?php _e( 'Current Theme' ); ?></span>
<# } #>
<h3 class="theme-name">{{{ data.name }}}<span class="theme-version"><?php printf( __( 'Version: %s' ), '{{ data.version }}' ); ?></span></h3>
<h4 class="theme-author"><?php printf( __( 'By %s' ), '{{{ data.authorAndUri }}}' ); ?></h4>
<p class="theme-description">{{{ data.description }}}</p>
<# if ( data.parent ) { #>
<p class="parent-theme"><?php printf( __( 'This is a child theme of %s.' ), '<strong>{{{ data.parent }}}</strong>' ); ?></p>
<# } #>
<# if ( data.tags ) { #>
<p class="theme-tags"><span><?php _e( 'Tags:' ); ?></span> {{ data.tags }}</p>
<# } #>
</div>
</div>
<div class="theme-actions">
<# if ( ! data.active ) { #>
<div class="inactive-theme">
<a href="<?php echo add_query_arg( 'theme', '{{ data.id }}', remove_query_arg( 'theme' ) ); ?>" target="_top" class="button button-primary"><?php _e( 'Live Preview' ); ?></a>
</div>
<# } #>
</div>
</div>
</script>
<?php
}
add_action( 'customize_controls_print_footer_scripts', 'customize_themes_print_templates' );

View File

@ -520,6 +520,351 @@
}
});
/**
* wp.customize.ThemesSection
*
* Custom section for themes that functions similarly to a backwards panel,
* and also handles the theme-details view rendering and navigation.
*
* @constructor
* @augments wp.customize.Section
* @augments wp.customize.Container
*/
api.ThemesSection = api.Section.extend({
currentTheme: '',
overlay: '',
template: '',
/**
* @since 4.2.0
*/
ready: function () {
var section = this;
section.overlay = section.container.find( '.theme-overlay' );
section.template = wp.template( 'customize-themes-details-view' );
// Bind global keyboard events.
$( 'body' ).on( 'keyup', function( event ) {
if ( ! section.overlay.find( '.theme-wrap' ).is( ':visible' ) ) {
return;
}
// Pressing the right arrow key fires a theme:next event
if ( 39 === event.keyCode ) {
section.nextTheme();
}
// Pressing the left arrow key fires a theme:previous event
if ( 37 === event.keyCode ) {
section.previousTheme();
}
// Pressing the escape key fires a theme:collapse event
if ( 27 === event.keyCode ) {
section.closeDetails();
}
});
},
/**
* @since 4.2.0
*/
attachEvents: function () {
var meta, section = this;
// Expand/Collapse section/panel.
section.container.find( '.accordion-section-title' ).on( 'click keydown', function( event ) {
if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
return;
}
event.preventDefault(); // Keep this AFTER the key filter above
if ( section.expanded() ) {
section.collapse();
} else {
section.expand();
}
});
section.container.find( '.themes-panel-back' ).on( 'click keydown', function( event ) {
if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
return;
}
event.preventDefault(); // Keep this AFTER the key filter above
section.collapse();
});
// Theme navigation in details view.
section.container.on( 'click keydown', '.left', function( event ) {
if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
return;
}
event.preventDefault(); // Keep this AFTER the key filter above
section.previousTheme();
});
section.container.on( 'click keydown', '.right', function( event ) {
if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
return;
}
event.preventDefault(); // Keep this AFTER the key filter above
section.nextTheme();
});
section.container.on( 'click keydown', '.theme-backdrop, .close', function( event ) {
if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
return;
}
event.preventDefault(); // Keep this AFTER the key filter above
section.closeDetails();
});
section.container.on( 'click keydown', '.theme-actions .button', function( event ) {
if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
return;
}
$( '.wp-full-overlay' ).addClass( 'customize-loading' );
});
section.container.on( 'input', '#themes-filter', function( event ) {
var term = event.currentTarget.value.toLowerCase().trim().replace( '-', ' ' ),
controls = section.controls();
controls.pop(); // Remove the last control (the add-new control).
_.each( controls, function( control ) {
control.filter( term );
});
// Update theme count. Note that the add-theme tile is a div.customize-control.
count = section.container.find( 'li.customize-control:visible' ).length;
section.container.find( '.theme-count' ).text( count );
});
},
/**
* Update UI to reflect expanded state
*
* @since 4.2.0
*
* @param {Boolean} expanded
* @param {Object} args
* @param {Boolean} args.unchanged
* @param {Callback} args.completeCallback
*/
onChangeExpanded: function ( 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 position, scroll,
panel = this,
section = panel.container.closest( '.accordion-section' ),
overlay = section.closest( '.wp-full-overlay' ),
container = section.closest( '.accordion-container' ),
siblings = container.find( '.open' ),
topPanel = overlay.find( '#customize-theme-controls > ul > .accordion-section > .accordion-section-title' ).add( '#customize-info > .accordion-section-title' ),
backBtn = overlay.find( '.themes-panel-back' ),
panelTitle = section.find( '.accordion-section-title' ).first(),
content = section.find( '.control-panel-content' );
if ( expanded ) {
// Collapse any sibling sections/panels
api.section.each( function ( otherSection ) {
if ( otherSection !== panel ) {
otherSection.collapse( { duration: args.duration } );
}
});
api.panel.each( function ( otherPanel ) {
if ( panel !== otherPanel ) {
otherPanel.collapse( { duration: 0 } );
}
});
content.show( 0, function() {
position = content.offset().top;
scroll = container.scrollTop();
content.css( 'margin-top', ( 45 - position - scroll ) );
section.addClass( 'current-panel' );
overlay.addClass( 'in-themes-panel' );
container.scrollTop( 0 );
if ( args.completeCallback ) {
args.completeCallback();
}
} );
topPanel.attr( 'tabindex', '-1' );
backBtn.attr( 'tabindex', '0' );
backBtn.focus();
} else {
siblings.removeClass( 'open' );
section.removeClass( 'current-panel' );
overlay.removeClass( 'in-themes-panel' );
content.delay( 180 ).hide( 0, function() {
content.css( 'margin-top', 'inherit' ); // Reset
if ( args.completeCallback ) {
args.completeCallback();
}
} );
topPanel.attr( 'tabindex', '0' );
backBtn.attr( 'tabindex', '-1' );
panelTitle.focus();
container.scrollTop( 0 );
}
},
/**
* Advance the modal to the next theme.
*
* @since 4.2.0
*/
nextTheme: function () {
var section = this;
if ( section.getNextTheme() ) {
section.showDetails( section.getNextTheme(), function() {
section.overlay.find( '.right' ).focus();
} );
}
},
/**
* Get the next theme model.
*
* @since 4.2.0
*/
getNextTheme: function () {
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-', '' );
control = api.control( next );
return control.params.theme;
},
/**
* Advance the modal to the previous theme.
*
* @since 4.2.0
*/
previousTheme: function () {
var section = this;
if ( section.getPreviousTheme() ) {
section.showDetails( section.getPreviousTheme(), function() {
section.overlay.find( '.left' ).focus();
} );
}
},
/**
* Get the previous theme model.
*
* @since 4.2.0
*/
getPreviousTheme: function () {
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-', '' );
control = api.control( previous );
return control.params.theme;
},
/**
* Disable buttons when we're viewing the first or last theme.
*
* @since 4.2.0
*/
updateLimits: function () {
if ( ! this.getNextTheme() ) {
this.overlay.find( '.right' ).addClass( 'disabled' );
}
if ( ! this.getPreviousTheme() ) {
this.overlay.find( '.left' ).addClass( 'disabled' );
}
},
/**
* Render & show the theme details for a given theme model.
*
* @since 4.2.0
*
* @param {Object} theme
*/
showDetails: function ( theme, callback ) {
var section = this;
callback = callback || function(){};
section.currentTheme = theme.id;
section.overlay.html( section.template( theme ) )
.fadeIn( 'fast' )
.focus();
$( 'body' ).addClass( 'modal-open' );
section.containFocus( section.overlay );
section.updateLimits();
callback();
},
/**
* Close the theme details modal.
*
* @since 4.2.0
*/
closeDetails: function ( theme ) {
$( 'body' ).removeClass( 'modal-open' );
this.overlay.fadeOut( 'fast' );
api.control( 'theme_' + this.currentTheme ).focus();
},
/**
* Keep tab focus within the theme details modal.
*
* @since 4.2.0
*/
containFocus: function( el ) {
var tabbables;
el.on( 'keydown', function( event ) {
// Return if it's not the tab key
// When navigating with prev/next focus is already handled
if ( 9 !== event.keyCode ) {
return;
}
// uses jQuery UI to get the tabbable elements
tabbables = $( ':tabbable', el );
// Keep focus within the overlay
if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
tabbables.first().focus();
return false;
} else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
tabbables.last().focus();
return false;
}
});
}
});
/**
* @since 4.1.0
*
@ -1409,6 +1754,63 @@
});
/**
* wp.customize.ThemeControl
*
* @constructor
* @augments wp.customize.Control
* @augments wp.customize.Class
*/
api.ThemeControl = api.Control.extend({
/**
* @since 4.2.0
*/
ready: function() {
var control = this;
// Bind details view trigger.
control.container.on( 'click keydown', '.theme', function( event ) {
if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
return;
}
if ( 'button' === event.target.className ) {
return;
}
api.section( control.section() ).showDetails( control.params.theme );
});
control.container.on( 'click keydown', '.theme-actions .button', function( event ) {
if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
return;
}
$( '.wp-full-overlay' ).addClass( 'customize-loading' );
});
},
/**
* Show or hide the theme based on the presence of the term in the title, description, and author.
*
* @since 4.2.0
*/
filter: function( term ) {
var control = this,
haystack = control.params.theme.name + ' '
+ control.params.theme.description + ' '
+ control.params.theme.tags + ' '
+ control.params.theme.author;
haystack = haystack.toLowerCase().replace( '-', ' ' );
if ( -1 !== haystack.search( term ) ) {
control.activate();
} else {
control.deactivate();
}
}
});
// Change objects contained within the main customize object to Settings.
api.defaultConstructor = api.Setting;
@ -1853,14 +2255,17 @@
});
api.controlConstructor = {
color: api.ColorControl,
upload: api.UploadControl,
image: api.ImageControl,
header: api.HeaderControl,
background: api.BackgroundControl
color: api.ColorControl,
upload: api.UploadControl,
image: api.ImageControl,
header: api.HeaderControl,
background: api.BackgroundControl,
theme: api.ThemeControl
};
api.panelConstructor = {};
api.sectionConstructor = {};
api.sectionConstructor = {
themes: api.ThemesSection
};
$( function() {
api.settings = window._wpCustomizeSettings;
@ -2273,6 +2678,9 @@
// Prompt user with AYS dialog if leaving the Customizer with unsaved changes
$( window ).on( 'beforeunload', function () {
if ( ! api.state( 'saved' )() ) {
var timeout = setTimeout( function() {
overlay.removeClass( 'customize-loading' );
}, 1 );
return api.l10n.saveAlert;
}
} );

File diff suppressed because one or more lines are too long

View File

@ -650,14 +650,33 @@ function wp_admin_bar_comments_menu( $wp_admin_bar ) {
function wp_admin_bar_appearance_menu( $wp_admin_bar ) {
$wp_admin_bar->add_group( array( 'parent' => 'site-name', 'id' => 'appearance' ) );
if ( current_user_can( 'switch_themes' ) || current_user_can( 'edit_theme_options' ) )
$wp_admin_bar->add_menu( array( 'parent' => 'appearance', 'id' => 'themes', 'title' => __('Themes'), 'href' => admin_url('themes.php') ) );
if ( ! current_user_can( 'edit_theme_options' ) )
return;
$current_url = ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
$customize_url = add_query_arg( 'url', urlencode( $current_url ), wp_customize_url() );
if ( current_user_can( 'switch_themes' ) ) {
$wp_admin_bar->add_menu( array(
'parent' => 'appearance',
'id' => 'themes',
'title' => __( 'Themes' ),
'href' => admin_url( 'themes.php' ),
'meta' => array(
'class' => 'hide-if-customize',
),
) );
if ( current_user_can( 'customize' ) ) {
$wp_admin_bar->add_menu( array(
'parent' => 'appearance',
'id' => 'customize-themes',
'title' => __( 'Themes' ),
'href' => add_query_arg( urlencode( 'autofocus[section]' ), 'themes', $customize_url ), // urlencode() needed due to #16859
'meta' => array(
'class' => 'hide-if-no-customize',
),
) );
}
}
if ( current_user_can( 'customize' ) ) {
$wp_admin_bar->add_menu( array(
'parent' => 'appearance',
@ -671,6 +690,10 @@ function wp_admin_bar_appearance_menu( $wp_admin_bar ) {
add_action( 'wp_before_admin_bar_render', 'wp_customize_support_script' );
}
if ( ! current_user_can( 'edit_theme_options' ) ) {
return;
}
if ( current_theme_supports( 'widgets' ) ) {
$wp_admin_bar->add_menu( array(
'parent' => 'appearance',

View File

@ -1100,6 +1100,101 @@ class WP_Customize_Header_Image_Control extends WP_Customize_Image_Control {
}
}
/**
* Customize Theme Control Class
*
* @package WordPress
* @subpackage Customize
* @since 4.2.0
*/
class WP_Customize_Theme_Control extends WP_Customize_Control {
public $type = 'theme';
public $theme;
/**
* Refresh the parameters passed to the JavaScript via JSON.
*
* @since 4.2.0
* @uses WP_Customize_Control::to_json()
*/
public function to_json() {
parent::to_json();
$this->json['theme'] = $this->theme;
}
/**
* Don't render the control content from PHP, as it's rendered via JS on load.
*
* @since 4.2.0
*/
public function render_content() {}
/**
* Render a JS template for theme display.
*
* @since 4.2.0
*/
public function content_template() {
?>
<div class="theme<# if ( data.theme.active ) { #> active<# } #>" tabindex="0" aria-describedby="{{ data.theme.id }}-action {{ data.theme.id }}-name">
<# if ( data.theme.screenshot[0] ) { #>
<div class="theme-screenshot">
<img src="{{ data.theme.screenshot[0] }}" alt="" />
</div>
<# } else { #>
<div class="theme-screenshot blank"></div>
<# } #>
<span class="more-details" id="{{ data.theme.id }}-action"><?php _e( 'Theme Details' ); ?></span>
<div class="theme-author"><?php printf( __( 'By %s' ), '{{ data.theme.author }}' ); ?></div>
<# if ( data.theme.active ) { #>
<h3 class="theme-name" id="{{ data.theme.id }}-name"><span><?php _ex( 'Previewing:', 'theme' ); ?></span> {{ data.theme.name }}</h3>
<# } else { #>
<h3 class="theme-name" id="{{ data.theme.id }}-name">{{ data.theme.name }}</h3>
<# } #>
<# if ( ! data.theme.active ) { #>
<div class="theme-actions">
<a class="button" href="<?php echo add_query_arg( 'theme', '{{ data.theme.id }}', remove_query_arg( 'theme' ) ); ?>" target="_top"><?php _e( 'Live Preview' ); ?></a>
</div>
<# } #>
</div>
<?php
}
}
/**
* Customize New Theme Control Class
*
* @package WordPress
* @subpackage Customize
* @since 4.2.0
*/
class WP_Customize_New_Theme_Control extends WP_Customize_Control {
/**
* Render the new control.
*
* @since 4.2.0
*/
public function render() {
if ( is_multisite() || ! current_user_can( 'install_themes') ) {
return;
}
?>
<div class="theme add-new-theme">
<a href="<?php echo admin_url( 'theme-install.php' ); ?>" target="_top">
<div class="theme-screenshot">
<span></span>
</div>
<h3 class="theme-name"><?php _e( 'Add New Theme' ); ?></h3>
</a>
</div>
<?php
}
}
/**
* Widget Area Customize Control Class
*

View File

@ -1110,6 +1110,38 @@ final class WP_Customize_Manager {
$this->register_control_type( 'WP_Customize_Upload_Control' );
$this->register_control_type( 'WP_Customize_Image_Control' );
$this->register_control_type( 'WP_Customize_Background_Image_Control' );
$this->register_control_type( 'WP_Customize_Theme_Control' );
/* Themes */
$this->add_section( new WP_Customize_Themes_Section( $this, 'themes', array(
'title' => sprintf( __( 'Theme: %s' ), $this->theme()->display('Name') ),
'capability' => 'switch_themes',
'priority' => 0,
) ) );
// 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.
$themes = wp_prepare_themes_for_js();
foreach ( $themes as $theme ) {
$theme_id = 'theme_' . $theme['id'];
$this->add_control( new WP_Customize_Theme_Control( $this, $theme_id, array(
'theme' => $theme,
'section' => 'themes',
'settings' => 'active_theme',
) ) );
}
$this->add_control( new WP_Customize_New_Theme_Control( $this, 'add_theme', array(
'section' => 'themes',
'settings' => 'active_theme',
) ) );
/* Site Title & Tagline */

View File

@ -311,6 +311,57 @@ class WP_Customize_Section {
}
}
/**
* Customize Themes Section Class.
*
* A UI container for theme controls, which behaves like a backwards Panel.
*
* @package WordPress
* @subpackage Customize
* @since 4.2.0
*/
class WP_Customize_Themes_Section extends WP_Customize_Section {
public $type = 'themes';
/**
* Render the themes section, which behaves like a panel.
*
* @since 4.2.0
*/
protected function render() {
$classes = 'accordion-section control-section control-section-' . $this->type;
?>
<li id="accordion-section-<?php echo esc_attr( $this->id ); ?>" class="<?php echo esc_attr( $classes ); ?>">
<h3 class="accordion-section-title" tabindex="0">
<?php echo esc_html( $this->title ); ?>
<span class="screen-reader-text"><?php _e( 'Press return or enter to expand' ); ?></span>
</h3>
<span class="control-panel-back themes-panel-back" tabindex="-1"><span class="screen-reader-text"><?php _e( 'Back' ); ?></span></span>
<div class="customize-themes-panel control-panel-content themes-php">
<h2><?php esc_html_e( 'Themes' ); ?>
<span class="title-count theme-count"><?php echo count( $this->controls ) - 1; ?></span>
<?php if ( ! is_multisite() && current_user_can( 'install_themes' ) ) : ?>
<a href="<?php echo admin_url( 'theme-install.php' ); ?>" target="_top" class="add-new-h2"><?php echo esc_html_x( 'Add New', 'Add new theme' ); ?></a>
<?php endif; ?>
</h2>
<div class="theme-overlay" tabindex="0" role="dialog" aria-label="<?php esc_attr_e( 'Theme details' ); ?>"></div>
<div id="customize-container"></div>
<?php if ( 6 < count( $this->controls ) ) : ?>
<p><label for="themes-filter">
<span class="screen-reader-text"><?php _e( 'Search installed themes...' ); ?></span>
<input type="search" id="themes-filter" placeholder="<?php esc_attr_e( 'Search installed themes...' ); ?>" />
</label></p>
<?php endif; ?>
<div class="theme-browser rendered">
<ul class="themes accordion-section-content">
</ul>
</div>
</div>
</li>
<?php }
}
/**
* Customizer section representing widget area (sidebar).
*

View File

@ -4,14 +4,14 @@
*
* @global string $wp_version
*/
$wp_version = '4.2-alpha-31532';
$wp_version = '4.2-alpha-31533';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.
*
* @global int $wp_db_version
*/
$wp_db_version = 31351;
$wp_db_version = 31532;
/**
* Holds the TinyMCE version