Make WordPress Core

Ticket #41897: 41897.0.diff

File 41897.0.diff, 26.1 KB (added by westonruter, 8 years ago)

https://github.com/xwp/wordpress-develop/pull/257

  • src/wp-admin/css/customize-controls.css

    diff --git src/wp-admin/css/customize-controls.css src/wp-admin/css/customize-controls.css
    index fd272d849e..e7d7af9c54 100644
    p.customize-section-description { 
    11301130}
    11311131
    11321132/**
    1133  * Custom CSS Section
     1133 * Code Editor Control and Custom CSS Section
    11341134 *
    11351135 * Modifications to the Section Container to make the textarea full-width and
    11361136 * full-height, if the control is the only control in the section.
    11371137 */
    11381138
     1139.customize-control-code_editor textarea {
     1140        width: 100%;
     1141        font-family: Consolas, Monaco, monospace;
     1142        font-size: 12px;
     1143        padding: 6px 8px;
     1144        -moz-tab-size: 4;
     1145        -o-tab-size: 4;
     1146        tab-size: 4;
     1147}
     1148.customize-control-code_editor textarea,
     1149.customize-control-code_editor .CodeMirror {
     1150        height: 14em;
     1151}
     1152
    11391153#customize-controls .customize-section-description-container.section-meta.customize-info {
    11401154        border-bottom: none;
    11411155}
    p.customize-section-description { 
    11461160
    11471161#customize-control-custom_css textarea {
    11481162        display: block;
    1149         font-family: Consolas, Monaco, monospace;
    1150         font-size: 12px;
    1151         padding: 6px 8px;
    11521163        height: 500px;
    1153         -moz-tab-size: 4;
    1154         -o-tab-size: 4;
    1155         tab-size: 4;
    11561164}
    11571165
    11581166.customize-section-description-container + #customize-control-custom_css:last-child textarea {
  • src/wp-admin/js/customize-controls.js

    diff --git src/wp-admin/js/customize-controls.js src/wp-admin/js/customize-controls.js
    index f72110fce1..5a33976e39 100644
     
    36293629                }
    36303630        });
    36313631
     3632        /**
     3633         * Class wp.customize.CodeEditorControl
     3634         *
     3635         * @constructor
     3636         * @augments wp.customize.Control
     3637         * @augments wp.customize.Class
     3638         */
     3639        api.CodeEditorControl = api.Control.extend({
     3640
     3641                /**
     3642                 * Initialize the editor when the containing section is ready and expanded.
     3643                 *
     3644                 * @since 4.9.0
     3645                 * @returns {void}
     3646                 */
     3647                ready: function() {
     3648                        var control = this;
     3649                        if ( ! control.section() ) {
     3650                                control.initEditor();
     3651                                return;
     3652                        }
     3653
     3654                        // Wait to initialize editor until section is embedded and expanded.
     3655                        api.section( control.section(), function( section ) {
     3656                                section.deferred.embedded.done( function() {
     3657                                        var onceExpanded;
     3658                                        if ( section.expanded() ) {
     3659                                                control.initEditor();
     3660                                        } else {
     3661                                                onceExpanded = function( isExpanded ) {
     3662                                                        if ( isExpanded ) {
     3663                                                                control.initEditor();
     3664                                                                section.expanded.unbind( onceExpanded );
     3665                                                        }
     3666                                                };
     3667                                                section.expanded.bind( onceExpanded );
     3668                                        }
     3669                                } );
     3670                        } );
     3671                },
     3672
     3673                /**
     3674                 * Initialize editor.
     3675                 *
     3676                 * @since 4.9.0
     3677                 * @returns {void}
     3678                 */
     3679                initEditor: function() {
     3680                        var control = this, element;
     3681
     3682                        element = new api.Element( control.container.find( 'textarea' ) );
     3683                        control.elements.push( element );
     3684                        element.sync( control.setting );
     3685                        element.set( control.setting() );
     3686
     3687                        if ( control.params.editor_settings ) {
     3688                                control.initSyntaxHighlightingEditor( control.params.editor_settings );
     3689                        } else {
     3690                                control.initPlainTextareaEditor();
     3691                        }
     3692                },
     3693
     3694                /**
     3695                 * Make sure editor gets focused when control is focused.
     3696                 *
     3697                 * @since 4.9.0
     3698                 * @param {Object}   [params]
     3699                 * @param {Function} [params.completeCallback]
     3700                 * @returns {void}
     3701                 */
     3702                focus: function( params ) {
     3703                        var control = this, extendedParams = _.extend( {}, params ), originalCompleteCallback;
     3704                        originalCompleteCallback = extendedParams.completeCallback;
     3705                        extendedParams.completeCallback = function() {
     3706                                if ( originalCompleteCallback ) {
     3707                                        originalCompleteCallback();
     3708                                }
     3709                                if ( control.editor ) {
     3710                                        control.editor.codemirror.focus();
     3711                                }
     3712                        };
     3713                        api.Control.prototype.focus.call( control, extendedParams );
     3714                },
     3715
     3716                /**
     3717                 * Initialize syntax-highlighting editor.
     3718                 *
     3719                 * @since 4.9.0
     3720                 * @param {object} codeEditorSettings - Code editor settings.
     3721                 * @returns {void}
     3722                 */
     3723                initSyntaxHighlightingEditor: function( codeEditorSettings ) {
     3724                        var control = this, $textarea = control.container.find( 'textarea' ), settings, suspendEditorUpdate = false;
     3725
     3726                        settings = _.extend( {}, codeEditorSettings, {
     3727                                onTabNext: _.bind( control.onTabNext, control ),
     3728                                onTabPrevious: _.bind( control.onTabPrevious, control ),
     3729                                onUpdateErrorNotice: _.bind( control.onUpdateErrorNotice, control )
     3730                        });
     3731
     3732                        control.editor = wp.codeEditor.initialize( $textarea, settings );
     3733
     3734                        // Refresh when receiving focus.
     3735                        control.editor.codemirror.on( 'focus', function( codemirror ) {
     3736                                codemirror.refresh();
     3737                        });
     3738
     3739                        /*
     3740                         * When the CodeMirror instance changes, mirror to the textarea,
     3741                         * where we have our "true" change event handler bound.
     3742                         */
     3743                        control.editor.codemirror.on( 'change', function( codemirror ) {
     3744                                suspendEditorUpdate = true;
     3745                                $textarea.val( codemirror.getValue() ).trigger( 'change' );
     3746                                suspendEditorUpdate = false;
     3747                        });
     3748
     3749                        // Update CodeMirror when the setting is changed by another plugin.
     3750                        control.setting.bind( function( value ) {
     3751                                if ( ! suspendEditorUpdate ) {
     3752                                        control.editor.codemirror.setValue( value );
     3753                                }
     3754                        });
     3755
     3756                        // Prevent collapsing section when hitting Esc to tab out of editor.
     3757                        control.editor.codemirror.on( 'keydown', function onKeydown( codemirror, event ) {
     3758                                var escKeyCode = 27;
     3759                                if ( escKeyCode === event.keyCode ) {
     3760                                        event.stopPropagation();
     3761                                }
     3762                        });
     3763                },
     3764
     3765                /**
     3766                 * Handle tabbing to the field after the editor.
     3767                 *
     3768                 * @since 4.9.0
     3769                 * @returns {void}
     3770                 */
     3771                onTabNext: function onTabNext() {
     3772                        var control = this, controls, controlIndex, section;
     3773                        section = api.section( control.section() );
     3774                        controls = section.controls();
     3775                        controlIndex = controls.indexOf( control );
     3776                        if ( controls.length === controlIndex + 1 ) {
     3777                                $( '#customize-footer-actions .collapse-sidebar' ).focus();
     3778                        } else {
     3779                                controls[ controlIndex + 1 ].container.find( ':focusable:first' ).focus();
     3780                        }
     3781                },
     3782
     3783                /**
     3784                 * Handle tabbing to the field before the editor.
     3785                 *
     3786                 * @since 4.9.0
     3787                 * @returns {void}
     3788                 */
     3789                onTabPrevious: function onTabPrevious() {
     3790                        var control = this, controls, controlIndex, section;
     3791                        section = api.section( control.section() );
     3792                        controls = section.controls();
     3793                        controlIndex = controls.indexOf( control );
     3794                        if ( 0 === controlIndex ) {
     3795                                section.contentContainer.find( '.customize-section-title .customize-help-toggle, .customize-section-title .customize-section-description.open .section-description-close' ).last().focus();
     3796                        } else {
     3797                                controls[ controlIndex - 1 ].contentContainer.find( ':focusable:first' ).focus();
     3798                        }
     3799                },
     3800
     3801                /**
     3802                 * Update error notice.
     3803                 *
     3804                 * @since 4.9.0
     3805                 * @param {Array} errorAnnotations - Error annotations.
     3806                 * @returns {void}
     3807                 */
     3808                onUpdateErrorNotice: function onUpdateErrorNotice( errorAnnotations ) {
     3809                        var control = this, message;
     3810                        control.setting.notifications.remove( 'csslint_error' );
     3811
     3812                        if ( 0 !== errorAnnotations.length ) {
     3813                                if ( 1 === errorAnnotations.length ) {
     3814                                        message = api.l10n.customCssError.singular.replace( '%d', '1' );
     3815                                } else {
     3816                                        message = api.l10n.customCssError.plural.replace( '%d', String( errorAnnotations.length ) );
     3817                                }
     3818                                control.setting.notifications.add( 'csslint_error', new api.Notification( 'csslint_error', {
     3819                                        message: message,
     3820                                        type: 'error'
     3821                                } ) );
     3822                        }
     3823                },
     3824
     3825                /**
     3826                 * Initialize plain-textarea editor when syntax highlighting is disabled.
     3827                 *
     3828                 * @since 4.9.0
     3829                 * @returns {void}
     3830                 */
     3831                initPlainTextareaEditor: function() {
     3832                        var control = this, $textarea = control.container.find( 'textarea' ), textarea = $textarea[0];
     3833
     3834                        $textarea.on( 'blur', function onBlur() {
     3835                                $textarea.data( 'next-tab-blurs', false );
     3836                        } );
     3837
     3838                        $textarea.on( 'keydown', function onKeydown( event ) {
     3839                                var selectionStart, selectionEnd, value, tabKeyCode = 9, escKeyCode = 27;
     3840
     3841                                if ( escKeyCode === event.keyCode ) {
     3842                                        if ( ! $textarea.data( 'next-tab-blurs' ) ) {
     3843                                                $textarea.data( 'next-tab-blurs', true );
     3844                                                event.stopPropagation(); // Prevent collapsing the section.
     3845                                        }
     3846                                        return;
     3847                                }
     3848
     3849                                // Short-circuit if tab key is not being pressed or if a modifier key *is* being pressed.
     3850                                if ( tabKeyCode !== event.keyCode || event.ctrlKey || event.altKey || event.shiftKey ) {
     3851                                        return;
     3852                                }
     3853
     3854                                // Prevent capturing Tab characters if Esc was pressed.
     3855                                if ( $textarea.data( 'next-tab-blurs' ) ) {
     3856                                        return;
     3857                                }
     3858
     3859                                selectionStart = textarea.selectionStart;
     3860                                selectionEnd = textarea.selectionEnd;
     3861                                value = textarea.value;
     3862
     3863                                if ( selectionStart >= 0 ) {
     3864                                        textarea.value = value.substring( 0, selectionStart ).concat( '\t', value.substring( selectionEnd ) );
     3865                                        $textarea.selectionStart = textarea.selectionEnd = selectionStart + 1;
     3866                                }
     3867
     3868                                event.stopPropagation();
     3869                                event.preventDefault();
     3870                        });
     3871                }
     3872        });
     3873
    36323874        // Change objects contained within the main customize object to Settings.
    36333875        api.defaultConstructor = api.Setting;
    36343876
     
    43244566                header:              api.HeaderControl,
    43254567                background:          api.BackgroundControl,
    43264568                background_position: api.BackgroundPositionControl,
    4327                 theme:               api.ThemeControl
     4569                theme:               api.ThemeControl,
     4570                code_editor:         api.CodeEditorControl
    43284571        };
    43294572        api.panelConstructor = {};
    43304573        api.sectionConstructor = {
     
    56335876
    56345877                // Add code editor for Custom CSS.
    56355878                (function() {
    5636                         var ready, sectionReady = $.Deferred(), controlReady = $.Deferred();
     5879                        var sectionReady = $.Deferred();
    56375880
    56385881                        api.section( 'custom_css', function( section ) {
    56395882                                section.deferred.embedded.done( function() {
     
    56485891                                        }
    56495892                                });
    56505893                        });
    5651                         api.control( 'custom_css', function( control ) {
    5652                                 control.deferred.embedded.done( function() {
    5653                                         controlReady.resolve( control );
    5654                                 });
    5655                         });
    5656 
    5657                         ready = $.when( sectionReady, controlReady );
    56585894
    56595895                        // Set up the section desription behaviors.
    5660                         ready.done( function setupSectionDescription( section, control ) {
     5896                        sectionReady.done( function setupSectionDescription( section ) {
     5897                                var control = api.control( 'custom_css' );
    56615898
    56625899                                // Close the section description when clicking the close button.
    56635900                                section.container.find( '.section-description-buttons .section-description-close' ).on( 'click', function() {
     
    56685905                                });
    56695906
    56705907                                // Reveal help text if setting is empty.
    5671                                 if ( ! control.setting.get() ) {
     5908                                if ( control && ! control.setting.get() ) {
    56725909                                        section.container.find( '.section-meta .customize-section-description:first' )
    56735910                                                .addClass( 'open' )
    56745911                                                .show()
    56755912                                                .attr( 'aria-expanded', 'true' );
    56765913                                }
    56775914                        });
    5678 
    5679                         // Set up the code editor itself.
    5680                         if ( api.settings.customCss && api.settings.customCss.codeEditor ) {
    5681 
    5682                                 // Set up the syntax highlighting editor.
    5683                                 ready.done( function setupSyntaxHighlightingEditor( section, control ) {
    5684                                         var $textarea = control.container.find( 'textarea' ), settings, suspendEditorUpdate = false;
    5685 
    5686                                         // Make sure editor gets focused when control is focused.
    5687                                         control.focus = (function( originalFocus ) { // eslint-disable-line max-nested-callbacks
    5688                                                 return function( params ) { // eslint-disable-line max-nested-callbacks
    5689                                                         var extendedParams = _.extend( {}, params ), originalCompleteCallback;
    5690                                                         originalCompleteCallback = extendedParams.completeCallback;
    5691                                                         extendedParams.completeCallback = function() {
    5692                                                                 if ( originalCompleteCallback ) {
    5693                                                                         originalCompleteCallback();
    5694                                                                 }
    5695                                                                 if ( control.editor ) {
    5696                                                                         control.editor.codemirror.focus();
    5697                                                                 }
    5698                                                         };
    5699                                                         originalFocus.call( this, extendedParams );
    5700                                                 };
    5701                                         })( control.focus );
    5702 
    5703                                         settings = _.extend( {}, api.settings.customCss.codeEditor, {
    5704 
    5705                                                 /**
    5706                                                  * Handle tabbing to the field after the editor.
    5707                                                  *
    5708                                                  * @returns {void}
    5709                                                  */
    5710                                                 onTabNext: function onTabNext() {
    5711                                                         var controls, controlIndex;
    5712                                                         controls = section.controls();
    5713                                                         controlIndex = controls.indexOf( control );
    5714                                                         if ( controls.length === controlIndex + 1 ) {
    5715                                                                 $( '#customize-footer-actions .collapse-sidebar' ).focus();
    5716                                                         } else {
    5717                                                                 controls[ controlIndex + 1 ].container.find( ':focusable:first' ).focus();
    5718                                                         }
    5719                                                 },
    5720 
    5721                                                 /**
    5722                                                  * Handle tabbing to the field before the editor.
    5723                                                  *
    5724                                                  * @returns {void}
    5725                                                  */
    5726                                                 onTabPrevious: function onTabPrevious() {
    5727                                                         var controls, controlIndex;
    5728                                                         controls = section.controls();
    5729                                                         controlIndex = controls.indexOf( control );
    5730                                                         if ( 0 === controlIndex ) {
    5731                                                                 section.contentContainer.find( '.customize-section-title .customize-help-toggle, .customize-section-title .customize-section-description.open .section-description-close' ).last().focus();
    5732                                                         } else {
    5733                                                                 controls[ controlIndex - 1 ].contentContainer.find( ':focusable:first' ).focus();
    5734                                                         }
    5735                                                 },
    5736 
    5737                                                 /**
    5738                                                  * Update error notice.
    5739                                                  *
    5740                                                  * @param {Array} errorAnnotations - Error annotations.
    5741                                                  * @returns {void}
    5742                                                  */
    5743                                                 onUpdateErrorNotice: function onUpdateErrorNotice( errorAnnotations ) {
    5744                                                         var message;
    5745                                                         control.setting.notifications.remove( 'csslint_error' );
    5746 
    5747                                                         if ( 0 !== errorAnnotations.length ) {
    5748                                                                 if ( 1 === errorAnnotations.length ) {
    5749                                                                         message = api.l10n.customCssError.singular.replace( '%d', '1' );
    5750                                                                 } else {
    5751                                                                         message = api.l10n.customCssError.plural.replace( '%d', String( errorAnnotations.length ) );
    5752                                                                 }
    5753                                                                 control.setting.notifications.add( 'csslint_error', new api.Notification( 'csslint_error', {
    5754                                                                         message: message,
    5755                                                                         type: 'error'
    5756                                                                 } ) );
    5757                                                         }
    5758                                                 }
    5759                                         });
    5760 
    5761                                         control.editor = wp.codeEditor.initialize( $textarea, settings );
    5762 
    5763                                         // Refresh when receiving focus.
    5764                                         control.editor.codemirror.on( 'focus', function( codemirror ) {
    5765                                                 codemirror.refresh();
    5766                                         });
    5767 
    5768                                         /*
    5769                                          * When the CodeMirror instance changes, mirror to the textarea,
    5770                                          * where we have our "true" change event handler bound.
    5771                                          */
    5772                                         control.editor.codemirror.on( 'change', function( codemirror ) {
    5773                                                 suspendEditorUpdate = true;
    5774                                                 $textarea.val( codemirror.getValue() ).trigger( 'change' );
    5775                                                 suspendEditorUpdate = false;
    5776                                         });
    5777 
    5778                                         // Update CodeMirror when the setting is changed by another plugin.
    5779                                         control.setting.bind( function( value ) {
    5780                                                 if ( ! suspendEditorUpdate ) {
    5781                                                         control.editor.codemirror.setValue( value );
    5782                                                 }
    5783                                         });
    5784 
    5785                                         // Prevent collapsing section when hitting Esc to tab out of editor.
    5786                                         control.editor.codemirror.on( 'keydown', function onKeydown( codemirror, event ) {
    5787                                                 var escKeyCode = 27;
    5788                                                 if ( escKeyCode === event.keyCode ) {
    5789                                                         event.stopPropagation();
    5790                                                 }
    5791                                         });
    5792                                 });
    5793                         } else {
    5794 
    5795                                 // Allow tabs to be entered in Custom CSS textarea.
    5796                                 ready.done( function allowTabs( section, control ) {
    5797 
    5798                                         var $textarea = control.container.find( 'textarea' ), textarea = $textarea[0];
    5799 
    5800                                         $textarea.on( 'blur', function onBlur() {
    5801                                                 $textarea.data( 'next-tab-blurs', false );
    5802                                         } );
    5803 
    5804                                         $textarea.on( 'keydown', function onKeydown( event ) {
    5805                                                 var selectionStart, selectionEnd, value, tabKeyCode = 9, escKeyCode = 27;
    5806 
    5807                                                 if ( escKeyCode === event.keyCode ) {
    5808                                                         if ( ! $textarea.data( 'next-tab-blurs' ) ) {
    5809                                                                 $textarea.data( 'next-tab-blurs', true );
    5810                                                                 event.stopPropagation(); // Prevent collapsing the section.
    5811                                                         }
    5812                                                         return;
    5813                                                 }
    5814 
    5815                                                 // Short-circuit if tab key is not being pressed or if a modifier key *is* being pressed.
    5816                                                 if ( tabKeyCode !== event.keyCode || event.ctrlKey || event.altKey || event.shiftKey ) {
    5817                                                         return;
    5818                                                 }
    5819 
    5820                                                 // Prevent capturing Tab characters if Esc was pressed.
    5821                                                 if ( $textarea.data( 'next-tab-blurs' ) ) {
    5822                                                         return;
    5823                                                 }
    5824 
    5825                                                 selectionStart = textarea.selectionStart;
    5826                                                 selectionEnd = textarea.selectionEnd;
    5827                                                 value = textarea.value;
    5828 
    5829                                                 if ( selectionStart >= 0 ) {
    5830                                                         textarea.value = value.substring( 0, selectionStart ).concat( '\t', value.substring( selectionEnd ) );
    5831                                                         $textarea.selectionStart = textarea.selectionEnd = selectionStart + 1;
    5832                                                 }
    5833 
    5834                                                 event.stopPropagation();
    5835                                                 event.preventDefault();
    5836                                         });
    5837                                 });
    5838                         }
    58395915                })();
    58405916
    58415917                // Toggle visibility of Header Video notice when active state change.
  • src/wp-includes/class-wp-customize-manager.php

    diff --git src/wp-includes/class-wp-customize-manager.php src/wp-includes/class-wp-customize-manager.php
    index cead2d0ce9..fa362b504e 100644
    final class WP_Customize_Manager { 
    213213        private $_changeset_data;
    214214
    215215        /**
    216          * Code Editor Settings for Custom CSS.
    217          *
    218          * This variable contains the settings returned by `wp_enqueue_code_editor()` which are then later output
    219          * to the client in `WP_Customize_Manager::customize_pane_settings()`. A value of false means that the
    220          * Custom CSS section or control was removed, or that the Syntax Highlighting user pref was turned off.
    221          *
    222          * @see wp_enqueue_code_editor()
    223          * @see WP_Customize_Manager::enqueue_control_scripts()
    224          * @see WP_Customize_Manager::customize_pane_settings()
    225          * @since 4.9.0
    226          * @var array|false
    227          */
    228         private $_custom_css_code_editor_settings = false;
    229 
    230         /**
    231216         * Constructor.
    232217         *
    233218         * @since 3.4.0
    final class WP_Customize_Manager { 
    291276                require_once( ABSPATH . WPINC . '/customize/class-wp-customize-site-icon-control.php' );
    292277                require_once( ABSPATH . WPINC . '/customize/class-wp-customize-header-image-control.php' );
    293278                require_once( ABSPATH . WPINC . '/customize/class-wp-customize-theme-control.php' );
     279                require_once( ABSPATH . WPINC . '/customize/class-wp-customize-code-editor-control.php' );
    294280                require_once( ABSPATH . WPINC . '/customize/class-wp-widget-area-customize-control.php' );
    295281                require_once( ABSPATH . WPINC . '/customize/class-wp-widget-form-customize-control.php' );
    296282                require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-control.php' );
    final class WP_Customize_Manager { 
    33373323                foreach ( $this->controls as $control ) {
    33383324                        $control->enqueue();
    33393325                }
    3340 
    3341                 if ( $this->get_section( 'custom_css' ) && $this->get_control( 'custom_css' ) ) {
    3342                         $this->_custom_css_code_editor_settings = wp_enqueue_code_editor( array(
    3343                                 'type' => 'text/css',
    3344                         ) );
    3345                 }
    33463326        }
    33473327
    33483328        /**
    final class WP_Customize_Manager { 
    36003580                                'stylesheet' => $this->get_stylesheet(),
    36013581                                'active'     => $this->is_theme_active(),
    36023582                        ),
    3603                         'customCss' => array(
    3604                                 'codeEditor' => $this->_custom_css_code_editor_settings,
    3605                         ),
    36063583                        'url'      => array(
    36073584                                'preview'       => esc_url_raw( $this->get_preview_url() ),
    36083585                                'parent'        => esc_url_raw( admin_url() ),
    final class WP_Customize_Manager { 
    37363713                $this->register_control_type( 'WP_Customize_Cropped_Image_Control' );
    37373714                $this->register_control_type( 'WP_Customize_Site_Icon_Control' );
    37383715                $this->register_control_type( 'WP_Customize_Theme_Control' );
     3716                $this->register_control_type( 'WP_Customize_Code_Editor_Control' );
    37393717
    37403718                /* Themes */
    37413719
    final class WP_Customize_Manager { 
    42514229                ) );
    42524230                $this->add_setting( $custom_css_setting );
    42534231
    4254                 $this->add_control( 'custom_css', array(
    4255                         'type'     => 'textarea',
     4232                $this->add_control( new WP_Customize_Code_Editor_Control( $this, 'custom_css', array(
    42564233                        'section'  => 'custom_css',
    42574234                        'settings' => array( 'default' => $custom_css_setting->id ),
    4258                         'input_attrs' => array(
    4259                                 'class' => 'code', // Ensures contents displayed as LTR instead of RTL.
    4260                         ),
    4261                 ) );
     4235                        'code_type' => 'text/css',
     4236                ) ) );
    42624237        }
    42634238
    42644239        /**
  • new file src/wp-includes/customize/class-wp-customize-code-editor-control.php

    diff --git src/wp-includes/customize/class-wp-customize-code-editor-control.php src/wp-includes/customize/class-wp-customize-code-editor-control.php
    new file mode 100644
    index 0000000000..24cae1173d
    - +  
     1<?php
     2/**
     3 * Customize API: WP_Customize_Code_Editor_Control class
     4 *
     5 * @package WordPress
     6 * @subpackage Customize
     7 * @since 4.9.0
     8 */
     9
     10/**
     11 * Customize Theme Control class.
     12 *
     13 * @since 4.9.0
     14 *
     15 * @see WP_Customize_Control
     16 */
     17class WP_Customize_Code_Editor_Control extends WP_Customize_Control {
     18
     19        /**
     20         * Customize control type.
     21         *
     22         * @since 4.9.0
     23         * @var string
     24         */
     25        public $type = 'code_editor';
     26
     27        /**
     28         * Type of code that is being edited.
     29         *
     30         * @var string
     31         * @since 4.9.0
     32         */
     33        public $code_type = '';
     34
     35        /**
     36         * Code editor settings.
     37         *
     38         * @see wp_enqueue_code_editor()
     39         * @since 4.9.0
     40         * @var array|false
     41         */
     42        public $editor_settings = array();
     43
     44        /**
     45         * Enqueue control related scripts/styles.
     46         *
     47         * @since 4.9.0
     48         */
     49        public function enqueue() {
     50                $this->editor_settings = wp_enqueue_code_editor( array_merge(
     51                        array(
     52                                'type' => $this->code_type,
     53                        ),
     54                        $this->editor_settings
     55                ) );
     56        }
     57
     58        /**
     59         * Refresh the parameters passed to the JavaScript via JSON.
     60         *
     61         * @since 4.9.0
     62         * @see WP_Customize_Control::json()
     63         *
     64         * @return array Array of parameters passed to the JavaScript.
     65         */
     66        public function json() {
     67                $json = parent::json();
     68                $json['code_type'] = $this->code_type;
     69                $json['editor_settings'] = $this->editor_settings;
     70                return $json;
     71        }
     72
     73        /**
     74         * Don't render the control content from PHP, as it's rendered via JS on load.
     75         *
     76         * @since 4.9.0
     77         */
     78        public function render_content() {}
     79
     80        /**
     81         * Render a JS template for theme display.
     82         *
     83         * @since 4.9.0
     84         */
     85        public function content_template() {
     86                ?>
     87                <# var elementIdPrefix = 'el' + String( Math.random() ); #>
     88                <# if ( data.label ) { #>
     89                        <label for="{{ elementIdPrefix }}_editor">
     90                                <span class="customize-control-title">{{ data.label }}</span>
     91                        </label>
     92                <# } #>
     93                <# if ( data.description ) { #>
     94                        <span class="description customize-control-description">{{{ data.description }}}</span>
     95                <# } #>
     96                <div class="customize-control-notifications-container"></div>
     97                <textarea id="{{ elementIdPrefix }}_editor" class="code"></textarea>
     98                <?php
     99        }
     100}
  • src/wp-includes/general-template.php

    diff --git src/wp-includes/general-template.php src/wp-includes/general-template.php
    index 9f6c47a268..ef025adeee 100644
    function wp_enqueue_editor() { 
    31253125 * @param array $args {
    31263126 *     Args.
    31273127 *
    3128  *     @type string   $type     The MIME type of the file to be edited.
    3129  *     @type string   $file     Filename to be edited. Extension is used to sniff the type. Can be supplied as alternative to `$type` param.
    3130  *     @type array    $settings Settings to merge on top of defaults which derive from `$type` or `$file` args.
    3131  *     @type WP_Theme $theme    Theme being edited when on theme editor.
    3132  *     @type string   $plugin   Plugin being edited when on plugin editor.
     3128 *     @type string   $type       The MIME type of the file to be edited.
     3129 *     @type string   $file       Filename to be edited. Extension is used to sniff the type. Can be supplied as alternative to `$type` param.
     3130 *     @type WP_Theme $theme      Theme being edited when on theme editor.
     3131 *     @type string   $plugin     Plugin being edited when on plugin editor.
     3132 *     @type array    $codemirror Additional CodeMirror setting overrides.
     3133 *     @type array    $csslint    CSSLint rule overrides.
     3134 *     @type array    $jshint     JSHint rule overrides.
     3135 *     @type array    $htmlhint   JSHint rule overrides.
    31333136 * }
    31343137 * @returns array|false Settings for the enqueued code editor, or false if the editor was not enqueued .
    31353138 */
    function wp_enqueue_code_editor( $args ) { 
    34083411        }
    34093412
    34103413        // Let settings supplied via args override any defaults.
    3411         if ( isset( $args['settings'] ) ) {
    3412                 foreach ( $args['settings'] as $key => $value ) {
    3413                         $settings[ $key ] = array_merge(
    3414                                 $settings[ $key ],
    3415                                 $value
    3416                         );
    3417                 }
     3414        foreach ( wp_array_slice_assoc( $args, array( 'codemirror', 'csslint', 'jshint', 'htmlhint' ) ) as $key => $value ) {
     3415                $settings[ $key ] = array_merge(
     3416                        $settings[ $key ],
     3417                        $value
     3418                );
    34183419        }
    34193420
    34203421        /**
    function wp_enqueue_code_editor( $args ) { 
    34283429         * @param array $args {
    34293430         *     Args passed when calling `wp_enqueue_code_editor()`.
    34303431         *
    3431          *     @type string   $type     The MIME type of the file to be edited.
    3432          *     @type string   $file     Filename being edited.
    3433          *     @type array    $settings Settings to merge on top of defaults which derive from `$type` or `$file` args.
    3434          *     @type WP_Theme $theme    Theme being edited when on theme editor.
    3435          *     @type string   $plugin   Plugin being edited when on plugin editor.
     3432         *     @type string   $type       The MIME type of the file to be edited.
     3433         *     @type string   $file       Filename being edited.
     3434         *     @type WP_Theme $theme      Theme being edited when on theme editor.
     3435         *     @type string   $plugin     Plugin being edited when on plugin editor.
     3436         *     @type array    $codemirror Additional CodeMirror setting overrides.
     3437         *     @type array    $csslint    CSSLint rule overrides.
     3438         *     @type array    $jshint     JSHint rule overrides.
     3439         *     @type array    $htmlhint   JSHint rule overrides.
    34363440         * }
    34373441         */
    34383442        $settings = apply_filters( 'wp_code_editor_settings', $settings, $args );
  • tests/phpunit/tests/customize/manager.php

    diff --git tests/phpunit/tests/customize/manager.php tests/phpunit/tests/customize/manager.php
    index 23aa759c8a..1955a1cd74 100644
    class Tests_WP_Customize_Manager extends WP_UnitTestCase { 
    23512351                $data = json_decode( $json, true );
    23522352                $this->assertNotEmpty( $data );
    23532353
    2354                 $this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices', 'customCss', 'changeset', 'timeouts' ), array_keys( $data ) );
     2354                $this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices', 'changeset', 'timeouts' ), array_keys( $data ) );
    23552355                $this->assertEquals( $autofocus, $data['autofocus'] );
    23562356                $this->assertArrayHasKey( 'save', $data['nonce'] );
    23572357                $this->assertArrayHasKey( 'preview', $data['nonce'] );