Make WordPress Core


Ignore:
Timestamp:
09/13/2017 06:07:48 AM (9 years ago)
Author:
westonruter
Message:

Editor: Add CodeMirror-powered code editor with syntax highlighting, linting, and auto-completion.

  • Code editor is integrated into the Theme/Plugin Editor, Additional CSS in Customizer, and Custom HTML widget. Code editor is not yet integrated into the post editor, and it may not be until accessibility concerns are addressed.
  • The CodeMirror component in the Custom HTML widget is integrated in a similar way to TinyMCE being integrated into the Text widget, adopting the same approach for integrating dynamic JavaScript-initialized fields.
  • Linting is performed for JS, CSS, HTML, and JSON via JSHint, CSSLint, HTMLHint, and JSONLint respectively. Linting is not yet supported for PHP.
  • When user lacks unfiltered_html the capability, the Custom HTML widget will report any Kses-invalid elements and attributes as errors via a custom Kses rule for HTMLHint.
  • When linting errors are detected, the user will be prevented from saving the code until the errors are fixed, reducing instances of broken websites.
  • The placeholder value is removed from Custom CSS in favor of a fleshed-out section description which now auto-expands when the CSS field is empty. See #39892.
  • The CodeMirror library is included as wp.CodeMirror to prevent conflicts with any existing CodeMirror global.
  • An wp.codeEditor.initialize() API in JS is provided to convert a textarea into CodeMirror, with a wp_enqueue_code_editor() function in PHP to manage enqueueing the assets and settings needed to edit a given type of code.
  • A user preference is added to manage whether or not "syntax highlighting" is enabled. The feature is opt-out, being enabled by default.
  • Allowed file extensions in the theme and plugin editors have been updated to include formats which CodeMirror has modes for: conf, css, diff, patch, html, htm, http, js, json, jsx, less, md, php, phtml, php3, php4, php5, php7, phps, scss, sass, sh, bash, sql, svg, xml, yml, yaml, txt.

Props westonruter, georgestephanis, obenland, melchoyce, pixolin, mizejewski, michelleweber, afercia, grahamarmfield, samikeijonen, rianrietveld, iseulde.
See #38707.
Fixes #12423, #39892.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/widgets/class-wp-widget-custom-html.php

    r41132 r41376  
    1818
    1919        /**
     20         * Whether or not the widget has been registered yet.
     21         *
     22         * @since 4.9.0
     23         * @var bool
     24         */
     25        protected $registered = false;
     26
     27        /**
    2028         * Default instance.
    2129         *
     
    4452                );
    4553                parent::__construct( 'custom_html', __( 'Custom HTML' ), $widget_ops, $control_ops );
     54        }
     55
     56        /**
     57         * Add hooks for enqueueing assets when registering all widget instances of this widget class.
     58         *
     59         * @since 4.9.0
     60         *
     61         * @param integer $number Optional. The unique order number of this widget instance
     62         *                        compared to other instances of the same class. Default -1.
     63         */
     64        public function _register_one( $number = -1 ) {
     65                parent::_register_one( $number );
     66                if ( $this->registered ) {
     67                        return;
     68                }
     69                $this->registered = true;
     70
     71                wp_add_inline_script( 'custom-html-widgets', sprintf( 'wp.customHtmlWidgets.idBases.push( %s );', wp_json_encode( $this->id_base ) ) );
     72
     73                // Note that the widgets component in the customizer will also do the 'admin_print_scripts-widgets.php' action in WP_Customize_Widgets::print_scripts().
     74                add_action( 'admin_print_scripts-widgets.php', array( $this, 'enqueue_admin_scripts' ) );
     75
     76                // Note that the widgets component in the customizer will also do the 'admin_footer-widgets.php' action in WP_Customize_Widgets::print_footer_scripts().
     77                add_action( 'admin_footer-widgets.php', array( 'WP_Widget_Custom_HTML', 'render_control_template_scripts' ) );
     78
     79                // Note this action is used to ensure the help text is added to the end.
     80                add_action( 'admin_head-widgets.php', array( 'WP_Widget_Custom_HTML', 'add_help_text' ) );
    4681        }
    4782
     
    119154
    120155        /**
     156         * Loads the required scripts and styles for the widget control.
     157         *
     158         * @since 4.9.0
     159         */
     160        public function enqueue_admin_scripts() {
     161                $settings = wp_enqueue_code_editor( array(
     162                        'type' => 'text/html',
     163                ) );
     164
     165                wp_enqueue_script( 'custom-html-widgets' );
     166                if ( empty( $settings ) ) {
     167                        $settings = array(
     168                                'disabled' => true,
     169                        );
     170                }
     171                wp_add_inline_script( 'custom-html-widgets', sprintf( 'wp.customHtmlWidgets.init( %s );', wp_json_encode( $settings ) ), 'after' );
     172
     173                $l10n = array(
     174                        'errorNotice' => wp_array_slice_assoc(
     175                                /* translators: placeholder is error count */
     176                                _n_noop( 'There is %d error which must be fixed before you can save.', 'There are %d errors which must be fixed before you can save.' ),
     177                                array( 'singular', 'plural' )
     178                        ),
     179                );
     180                wp_add_inline_script( 'custom-html-widgets', sprintf( 'jQuery.extend( wp.customHtmlWidgets.l10n, %s );', wp_json_encode( $l10n ) ), 'after' );
     181        }
     182
     183        /**
    121184         * Outputs the Custom HTML widget settings form.
    122185         *
    123186         * @since 4.8.1
    124          *
     187         * @since 4.9.0 The form contains only hidden sync inputs. For the control UI, see `WP_Widget_Custom_HTML::render_control_template_scripts()`.
     188         *
     189         * @see WP_Widget_Custom_HTML::render_control_template_scripts()
    125190         * @param array $instance Current instance.
    126191         * @returns void
     
    129194                $instance = wp_parse_args( (array) $instance, $this->default_instance );
    130195                ?>
    131                 <p>
    132                         <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:' ); ?></label>
    133                         <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $instance['title'] ); ?>"/>
    134                 </p>
    135 
    136                 <p>
    137                         <label for="<?php echo $this->get_field_id( 'content' ); ?>"><?php _e( 'Content:' ); ?></label>
    138                         <textarea class="widefat code" rows="16" cols="20" id="<?php echo $this->get_field_id( 'content' ); ?>" name="<?php echo $this->get_field_name( 'content' ); ?>"><?php echo esc_textarea( $instance['content'] ); ?></textarea>
    139                 </p>
    140 
    141                 <?php if ( ! current_user_can( 'unfiltered_html' ) ) : ?>
    142                         <?php
    143                         $probably_unsafe_html = array( 'script', 'iframe', 'form', 'input', 'style' );
    144                         $allowed_html = wp_kses_allowed_html( 'post' );
    145                         $disallowed_html = array_diff( $probably_unsafe_html, array_keys( $allowed_html ) );
    146                         ?>
    147                         <?php if ( ! empty( $disallowed_html ) ) : ?>
    148                                 <p>
    149                                         <?php _e( 'Some HTML tags are not permitted, including:' ); ?>
    150                                         <code><?php echo join( '</code>, <code>', $disallowed_html ); ?></code>
    151                                 </p>
     196                <input id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" class="title sync-input" type="hidden" value="<?php echo esc_attr( $instance['title'] ); ?>"/>
     197                <textarea id="<?php echo $this->get_field_id( 'content' ); ?>" name="<?php echo $this->get_field_name( 'content' ); ?>" class="content sync-input" hidden><?php echo esc_textarea( $instance['content'] ); ?></textarea>
     198                <?php
     199        }
     200
     201        /**
     202         * Render form template scripts.
     203         *
     204         * @since 4.9.0
     205         */
     206        public static function render_control_template_scripts() {
     207                ?>
     208                <script type="text/html" id="tmpl-widget-custom-html-control-fields">
     209                        <# var elementIdPrefix = 'el' + String( Math.random() ).replace( /\D/g, '' ) + '_' #>
     210                        <p>
     211                                <label for="{{ elementIdPrefix }}title"><?php esc_html_e( 'Title:', 'default' ); ?></label>
     212                                <input id="{{ elementIdPrefix }}title" type="text" class="widefat title">
     213                        </p>
     214
     215                        <p>
     216                                <label for="{{ elementIdPrefix }}content" class="screen-reader-text"><?php esc_html_e( 'Content:', 'default' ); ?></label>
     217                                <textarea id="{{ elementIdPrefix }}content" class="widefat code content" rows="16" cols="20"></textarea>
     218                        </p>
     219
     220                        <?php if ( ! current_user_can( 'unfiltered_html' ) ) : ?>
     221                                <?php
     222                                $probably_unsafe_html = array( 'script', 'iframe', 'form', 'input', 'style' );
     223                                $allowed_html = wp_kses_allowed_html( 'post' );
     224                                $disallowed_html = array_diff( $probably_unsafe_html, array_keys( $allowed_html ) );
     225                                ?>
     226                                <?php if ( ! empty( $disallowed_html ) ) : ?>
     227                                        <# if ( data.codeEditorDisabled ) { #>
     228                                                <p>
     229                                                        <?php _e( 'Some HTML tags are not permitted, including:', 'default' ); ?>
     230                                                        <code><?php echo join( '</code>, <code>', $disallowed_html ); ?></code>
     231                                                </p>
     232                                        <# } #>
     233                                <?php endif; ?>
    152234                        <?php endif; ?>
    153                 <?php endif; ?>
     235
     236                        <div class="code-editor-error-container"></div>
     237                </script>
    154238                <?php
    155239        }
     240
     241        /**
     242         * Add help text to widgets admin screen.
     243         *
     244         * @since 4.9.0
     245         */
     246        public static function add_help_text() {
     247                $screen = get_current_screen();
     248
     249                $content = '<p>';
     250                $content .= __( 'Use the Custom HTML widget to add arbitrary HTML code to your widget areas.' );
     251                $content .= '</p>';
     252
     253                $content .= '<p>' . __( 'When using a keyboard to navigate:' ) . '</p>';
     254                $content .= '<ul>';
     255                $content .= '<li>' . __( 'In the HTML edit field, Tab enters a tab character.' ) . '</li>';
     256                $content .= '<li>' . __( 'To move keyboard focus, press Esc then Tab for the next element, or Esc then Shift+Tab for the previous element.' ) . '</li>';
     257                $content .= '</ul>';
     258
     259                $content .= '<p>';
     260                $content .= sprintf(
     261                        /* translators: placeholder is link to user profile */
     262                        __( 'The edit field automatically highlights code syntax. You can disable this in your %s to work in plan text mode.' ),
     263                        sprintf(
     264                                ' <a href="%1$s" class="external-link" target="_blank">%2$s<span class="screen-reader-text">%3$s</span></a>',
     265                                esc_url( get_edit_profile_url() . '#syntax_highlighting' ),
     266                                __( 'user profile' ),
     267                                /* translators: accessibility text */
     268                                __( '(opens in a new window)', 'default' )
     269                        )
     270                );
     271                $content .= '</p>';
     272
     273                $screen->add_help_tab( array(
     274                        'id' => 'custom_html_widget',
     275                        'title' => __( 'Custom HTML Widget' ),
     276                        'content' => $content,
     277                ) );
     278        }
    156279}
Note: See TracChangeset for help on using the changeset viewer.