Make WordPress Core


Ignore:
Timestamp:
09/13/2017 06:07:48 AM (7 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.