Make WordPress Core

Changeset 31533


Ignore:
Timestamp:
02/24/2015 08:30:22 PM (10 years ago)
Author:
markjaquith
Message:

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

Location:
trunk/src
Files:
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/css/customize-controls.css

    r31505 r31533  
    826826.customize-control-header .new {
    827827    float: right;
     828}
     829
     830/**
     831 * Themes
     832 */
     833@-webkit-keyframes customize-reload {
     834    0%   { opacity: 0; }
     835    100% { opacity: 1; }
     836}
     837
     838@-moz-keyframes customize-reload {
     839    0%   { opacity: 0; }
     840    100% { opacity: 1; }
     841}
     842
     843@keyframes customize-reload {
     844    0%   { opacity: 0; }
     845    100% { opacity: 1; }
     846}
     847
     848/* #customize-container is reused from customize-loader.js, hence the naming. */
     849.wp-customizer .customize-loading #customize-container {
     850    display: block;
     851    -webkit-animation: customize-reload .75s; /* Can't use `transition` because `display` changes here. */
     852    -moz-animation: customize-reload .75s;
     853    animation: customize-reload .75s;
     854}
     855
     856.customize-themes-panel {
     857    display: none;
     858    padding: 0 8px;
     859    background: #f1f1f1;
     860    box-sizing: border-box;
     861    -webkit-box-sizing: border-box;
     862    -moz-box-sizing: border-box;
     863}
     864
     865.control-section.open .customize-themes-panel {
     866    display: block;
     867}
     868
     869#customize-theme-controls .customize-themes-panel .accordion-section-content {
     870    background: transparent;
     871    display: block;
     872}
     873
     874.customize-control.customize-control-theme {
     875    margin-bottom: 8px;
     876}
     877
     878.wp-customizer .theme-browser .themes {
     879    padding-bottom: 8px;
     880}
     881
     882.wp-customizer .theme-browser .theme {
     883    margin: 0;
     884    width: 100%;
     885}
     886
     887.wp-customizer .theme-browser .theme .theme-actions {
     888    -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
     889    opacity: 1;
     890}
     891
     892#customize-controls h3.theme-name {
     893    font-size: 15px;
     894}
     895
     896.wp-customizer .theme-browser .theme.active .theme-name {
     897    padding-right: 15px;
     898}
     899
     900.wp-customizer #themes-filter {
     901    width: 100%;
     902}
     903
     904/* Panel-like behavior */
     905#accordion-section-themes .accordion-section-title:after {
     906    content: "\f148";
     907}
     908
     909.rtl #accordion-section-themes .accordion-section-title:after {
     910    -webkit-transform: rotate(180deg);
     911    -ms-transform: rotate(180deg);
     912    transform: rotate(180deg);
     913}
     914
     915#customize-theme-controls .control-section.current-panel > h3.accordion-section-title {
     916    left: 0;
     917}
     918
     919.customize-themes-panel.control-panel-content {
     920    position: absolute;
     921    left: -100%;
     922    top: 0;
     923    width: 100%;
     924    border-top: 1px solid #ddd;
     925}
     926
     927.in-themes-panel #customize-info,
     928.in-themes-panel #customize-theme-controls > ul > .accordion-section {
     929    left: 100%;
     930}
     931
     932.themes-panel-back:before {
     933    top: 13px;
     934    left: 14px;
     935}
     936
     937.in-themes-panel .themes-panel-back {
     938    left: 0;
     939}
     940
     941.in-sub-panel .themes-panel-back {
     942    display: none;
     943}
     944
     945.control-panel-back.themes-panel-back:before {
     946    content: "\f345";
     947}
     948
     949.rtl .control-panel-back.themes-panel-back:before {
     950    content: "\f341";
     951}
     952
     953/* Details View */
     954.wp-customizer .theme-overlay {
     955    display: none;
     956}
     957
     958.wp-customizer.modal-open .theme-overlay {
     959    position: fixed;
     960    left: 0;
     961    top: 0;
     962    right: 0;
     963    bottom: 0;
     964    z-index: 109;
     965}
     966
     967.wp-customizer .theme-overlay .theme-backdrop {
     968    background: rgba( 238, 238, 238, 0.75 );
     969    position: fixed;
     970    z-index: 110;
     971}
     972
     973.wp-customizer .theme-overlay .theme-wrap {
     974    left: 90px;
     975    right: 90px;
     976    top: 45px;
     977    bottom: 45px;
     978    z-index: 120;
     979    max-width: 1740px; /* To ensure that theme screenshots are not displayed larger than 880px wide. */
     980}
     981
     982.wp-customizer .theme-overlay .theme-actions {
     983    text-align: right; /* Because there's only one action, match the pattern of media modals and right-align the action. */
     984}
     985
     986.modal-open .in-themes-panel #customize-controls .wp-full-overlay-sidebar-content {
     987    overflow: visible; /* Prevent the top-level Customizer controls from becoming visible when elements on the right of the details modal are focused. */
     988}
     989
     990/* Small Screens */
     991@media (max-width:850px), (max-height:472px) {
     992    .wp-customizer .theme-overlay .theme-wrap {
     993        left: 0;
     994        right: 0;
     995        top: 0;
     996        bottom: 0;
     997    }   
    828998}
    829999
  • trunk/src/wp-admin/customize.php

    r31484 r31533  
    137137        </div>
    138138
    139         <?php
    140             $screenshot = $wp_customize->theme()->get_screenshot();
    141             $cannot_expand = ! ( $wp_customize->is_theme_active() || $screenshot || $wp_customize->theme()->get('Description') );
    142         ?>
    143 
    144139        <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 -->
    145140        <div class="wp-full-overlay-sidebar-content" tabindex="-1">
    146             <div id="customize-info" class="accordion-section <?php if ( $cannot_expand ) echo ' cannot-expand'; ?>">
     141            <div id="customize-info" class="accordion-section">
    147142                <div class="accordion-section-title" aria-label="<?php esc_attr_e( 'Customizer Options' ); ?>" tabindex="0">
    148143                    <span class="preview-notice"><?php
    149                         if ( ! $wp_customize->is_theme_active() ) {
    150                             /* translators: %s is the theme name in the Customize/Live Preview pane */
    151                             echo sprintf( __( 'You are previewing %s' ), '<strong class="theme-name">' . $wp_customize->theme()->display('Name') . '</strong>' );
    152                         } else {
    153                             /* translators: %s is the site/panel title in the Customize pane */
    154                             echo sprintf( __( 'You are customizing %s' ), '<strong class="theme-name site-title">' . get_bloginfo( 'name' ) . '</strong>' );
    155                         }
     144                        echo sprintf( __( 'You are customizing %s' ), '<strong class="theme-name site-title">' . get_bloginfo( 'name' ) . '</strong>' );
    156145                    ?></span>
    157146                </div>
    158                 <?php if ( ! $cannot_expand ) : ?>
    159                 <div class="accordion-section-content">
    160                     <?php if ( ! $wp_customize->is_theme_active() ) :
    161                         if ( $screenshot ) : ?>
    162                             <img class="theme-screenshot" src="<?php echo esc_url( $screenshot ); ?>" />
    163                         <?php endif; ?>
    164 
    165                         <?php if ( $wp_customize->theme()->get('Description') ): ?>
    166                             <div class="theme-description"><?php echo $wp_customize->theme()->display('Description'); ?></div>
    167                         <?php endif;
    168                     else:
    169                         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.' );
    170                     endif; ?>
    171                 </div>
    172                 <?php endif; ?>
     147                <div class="accordion-section-content"><?php
     148                    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.' );
     149                ?></div>
    173150            </div>
    174151
     
    247224            'preview'       => esc_url_raw( $url ? $url : home_url( '/' ) ),
    248225            'parent'        => esc_url_raw( admin_url() ),
    249             'activated'     => esc_url_raw( admin_url( 'themes.php?activated=true&previewed' ) ),
     226            'activated'     => esc_url_raw( home_url( '/' ) ),
    250227            'ajax'          => esc_url_raw( admin_url( 'admin-ajax.php', 'relative' ) ),
    251228            'allowed'       => array_map( 'esc_url_raw', $allowed_urls ),
  • trunk/src/wp-admin/includes/theme.php

    r31363 r31533  
    487487    return array_values( $prepared_themes );
    488488}
     489
     490/**
     491 * Print JS templates for the theme-browsing UI in the Customizer.
     492 *
     493 * @since 4.2.0
     494 */
     495function customize_themes_print_templates() {
     496    ?>
     497    <script type="text/html" id="tmpl-customize-themes-details-view">
     498        <div class="theme-backdrop"></div>
     499        <div class="theme-wrap">
     500            <div class="theme-header">
     501                <button type="button" class="left dashicons dashicons-no"><span class="screen-reader-text"><?php _e( 'Show previous theme' ); ?></span></button>
     502                <button type="button" class="right dashicons dashicons-no"><span class="screen-reader-text"><?php _e( 'Show next theme' ); ?></span></button>
     503                <button type="button" class="close dashicons dashicons-no"><span class="screen-reader-text"><?php _e( 'Close details dialog' ); ?></span></button>
     504            </div>
     505            <div class="theme-about">
     506                <div class="theme-screenshots">
     507                <# if ( data.screenshot[0] ) { #>
     508                    <div class="screenshot"><img src="{{ data.screenshot[0] }}" alt="" /></div>
     509                <# } else { #>
     510                    <div class="screenshot blank"></div>
     511                <# } #>
     512                </div>
     513
     514                <div class="theme-info">
     515                    <# if ( data.active ) { #>
     516                        <span class="current-label"><?php _e( 'Current Theme' ); ?></span>
     517                    <# } #>
     518                    <h3 class="theme-name">{{{ data.name }}}<span class="theme-version"><?php printf( __( 'Version: %s' ), '{{ data.version }}' ); ?></span></h3>
     519                    <h4 class="theme-author"><?php printf( __( 'By %s' ), '{{{ data.authorAndUri }}}' ); ?></h4>
     520                    <p class="theme-description">{{{ data.description }}}</p>
     521
     522                    <# if ( data.parent ) { #>
     523                        <p class="parent-theme"><?php printf( __( 'This is a child theme of %s.' ), '<strong>{{{ data.parent }}}</strong>' ); ?></p>
     524                    <# } #>
     525
     526                    <# if ( data.tags ) { #>
     527                        <p class="theme-tags"><span><?php _e( 'Tags:' ); ?></span> {{ data.tags }}</p>
     528                    <# } #>
     529                </div>
     530            </div>
     531
     532            <div class="theme-actions">
     533                <# if ( ! data.active ) { #>
     534                    <div class="inactive-theme">
     535                        <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>
     536                    </div>
     537                <# } #>
     538            </div>
     539        </div>
     540    </script>
     541    <?php
     542}
     543add_action( 'customize_controls_print_footer_scripts', 'customize_themes_print_templates' );
  • trunk/src/wp-admin/js/customize-controls.js

    r31384 r31533  
    518518                content.slideUp( args.duration, args.completeCallback );
    519519            }
     520        }
     521    });
     522
     523    /**
     524     * wp.customize.ThemesSection
     525     *
     526     * Custom section for themes that functions similarly to a backwards panel,
     527     * and also handles the theme-details view rendering and navigation.
     528     *
     529     * @constructor
     530     * @augments wp.customize.Section
     531     * @augments wp.customize.Container
     532     */
     533    api.ThemesSection = api.Section.extend({
     534        currentTheme: '',
     535        overlay: '',
     536        template: '',
     537
     538        /**
     539         * @since 4.2.0
     540         */
     541        ready: function () {
     542            var section = this;
     543            section.overlay = section.container.find( '.theme-overlay' );
     544            section.template = wp.template( 'customize-themes-details-view' );
     545
     546            // Bind global keyboard events.
     547            $( 'body' ).on( 'keyup', function( event ) {
     548                if ( ! section.overlay.find( '.theme-wrap' ).is( ':visible' ) ) {
     549                    return;
     550                }
     551
     552                // Pressing the right arrow key fires a theme:next event
     553                if ( 39 === event.keyCode ) {
     554                    section.nextTheme();
     555                }
     556
     557                // Pressing the left arrow key fires a theme:previous event
     558                if ( 37 === event.keyCode ) {
     559                    section.previousTheme();
     560                }
     561
     562                // Pressing the escape key fires a theme:collapse event
     563                if ( 27 === event.keyCode ) {
     564                    section.closeDetails();
     565                }
     566            });
     567        },
     568
     569        /**
     570         * @since 4.2.0
     571         */
     572        attachEvents: function () {
     573            var meta, section = this;
     574
     575            // Expand/Collapse section/panel.
     576            section.container.find( '.accordion-section-title' ).on( 'click keydown', function( event ) {
     577                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
     578                    return;
     579                }
     580                event.preventDefault(); // Keep this AFTER the key filter above
     581
     582                if ( section.expanded() ) {
     583                    section.collapse();
     584                } else {
     585                    section.expand();
     586                }
     587            });
     588
     589            section.container.find( '.themes-panel-back' ).on( 'click keydown', function( event ) {
     590                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
     591                    return;
     592                }
     593
     594                event.preventDefault(); // Keep this AFTER the key filter above
     595
     596                section.collapse();
     597            });
     598
     599            // Theme navigation in details view.
     600            section.container.on( 'click keydown', '.left', function( event ) {
     601                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
     602                    return;
     603                }
     604
     605                event.preventDefault(); // Keep this AFTER the key filter above
     606
     607                section.previousTheme();
     608            });
     609
     610            section.container.on( 'click keydown', '.right', function( event ) {
     611                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
     612                    return;
     613                }
     614
     615                event.preventDefault(); // Keep this AFTER the key filter above
     616
     617                section.nextTheme();
     618            });
     619
     620            section.container.on( 'click keydown', '.theme-backdrop, .close', function( event ) {
     621                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
     622                    return;
     623                }
     624
     625                event.preventDefault(); // Keep this AFTER the key filter above
     626
     627                section.closeDetails();
     628            });
     629
     630            section.container.on( 'click keydown', '.theme-actions .button', function( event ) {
     631                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
     632                    return;
     633                }
     634
     635                $( '.wp-full-overlay' ).addClass( 'customize-loading' );
     636            });
     637
     638            section.container.on( 'input', '#themes-filter', function( event ) {
     639                var term = event.currentTarget.value.toLowerCase().trim().replace( '-', ' ' ),
     640                    controls = section.controls();
     641                controls.pop(); // Remove the last control (the add-new control).
     642                _.each( controls, function( control ) {
     643                    control.filter( term );
     644                });
     645                // Update theme count. Note that the add-theme tile is a div.customize-control.
     646                count = section.container.find( 'li.customize-control:visible' ).length;
     647                section.container.find( '.theme-count' ).text( count );
     648            });
     649        },
     650
     651        /**
     652         * Update UI to reflect expanded state
     653         *
     654         * @since 4.2.0
     655         *
     656         * @param {Boolean}  expanded
     657         * @param {Object}   args
     658         * @param {Boolean}  args.unchanged
     659         * @param {Callback} args.completeCallback
     660         */
     661        onChangeExpanded: function ( expanded, args ) {
     662
     663            // Immediately call the complete callback if there were no changes
     664            if ( args.unchanged ) {
     665                if ( args.completeCallback ) {
     666                    args.completeCallback();
     667                }
     668                return;
     669            }
     670
     671            // Note: there is a second argument 'args' passed
     672            var position, scroll,
     673                panel = this,
     674                section = panel.container.closest( '.accordion-section' ),
     675                overlay = section.closest( '.wp-full-overlay' ),
     676                container = section.closest( '.accordion-container' ),
     677                siblings = container.find( '.open' ),
     678                topPanel = overlay.find( '#customize-theme-controls > ul > .accordion-section > .accordion-section-title' ).add( '#customize-info > .accordion-section-title' ),
     679                backBtn = overlay.find( '.themes-panel-back' ),
     680                panelTitle = section.find( '.accordion-section-title' ).first(),
     681                content = section.find( '.control-panel-content' );
     682
     683            if ( expanded ) {
     684
     685                // Collapse any sibling sections/panels
     686                api.section.each( function ( otherSection ) {
     687                    if ( otherSection !== panel ) {
     688                        otherSection.collapse( { duration: args.duration } );
     689                    }
     690                });
     691                api.panel.each( function ( otherPanel ) {
     692                    if ( panel !== otherPanel ) {
     693                        otherPanel.collapse( { duration: 0 } );
     694                    }
     695                });
     696
     697                content.show( 0, function() {
     698                    position = content.offset().top;
     699                    scroll = container.scrollTop();
     700                    content.css( 'margin-top', ( 45 - position - scroll ) );
     701                    section.addClass( 'current-panel' );
     702                    overlay.addClass( 'in-themes-panel' );
     703                    container.scrollTop( 0 );
     704                    if ( args.completeCallback ) {
     705                        args.completeCallback();
     706                    }
     707                } );
     708                topPanel.attr( 'tabindex', '-1' );
     709                backBtn.attr( 'tabindex', '0' );
     710                backBtn.focus();
     711            } else {
     712                siblings.removeClass( 'open' );
     713                section.removeClass( 'current-panel' );
     714                overlay.removeClass( 'in-themes-panel' );
     715                content.delay( 180 ).hide( 0, function() {
     716                    content.css( 'margin-top', 'inherit' ); // Reset
     717                    if ( args.completeCallback ) {
     718                        args.completeCallback();
     719                    }
     720                } );
     721                topPanel.attr( 'tabindex', '0' );
     722                backBtn.attr( 'tabindex', '-1' );
     723                panelTitle.focus();
     724                container.scrollTop( 0 );
     725            }
     726        },
     727
     728        /**
     729         * Advance the modal to the next theme.
     730         *
     731         * @since 4.2.0
     732         */
     733        nextTheme: function () {
     734            var section = this;
     735            if ( section.getNextTheme() ) {
     736                section.showDetails( section.getNextTheme(), function() {
     737                    section.overlay.find( '.right' ).focus();
     738                } );
     739            }
     740        },
     741
     742        /**
     743         * Get the next theme model.
     744         *
     745         * @since 4.2.0
     746         */
     747        getNextTheme: function () {
     748            var control, next;
     749            control = api.control( 'theme_' + this.currentTheme );
     750            next = control.container.next( 'li.customize-control-theme' );
     751            if ( ! next.length ) {
     752                return false;
     753            }
     754            next = next[0].id.replace( 'customize-control-', '' );
     755            control = api.control( next );
     756
     757            return control.params.theme;
     758        },
     759
     760        /**
     761         * Advance the modal to the previous theme.
     762         *
     763         * @since 4.2.0
     764         */
     765        previousTheme: function () {
     766            var section = this;
     767            if ( section.getPreviousTheme() ) {
     768                section.showDetails( section.getPreviousTheme(), function() {
     769                    section.overlay.find( '.left' ).focus();
     770                } );
     771            }
     772        },
     773
     774        /**
     775         * Get the previous theme model.
     776         *
     777         * @since 4.2.0
     778         */
     779        getPreviousTheme: function () {
     780            var control, previous;
     781            control = api.control( 'theme_' + this.currentTheme );
     782            previous = control.container.prev( 'li.customize-control-theme' );
     783            if ( ! previous.length ) {
     784                return false;
     785            }
     786            previous = previous[0].id.replace( 'customize-control-', '' );
     787            control = api.control( previous );
     788
     789            return control.params.theme;
     790        },
     791
     792        /**
     793         * Disable buttons when we're viewing the first or last theme.
     794         *
     795         * @since 4.2.0
     796         */
     797        updateLimits: function () {
     798            if ( ! this.getNextTheme() ) {
     799                this.overlay.find( '.right' ).addClass( 'disabled' );
     800            }
     801            if ( ! this.getPreviousTheme() ) {
     802                this.overlay.find( '.left' ).addClass( 'disabled' );
     803            }
     804        },
     805
     806        /**
     807         * Render & show the theme details for a given theme model.
     808         *
     809         * @since 4.2.0
     810         *
     811         * @param {Object}   theme
     812         */
     813        showDetails: function ( theme, callback ) {
     814            var section = this;
     815            callback = callback || function(){};
     816            section.currentTheme = theme.id;
     817            section.overlay.html( section.template( theme ) )
     818                           .fadeIn( 'fast' )
     819                           .focus();
     820            $( 'body' ).addClass( 'modal-open' );
     821            section.containFocus( section.overlay );
     822            section.updateLimits();
     823            callback();
     824        },
     825
     826        /**
     827         * Close the theme details modal.
     828         *
     829         * @since 4.2.0
     830         */
     831        closeDetails: function ( theme ) {
     832            $( 'body' ).removeClass( 'modal-open' );
     833            this.overlay.fadeOut( 'fast' );
     834            api.control( 'theme_' + this.currentTheme ).focus();
     835        },
     836
     837        /**
     838         * Keep tab focus within the theme details modal.
     839         *
     840         * @since 4.2.0
     841         */
     842        containFocus: function( el ) {
     843            var tabbables;
     844
     845            el.on( 'keydown', function( event ) {
     846
     847                // Return if it's not the tab key
     848                // When navigating with prev/next focus is already handled
     849                if ( 9 !== event.keyCode ) {
     850                    return;
     851                }
     852
     853                // uses jQuery UI to get the tabbable elements
     854                tabbables = $( ':tabbable', el );
     855
     856                // Keep focus within the overlay
     857                if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
     858                    tabbables.first().focus();
     859                    return false;
     860                } else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
     861                    tabbables.last().focus();
     862                    return false;
     863                }
     864            });
    520865        }
    521866    });
     
    14101755    });
    14111756
     1757    /**
     1758     * wp.customize.ThemeControl
     1759     *
     1760     * @constructor
     1761     * @augments wp.customize.Control
     1762     * @augments wp.customize.Class
     1763     */
     1764    api.ThemeControl = api.Control.extend({
     1765
     1766        /**
     1767         * @since 4.2.0
     1768         */
     1769        ready: function() {
     1770            var control = this;
     1771
     1772            // Bind details view trigger.
     1773            control.container.on( 'click keydown', '.theme', function( event ) {
     1774                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
     1775                    return;
     1776                }
     1777
     1778                if ( 'button' === event.target.className ) {
     1779                    return;
     1780                }
     1781
     1782                api.section( control.section() ).showDetails( control.params.theme );
     1783            });
     1784
     1785            control.container.on( 'click keydown', '.theme-actions .button', function( event ) {
     1786                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
     1787                    return;
     1788                }
     1789
     1790                $( '.wp-full-overlay' ).addClass( 'customize-loading' );
     1791            });
     1792        },
     1793
     1794        /**
     1795         * Show or hide the theme based on the presence of the term in the title, description, and author.
     1796         *
     1797         * @since 4.2.0
     1798         */
     1799        filter: function( term ) {
     1800            var control = this,
     1801                haystack = control.params.theme.name + ' '
     1802                           + control.params.theme.description + ' '
     1803                           + control.params.theme.tags + ' '
     1804                           + control.params.theme.author;
     1805            haystack = haystack.toLowerCase().replace( '-', ' ' );
     1806            if ( -1 !== haystack.search( term ) ) {
     1807                control.activate();
     1808            } else {
     1809                control.deactivate();
     1810            }
     1811        }
     1812    });
     1813
    14121814    // Change objects contained within the main customize object to Settings.
    14131815    api.defaultConstructor = api.Setting;
     
    18542256
    18552257    api.controlConstructor = {
    1856         color:  api.ColorControl,
    1857         upload: api.UploadControl,
    1858         image:  api.ImageControl,
    1859         header: api.HeaderControl,
    1860         background: api.BackgroundControl
     2258        color:      api.ColorControl,
     2259        upload:     api.UploadControl,
     2260        image:      api.ImageControl,
     2261        header:     api.HeaderControl,
     2262        background: api.BackgroundControl,
     2263        theme:      api.ThemeControl
    18612264    };
    18622265    api.panelConstructor = {};
    1863     api.sectionConstructor = {};
     2266    api.sectionConstructor = {
     2267        themes: api.ThemesSection
     2268    };
    18642269
    18652270    $( function() {
     
    22742679        $( window ).on( 'beforeunload', function () {
    22752680            if ( ! api.state( 'saved' )() ) {
     2681                var timeout = setTimeout( function() {
     2682                    overlay.removeClass( 'customize-loading' );
     2683                }, 1 );
    22762684                return api.l10n.saveAlert;
    22772685            }
  • trunk/src/wp-includes/admin-bar.php

    r31456 r31533  
    651651    $wp_admin_bar->add_group( array( 'parent' => 'site-name', 'id' => 'appearance' ) );
    652652
    653     if ( current_user_can( 'switch_themes' ) || current_user_can( 'edit_theme_options' ) )
    654         $wp_admin_bar->add_menu( array( 'parent' => 'appearance', 'id' => 'themes', 'title' => __('Themes'), 'href' => admin_url('themes.php') ) );
    655 
    656     if ( ! current_user_can( 'edit_theme_options' ) )
    657         return;
    658 
    659653    $current_url = ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
    660654    $customize_url = add_query_arg( 'url', urlencode( $current_url ), wp_customize_url() );
     655
     656    if ( current_user_can( 'switch_themes' ) ) {
     657        $wp_admin_bar->add_menu( array(
     658            'parent' => 'appearance',
     659            'id'     => 'themes',
     660            'title'  => __( 'Themes' ),
     661            'href'   => admin_url( 'themes.php' ),
     662            'meta'   => array(
     663                'class' => 'hide-if-customize',
     664            ),
     665        ) );
     666
     667        if ( current_user_can( 'customize' ) ) {
     668            $wp_admin_bar->add_menu( array(
     669                'parent' => 'appearance',
     670                'id'     => 'customize-themes',
     671                'title'  => __( 'Themes' ),
     672                'href'   => add_query_arg( urlencode( 'autofocus[section]' ), 'themes', $customize_url ), // urlencode() needed due to #16859
     673                'meta'   => array(
     674                    'class' => 'hide-if-no-customize',
     675                ),
     676            ) );
     677        }
     678    }
     679
    661680    if ( current_user_can( 'customize' ) ) {
    662681        $wp_admin_bar->add_menu( array(
     
    670689        ) );
    671690        add_action( 'wp_before_admin_bar_render', 'wp_customize_support_script' );
     691    }
     692
     693    if ( ! current_user_can( 'edit_theme_options' ) ) {
     694        return;
    672695    }
    673696
  • trunk/src/wp-includes/class-wp-customize-control.php

    r31101 r31533  
    11021102
    11031103/**
     1104 * Customize Theme Control Class
     1105 *
     1106 * @package WordPress
     1107 * @subpackage Customize
     1108 * @since 4.2.0
     1109 */
     1110class WP_Customize_Theme_Control extends WP_Customize_Control {
     1111
     1112    public $type = 'theme';
     1113    public $theme;
     1114
     1115    /**
     1116     * Refresh the parameters passed to the JavaScript via JSON.
     1117     *
     1118     * @since 4.2.0
     1119     * @uses WP_Customize_Control::to_json()
     1120     */
     1121    public function to_json() {
     1122        parent::to_json();
     1123        $this->json['theme'] = $this->theme;
     1124    }
     1125
     1126    /**
     1127     * Don't render the control content from PHP, as it's rendered via JS on load.
     1128     *
     1129     * @since 4.2.0
     1130     */
     1131    public function render_content() {}
     1132
     1133    /**
     1134     * Render a JS template for theme display.
     1135     *
     1136     * @since 4.2.0
     1137     */
     1138    public function content_template() {
     1139    ?>
     1140        <div class="theme<# if ( data.theme.active ) { #> active<# } #>" tabindex="0" aria-describedby="{{ data.theme.id }}-action {{ data.theme.id }}-name">
     1141            <# if ( data.theme.screenshot[0] ) { #>
     1142                <div class="theme-screenshot">
     1143                    <img src="{{ data.theme.screenshot[0] }}" alt="" />
     1144                </div>
     1145            <# } else { #>
     1146                <div class="theme-screenshot blank"></div>
     1147            <# } #>
     1148            <span class="more-details" id="{{ data.theme.id }}-action"><?php _e( 'Theme Details' ); ?></span>
     1149            <div class="theme-author"><?php printf( __( 'By %s' ), '{{ data.theme.author }}' ); ?></div>
     1150
     1151            <# if ( data.theme.active ) { #>
     1152                <h3 class="theme-name" id="{{ data.theme.id }}-name"><span><?php _ex( 'Previewing:', 'theme' ); ?></span> {{ data.theme.name }}</h3>
     1153            <# } else { #>
     1154                <h3 class="theme-name" id="{{ data.theme.id }}-name">{{ data.theme.name }}</h3>
     1155            <# } #>
     1156
     1157            <# if ( ! data.theme.active ) { #>
     1158                <div class="theme-actions">
     1159                    <a class="button" href="<?php echo add_query_arg( 'theme', '{{ data.theme.id }}', remove_query_arg( 'theme' ) ); ?>" target="_top"><?php _e( 'Live Preview' ); ?></a>
     1160                </div>
     1161            <# } #>
     1162        </div>
     1163    <?php
     1164    }
     1165}
     1166
     1167/**
     1168 * Customize New Theme Control Class
     1169 *
     1170 * @package WordPress
     1171 * @subpackage Customize
     1172 * @since 4.2.0
     1173 */
     1174class WP_Customize_New_Theme_Control extends WP_Customize_Control {
     1175
     1176    /**
     1177     * Render the new control.
     1178     *
     1179     * @since 4.2.0
     1180     */
     1181    public function render() {
     1182        if ( is_multisite() || ! current_user_can( 'install_themes') ) {
     1183            return;
     1184        }
     1185        ?>
     1186        <div class="theme add-new-theme">
     1187            <a href="<?php echo admin_url( 'theme-install.php' ); ?>" target="_top">
     1188                <div class="theme-screenshot">
     1189                    <span></span>
     1190                </div>
     1191                <h3 class="theme-name"><?php _e( 'Add New Theme' ); ?></h3>
     1192            </a>
     1193        </div>
     1194        <?php
     1195    }
     1196}
     1197
     1198/**
    11041199 * Widget Area Customize Control Class
    11051200 *
  • trunk/src/wp-includes/class-wp-customize-manager.php

    r31421 r31533  
    11111111        $this->register_control_type( 'WP_Customize_Image_Control' );
    11121112        $this->register_control_type( 'WP_Customize_Background_Image_Control' );
     1113        $this->register_control_type( 'WP_Customize_Theme_Control' );
     1114
     1115        /* Themes */
     1116
     1117        $this->add_section( new WP_Customize_Themes_Section( $this, 'themes', array(
     1118            'title' => sprintf( __( 'Theme: %s' ), $this->theme()->display('Name') ),
     1119            'capability' => 'switch_themes',
     1120            'priority' => 0,
     1121        ) ) );
     1122
     1123        // Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience).
     1124        $this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array(
     1125            'capability' => 'switch_themes',
     1126        ) ) );
     1127
     1128        require_once( ABSPATH . 'wp-admin/includes/theme.php' );
     1129
     1130        // Theme Controls.
     1131        $themes = wp_prepare_themes_for_js();
     1132        foreach ( $themes as $theme ) {
     1133            $theme_id = 'theme_' . $theme['id'];
     1134            $this->add_control( new WP_Customize_Theme_Control( $this, $theme_id, array(
     1135                'theme' => $theme,
     1136                'section' => 'themes',
     1137                'settings' => 'active_theme',
     1138            ) ) );
     1139        }
     1140
     1141        $this->add_control( new WP_Customize_New_Theme_Control( $this, 'add_theme', array(
     1142            'section' => 'themes',
     1143            'settings' => 'active_theme',
     1144        ) ) );
    11131145
    11141146        /* Site Title & Tagline */
  • trunk/src/wp-includes/class-wp-customize-section.php

    r31126 r31533  
    313313
    314314/**
     315 * Customize Themes Section Class.
     316 *
     317 * A UI container for theme controls, which behaves like a backwards Panel.
     318 *
     319 * @package WordPress
     320 * @subpackage Customize
     321 * @since 4.2.0
     322 */
     323class WP_Customize_Themes_Section extends WP_Customize_Section {
     324
     325    public $type = 'themes';
     326
     327    /**
     328     * Render the themes section, which behaves like a panel.
     329     *
     330     * @since 4.2.0
     331     */
     332    protected function render() {
     333        $classes = 'accordion-section control-section control-section-' . $this->type;
     334        ?>
     335        <li id="accordion-section-<?php echo esc_attr( $this->id ); ?>" class="<?php echo esc_attr( $classes ); ?>">
     336            <h3 class="accordion-section-title" tabindex="0">
     337                <?php echo esc_html( $this->title ); ?>
     338                <span class="screen-reader-text"><?php _e( 'Press return or enter to expand' ); ?></span>
     339            </h3>
     340            <span class="control-panel-back themes-panel-back" tabindex="-1"><span class="screen-reader-text"><?php _e( 'Back' ); ?></span></span>
     341            <div class="customize-themes-panel control-panel-content themes-php">
     342                <h2><?php esc_html_e( 'Themes' ); ?>
     343                    <span class="title-count theme-count"><?php echo count( $this->controls ) - 1; ?></span>
     344                <?php if ( ! is_multisite() && current_user_can( 'install_themes' ) ) : ?>
     345                    <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>
     346                <?php endif; ?>
     347                </h2>
     348                <div class="theme-overlay" tabindex="0" role="dialog" aria-label="<?php esc_attr_e( 'Theme details' ); ?>"></div>
     349                <div id="customize-container"></div>
     350                <?php if ( 6 < count( $this->controls ) ) : ?>
     351                    <p><label for="themes-filter">
     352                        <span class="screen-reader-text"><?php _e( 'Search installed themes...' ); ?></span>
     353                        <input type="search" id="themes-filter" placeholder="<?php esc_attr_e( 'Search installed themes...' ); ?>" />
     354                    </label></p>
     355                <?php endif; ?>
     356                <div class="theme-browser rendered">
     357                    <ul class="themes accordion-section-content">
     358                    </ul>
     359                </div>
     360            </div>
     361        </li>
     362<?php }
     363}
     364
     365/**
    315366 * Customizer section representing widget area (sidebar).
    316367 *
  • trunk/src/wp-includes/version.php

    r31351 r31533  
    1212 * @global int $wp_db_version
    1313 */
    14 $wp_db_version = 31351;
     14$wp_db_version = 31532;
    1515
    1616/**
Note: See TracChangeset for help on using the changeset viewer.