WordPress.org

Make WordPress Core

Ticket #24048: 24048.11.diff

File 24048.11.diff, 30.0 KB (added by westonruter, 7 months ago)

Δ https://github.com/xwp/wordpress-develop/pull/281/commits/048ec47

  • src/wp-admin/css/common.css

    diff --git src/wp-admin/css/common.css src/wp-admin/css/common.css
    index 1702e7736c..81c944dd80 100644
    div.error { 
    14661466.wrap #templateside .notice {
    14671467        display: block;
    14681468        margin: 0;
    1469         padding: 5px 12px;
     1469        padding: 5px 8px;
    14701470        font-weight: 600;
    14711471        text-decoration: none;
    14721472}
    14731473
    14741474.wrap #templateside span.notice {
    1475   margin-left: -12px;
     1475        margin-left: -12px;
    14761476}
    14771477
    14781478#templateside li.notice a {
    1479   padding: 0;
     1479        padding: 0;
    14801480}
    14811481
    14821482/* Update icon. */
    img { 
    30363036        width: 97%;
    30373037        height: calc( 100vh - 280px );
    30383038}
     3039
    30393040#templateside {
    30403041        margin-top: 31px;
    3041         overflow: scroll;
     3042        overflow: auto;
     3043        padding: 2px;
     3044        height: calc(100vh - 280px);
     3045}
     3046#templateside ul ul {
     3047        padding-left: 12px;
     3048}
     3049
     3050/*
     3051 * Styles for Theme and Plugin editors.
     3052 */
     3053
     3054/* Hide collapsed items. */
     3055[role="treeitem"][aria-expanded="false"] > ul {
     3056        display: none;
    30423057}
    30433058
     3059/* Use arrow dashicons for folder states, but hide from screen readers. */
     3060[role="treeitem"] span[aria-hidden] {
     3061        display: inline;
     3062        font-family: dashicons;
     3063        font-size: 20px;
     3064        position: absolute;
     3065        pointer-events: none;
     3066}
     3067[role="treeitem"][aria-expanded="false"] > .folder-label .icon:after {
     3068        content: "\f139";
     3069}
     3070[role="treeitem"][aria-expanded="true"] > .folder-label .icon:after {
     3071        content: "\f140";
     3072}
     3073[role="treeitem"] .folder-label {
     3074        display: block;
     3075        padding: 3px 3px 3px 12px;
     3076        cursor: pointer;
     3077}
     3078
     3079/* Remove outline, and create our own focus and hover styles */
     3080[role="treeitem"] {
     3081        outline: 0;
     3082}
     3083[role="treeitem"] .folder-label.focus {
     3084        color: #124964;
     3085        box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, .8);
     3086}
     3087[role="treeitem"].hover,
     3088[role="treeitem"] .folder-label.hover {
     3089        background-color: #DDDDDD;
     3090}
     3091
     3092.tree-folder {
     3093        margin: 0;
     3094        position: relative;
     3095}
     3096[role="treeitem"] li {
     3097        position: relative;
     3098}
     3099
     3100/* Styles for folder indicators/depth */
     3101.tree-folder .tree-folder::after {
     3102        content: ' ';
     3103        display: block;
     3104        position: absolute;
     3105        left: 2px;
     3106        border-left: 1px solid #ccc;
     3107        top: -13px;
     3108        bottom: 10px;
     3109}
     3110.tree-folder > li::before {
     3111        content: ' ';
     3112        position: absolute;
     3113        display: block;
     3114        border-left: 1px solid #ccc;
     3115        left: 2px;
     3116        top: -5px;
     3117        height: 18px;
     3118        width: 7px;
     3119        border-bottom: 1px solid #ccc;
     3120}
     3121.tree-folder > li::after {
     3122        content: ' ';
     3123        position: absolute;
     3124        display: block;
     3125        border-left: 1px solid #ccc;
     3126        left: 2px;
     3127        bottom: -7px;
     3128        top: 0;
     3129}
     3130
     3131/* current-file needs to adjustment for .notice styles */
     3132#templateside .current-file {
     3133        margin: -4px 0 -2px;
     3134}
     3135.tree-folder > .current-file::before {
     3136        left: 4px;
     3137        height: 15px;
     3138        width: 6px;
     3139        border-left: none;
     3140        top: 3px;
     3141}
     3142.tree-folder > .current-file::after {
     3143        bottom: -4px;
     3144        height: 7px;
     3145        left: 2px;
     3146        top: auto;
     3147}
     3148
     3149/* Lines shouldn't continue on last item */
     3150.tree-folder > li:last-child::after,
     3151.tree-folder li:last-child > .tree-folder::after {
     3152        display: none;
     3153}
     3154
     3155
    30443156#theme-plugin-editor-label {
    30453157        display: inline-block;
    30463158        margin-bottom: 1em;
    img { 
    36533765                width: 100%;
    36543766        }
    36553767
     3768        #templateside ul ul {
     3769                padding-left: 1.5em;
     3770        }
     3771        [role="treeitem"] .folder-label {
     3772                display: block;
     3773                padding: 5px;
     3774        }
     3775        .tree-folder > li::before,
     3776        .tree-folder > li::after,
     3777        .tree-folder .tree-folder::after {
     3778                left: -8px;
     3779        }
     3780        .tree-folder > li::before {
     3781                top: 0px;
     3782                height: 13px;
     3783        }
     3784        .tree-folder > .current-file::before {
     3785                left: -5px;
     3786                top: 7px;
     3787                width: 4px;
     3788        }
     3789        .tree-folder > .current-file::after {
     3790                height: 9px;
     3791                left: -8px;
     3792        }
     3793        .wrap #templateside span.notice {
     3794                margin-left: -14px;
     3795        }
     3796
    36563797        .fileedit-sub .alignright {
    36573798                margin-top: 15px;
    36583799        }
  • src/wp-admin/includes/misc.php

    diff --git src/wp-admin/includes/misc.php src/wp-admin/includes/misc.php
    index c434b8d3ed..5cf0e21f28 100644
    function update_recently_edited( $file ) { 
    269269        update_option( 'recently_edited', $oldfiles );
    270270}
    271271
     272/**
     273 * Makes a tree structure for the Theme Editor's file list.
     274 *
     275 * @since 4.9.0
     276 * @access private
     277 *
     278 * @param array $allowed_files List of theme file paths.
     279 * @return array Tree structure for listing theme files.
     280 */
     281function wp_make_theme_file_tree( $allowed_files ) {
     282        $tree_list = array();
     283        foreach ( $allowed_files as $file_name => $absolute_filename ) {
     284                $list = explode( '/', $file_name );
     285                $last_dir = &$tree_list;
     286                foreach ( $list as $dir ) {
     287                        $last_dir =& $last_dir[ $dir ];
     288                }
     289                $last_dir = $file_name;
     290        }
     291        return $tree_list;
     292}
     293
     294/**
     295 * Outputs the formatted file list for the Theme Editor.
     296 *
     297 * @since 4.9.0
     298 * @access private
     299 *
     300 * @param array|string $tree  List of file/folder paths, or filename.
     301 * @param int          $level The aria-level for the current iteration.
     302 * @param int          $size  The aria-setsize for the current iteration.
     303 * @param int          $index The aria-posinset for the current iteration.
     304 */
     305function wp_print_theme_file_tree( $tree, $level = 2, $size = 1, $index = 1 ) {
     306        global $relative_file, $stylesheet;
     307
     308        if ( is_array( $tree ) ) {
     309                $index = 0;
     310                $size = count( $tree );
     311                foreach ( $tree as $label => $theme_file ) :
     312                        $index++;
     313                        if ( ! is_array( $theme_file ) ) {
     314                                wp_print_theme_file_tree( $theme_file, $level, $index, $size );
     315                                continue;
     316                        }
     317                        ?>
     318                        <li role="treeitem" aria-expanded="true" tabindex="-1"
     319                                aria-level="<?php echo esc_attr( $level ); ?>"
     320                                aria-setsize="<?php echo esc_attr( $size ); ?>"
     321                                aria-posinset="<?php echo esc_attr( $index ); ?>">
     322                                <span class="folder-label"><?php echo esc_html( $label ); ?> <span class="screen-reader-text"><?php _e( 'folder' ); ?></span><span aria-hidden="true" class="icon"></span></span>
     323                                <ul role="group" class="tree-folder"><?php wp_print_theme_file_tree( $theme_file, $level + 1, $index, $size ); ?></ul>
     324                        </li>
     325                        <?php
     326                endforeach;
     327        } else {
     328                $filename = $tree;
     329                $url = add_query_arg(
     330                        array(
     331                                'file' => rawurlencode( $tree ),
     332                                'theme' => rawurlencode( $stylesheet ),
     333                        ),
     334                        admin_url( 'theme-editor.php' )
     335                );
     336                ?>
     337                <li role="none" class="<?php echo esc_attr( $relative_file === $filename ? 'current-file' : '' ); ?>">
     338                        <a role="treeitem" tabindex="<?php echo esc_attr( $relative_file === $filename ? '0' : '-1' ); ?>"
     339                                href="<?php echo esc_url( $url ); ?>"
     340                                aria-level="<?php echo esc_attr( $level ); ?>"
     341                                aria-setsize="<?php echo esc_attr( $size ); ?>"
     342                                aria-posinset="<?php echo esc_attr( $index ); ?>">
     343                                <?php
     344                                $file_description = esc_html( get_file_description( $filename ) );
     345                                if ( $file_description !== $filename && basename( $filename ) !== $file_description ) {
     346                                        $file_description .= '<br /><span class="nonessential">(' . esc_html( $filename ) . ')</span>';
     347                                }
     348
     349                                if ( $relative_file === $filename ) {
     350                                        echo '<span class="notice notice-info">' . $file_description . '</span>';
     351                                } else {
     352                                        echo $file_description;
     353                                }
     354                                ?>
     355                        </a>
     356                </li>
     357                <?php
     358        }
     359}
     360
     361/**
     362 * Makes a tree structure for the Plugin Editor's file list.
     363 *
     364 * @since 4.9.0
     365 * @access private
     366 *
     367 * @param string $plugin_editable_files List of plugin file paths.
     368 * @return array Tree structure for listing plugin files.
     369 */
     370function wp_make_plugin_file_tree( $plugin_editable_files ) {
     371        $tree_list = array();
     372        foreach ( $plugin_editable_files as $plugin_file ) {
     373                $list = explode( '/', preg_replace( '#^.+?/#', '', $plugin_file ) );
     374                $last_dir = &$tree_list;
     375                foreach ( $list as $dir ) {
     376                        $last_dir =& $last_dir[ $dir ];
     377                }
     378                $last_dir = $plugin_file;
     379        }
     380        return $tree_list;
     381}
     382
     383/**
     384 * Outputs the formatted file list for the Plugin Editor.
     385 *
     386 * @since 4.9.0
     387 * @access private
     388 *
     389 * @param array|string $tree  List of file/folder paths, or filename.
     390 * @param string       $label Name of file or folder to print.
     391 * @param int          $level The aria-level for the current iteration.
     392 * @param int          $size  The aria-setsize for the current iteration.
     393 * @param int          $index The aria-posinset for the current iteration.
     394 */
     395function wp_print_plugin_file_tree( $tree, $label = '', $level = 2, $size = 1, $index = 1 ) {
     396        global $file, $plugin;
     397        if ( is_array( $tree ) ) {
     398                $index = 0;
     399                $size = count( $tree );
     400                foreach ( $tree as $label => $plugin_file ) :
     401                        $index++;
     402                        if ( ! is_array( $plugin_file ) ) {
     403                                wp_print_plugin_file_tree( $plugin_file, $label, $level, $index, $size );
     404                                continue;
     405                        }
     406                        ?>
     407                        <li role="treeitem" aria-expanded="true" tabindex="-1"
     408                                aria-level="<?php echo esc_attr( $level ); ?>"
     409                                aria-setsize="<?php echo esc_attr( $size ); ?>"
     410                                aria-posinset="<?php echo esc_attr( $index ); ?>">
     411                                <span class="folder-label"><?php echo esc_html( $label ); ?> <span class="screen-reader-text"><?php _e( 'folder' ); ?></span><span aria-hidden="true" class="icon"></span></span>
     412                                <ul role="group" class="tree-folder"><?php wp_print_plugin_file_tree( $plugin_file, '', $level + 1, $index, $size ); ?></ul>
     413                        </li>
     414                        <?php
     415                endforeach;
     416        } else {
     417                $url = add_query_arg(
     418                        array(
     419                                'file' => rawurlencode( $tree ),
     420                                'plugin' => rawurlencode( $plugin ),
     421                        ),
     422                        admin_url( 'plugin-editor.php' )
     423                );
     424                ?>
     425                <li role="none" class="<?php echo esc_attr( $file === $tree ? 'current-file' : '' ); ?>">
     426                        <a role="treeitem" tabindex="<?php echo esc_attr( $file === $tree ? '0' : '-1' ); ?>"
     427                                href="<?php echo esc_url( $url ); ?>"
     428                                aria-level="<?php echo esc_attr( $level ); ?>"
     429                                aria-setsize="<?php echo esc_attr( $size ); ?>"
     430                                aria-posinset="<?php echo esc_attr( $index ); ?>">
     431                                <?php
     432                                if ( $file === $tree ) {
     433                                        echo '<span class="notice notice-info">' . esc_html( $label ) . '</span>';
     434                                } else {
     435                                        echo esc_html( $label );
     436                                }
     437                                ?>
     438                        </a>
     439                </li>
     440                <?php
     441        }
     442}
     443
    272444/**
    273445 * Flushes rewrite rules if siteurl, home or page_on_front changed.
    274446 *
  • src/wp-admin/js/theme-plugin-editor.js

    diff --git src/wp-admin/js/theme-plugin-editor.js src/wp-admin/js/theme-plugin-editor.js
    index 683d7fb562..2be828728b 100644
    if ( ! window.wp ) { 
    66
    77wp.themePluginEditor = (function( $ ) {
    88        'use strict';
     9        var component, TreeLinks;
    910
    10         var component = {
     11        component = {
    1112                l10n: {
    1213                        lintError: {
    1314                                singular: '',
    wp.themePluginEditor = (function( $ ) { 
    6667                        } );
    6768                }
    6869
     70                $( component.initFileBrowser );
     71
    6972                $( window ).on( 'beforeunload', function() {
    7073                        if ( component.dirty ) {
    7174                                return component.l10n.saveAlert;
    wp.themePluginEditor = (function( $ ) { 
    8689                        pointer: component.themeOrPlugin + '_editor_notice'
    8790                });
    8891
    89                 // hide modal
     92                // Hide modal.
    9093                component.warning.remove();
    9194                $( 'body' ).removeClass( 'modal-open' );
    9295
    93                 // return focus - is this a trap?
     96                // Return focus - is this a trap?
    9497                component.instance.codemirror.focus();
    9598        };
    9699
    wp.themePluginEditor = (function( $ ) { 
    148151                        component.removeNotice( component.lastSaveNoticeCode );
    149152                }
    150153
    151                 request.done( function ( response ) {
     154                request.done( function( response ) {
    152155                        component.lastSaveNoticeCode = 'file_saved';
    153156                        component.addNotice({
    154157                                code: component.lastSaveNoticeCode,
    wp.themePluginEditor = (function( $ ) { 
    159162                        component.dirty = false;
    160163                } );
    161164
    162                 request.fail( function ( response ) {
     165                request.fail( function( response ) {
    163166                        var notice = $.extend(
    164167                                {
    165168                                        code: 'save_error',
    wp.themePluginEditor = (function( $ ) { 
    350353                component.instance = editor;
    351354        };
    352355
     356        /**
     357         * Initialization of the file browser's folder states.
     358         *
     359         * @since 4.9.0
     360         * @returns {void}
     361         */
     362        component.initFileBrowser = function initFileBrowser() {
     363
     364                var $templateside = $( '#templateside' );
     365
     366                // Collapse all folders.
     367                $templateside.find( '[role="group"]' ).parent().attr( 'aria-expanded', false );
     368
     369                // Expand ancestors to the current file.
     370                $templateside.find( '.notice' ).parents( '[aria-expanded]' ).attr( 'aria-expanded', true );
     371
     372                // Find Tree elements and enhance them.
     373                $templateside.find( '[role="tree"]' ).each( function() {
     374                        var treeLinks = new TreeLinks( this );
     375                        treeLinks.init();
     376                } );
     377
     378                // Scroll the current file into view.
     379                $templateside.find( '.current-file' ).each( function() {
     380                        this.scrollIntoView( false );
     381                } );
     382        };
     383
     384        /* jshint ignore:start */
     385        /* jscs:disable */
     386        /* eslint-disable */
     387
     388        /**
     389         * Creates a new TreeitemLink.
     390         *
     391         * @since 4.9.0
     392         * @class
     393         * @private
     394         * @see {@link https://www.w3.org/TR/wai-aria-practices-1.1/examples/treeview/treeview-2/treeview-2b.html|W3C Treeview Example}
     395         * @license W3C-20150513
     396         */
     397        var TreeitemLink = (function () {
     398                /**
     399                 *   This content is licensed according to the W3C Software License at
     400                 *   https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
     401                 *
     402                 *   File:   TreeitemLink.js
     403                 *
     404                 *   Desc:   Treeitem widget that implements ARIA Authoring Practices
     405                 *           for a tree being used as a file viewer
     406                 *
     407                 *   Author: Jon Gunderson, Ku Ja Eun and Nicholas Hoyt
     408                 */
     409
     410                /**
     411                 *   @constructor
     412                 *
     413                 *   @desc
     414                 *       Treeitem object for representing the state and user interactions for a
     415                 *       treeItem widget
     416                 *
     417                 *   @param node
     418                 *       An element with the role=tree attribute
     419                 */
     420
     421                var TreeitemLink = function (node, treeObj, group) {
     422
     423                        // Check whether node is a DOM element
     424                        if (typeof node !== 'object') {
     425                                return;
     426                        }
     427
     428                        node.tabIndex = -1;
     429                        this.tree = treeObj;
     430                        this.groupTreeitem = group;
     431                        this.domNode = node;
     432                        this.label = node.textContent.trim();
     433                        this.stopDefaultClick = false;
     434
     435                        if (node.getAttribute('aria-label')) {
     436                                this.label = node.getAttribute('aria-label').trim();
     437                        }
     438
     439                        this.isExpandable = false;
     440                        this.isVisible = false;
     441                        this.inGroup = false;
     442
     443                        if (group) {
     444                                this.inGroup = true;
     445                        }
     446
     447                        var elem = node.firstElementChild;
     448
     449                        while (elem) {
     450
     451                                if (elem.tagName.toLowerCase() == 'ul') {
     452                                        elem.setAttribute('role', 'group');
     453                                        this.isExpandable = true;
     454                                        break;
     455                                }
     456
     457                                elem = elem.nextElementSibling;
     458                        }
     459
     460                        this.keyCode = Object.freeze({
     461                                RETURN: 13,
     462                                SPACE: 32,
     463                                PAGEUP: 33,
     464                                PAGEDOWN: 34,
     465                                END: 35,
     466                                HOME: 36,
     467                                LEFT: 37,
     468                                UP: 38,
     469                                RIGHT: 39,
     470                                DOWN: 40
     471                        });
     472                };
     473
     474                TreeitemLink.prototype.init = function () {
     475                        this.domNode.tabIndex = -1;
     476
     477                        if (!this.domNode.getAttribute('role')) {
     478                                this.domNode.setAttribute('role', 'treeitem');
     479                        }
     480
     481                        this.domNode.addEventListener('keydown', this.handleKeydown.bind(this));
     482                        this.domNode.addEventListener('click', this.handleClick.bind(this));
     483                        this.domNode.addEventListener('focus', this.handleFocus.bind(this));
     484                        this.domNode.addEventListener('blur', this.handleBlur.bind(this));
     485
     486                        if (this.isExpandable) {
     487                                this.domNode.firstElementChild.addEventListener('mouseover', this.handleMouseOver.bind(this));
     488                                this.domNode.firstElementChild.addEventListener('mouseout', this.handleMouseOut.bind(this));
     489                        }
     490                        else {
     491                                this.domNode.addEventListener('mouseover', this.handleMouseOver.bind(this));
     492                                this.domNode.addEventListener('mouseout', this.handleMouseOut.bind(this));
     493                        }
     494                };
     495
     496                TreeitemLink.prototype.isExpanded = function () {
     497
     498                        if (this.isExpandable) {
     499                                return this.domNode.getAttribute('aria-expanded') === 'true';
     500                        }
     501
     502                        return false;
     503
     504                };
     505
     506                /* EVENT HANDLERS */
     507
     508                TreeitemLink.prototype.handleKeydown = function (event) {
     509                        var tgt = event.currentTarget,
     510                                flag = false,
     511                                _char = event.key,
     512                                clickEvent;
     513
     514                        function isPrintableCharacter(str) {
     515                                return str.length === 1 && str.match(/\S/);
     516                        }
     517
     518                        function printableCharacter(item) {
     519                                if (_char == '*') {
     520                                        item.tree.expandAllSiblingItems(item);
     521                                        flag = true;
     522                                }
     523                                else {
     524                                        if (isPrintableCharacter(_char)) {
     525                                                item.tree.setFocusByFirstCharacter(item, _char);
     526                                                flag = true;
     527                                        }
     528                                }
     529                        }
     530
     531                        this.stopDefaultClick = false;
     532
     533                        if (event.altKey || event.ctrlKey || event.metaKey) {
     534                                return;
     535                        }
     536
     537                        if (event.shift) {
     538                                if (event.keyCode == this.keyCode.SPACE || event.keyCode == this.keyCode.RETURN) {
     539                                        event.stopPropagation();
     540                                        this.stopDefaultClick = true;
     541                                }
     542                                else {
     543                                        if (isPrintableCharacter(_char)) {
     544                                                printableCharacter(this);
     545                                        }
     546                                }
     547                        }
     548                        else {
     549                                switch (event.keyCode) {
     550                                        case this.keyCode.SPACE:
     551                                        case this.keyCode.RETURN:
     552                                                if (this.isExpandable) {
     553                                                        if (this.isExpanded()) {
     554                                                                this.tree.collapseTreeitem(this);
     555                                                        }
     556                                                        else {
     557                                                                this.tree.expandTreeitem(this);
     558                                                        }
     559                                                        flag = true;
     560                                                }
     561                                                else {
     562                                                        event.stopPropagation();
     563                                                        this.stopDefaultClick = true;
     564                                                }
     565                                                break;
     566
     567                                        case this.keyCode.UP:
     568                                                this.tree.setFocusToPreviousItem(this);
     569                                                flag = true;
     570                                                break;
     571
     572                                        case this.keyCode.DOWN:
     573                                                this.tree.setFocusToNextItem(this);
     574                                                flag = true;
     575                                                break;
     576
     577                                        case this.keyCode.RIGHT:
     578                                                if (this.isExpandable) {
     579                                                        if (this.isExpanded()) {
     580                                                                this.tree.setFocusToNextItem(this);
     581                                                        }
     582                                                        else {
     583                                                                this.tree.expandTreeitem(this);
     584                                                        }
     585                                                }
     586                                                flag = true;
     587                                                break;
     588
     589                                        case this.keyCode.LEFT:
     590                                                if (this.isExpandable && this.isExpanded()) {
     591                                                        this.tree.collapseTreeitem(this);
     592                                                        flag = true;
     593                                                }
     594                                                else {
     595                                                        if (this.inGroup) {
     596                                                                this.tree.setFocusToParentItem(this);
     597                                                                flag = true;
     598                                                        }
     599                                                }
     600                                                break;
     601
     602                                        case this.keyCode.HOME:
     603                                                this.tree.setFocusToFirstItem();
     604                                                flag = true;
     605                                                break;
     606
     607                                        case this.keyCode.END:
     608                                                this.tree.setFocusToLastItem();
     609                                                flag = true;
     610                                                break;
     611
     612                                        default:
     613                                                if (isPrintableCharacter(_char)) {
     614                                                        printableCharacter(this);
     615                                                }
     616                                                break;
     617                                }
     618                        }
     619
     620                        if (flag) {
     621                                event.stopPropagation();
     622                                event.preventDefault();
     623                        }
     624                };
     625
     626                TreeitemLink.prototype.handleClick = function (event) {
     627
     628                        // only process click events that directly happened on this treeitem
     629                        if (event.target !== this.domNode && event.target !== this.domNode.firstElementChild) {
     630                                return;
     631                        }
     632
     633                        if (this.isExpandable) {
     634                                if (this.isExpanded()) {
     635                                        this.tree.collapseTreeitem(this);
     636                                }
     637                                else {
     638                                        this.tree.expandTreeitem(this);
     639                                }
     640                                event.stopPropagation();
     641                        }
     642                };
     643
     644                TreeitemLink.prototype.handleFocus = function (event) {
     645                        var node = this.domNode;
     646                        if (this.isExpandable) {
     647                                node = node.firstElementChild;
     648                        }
     649                        node.classList.add('focus');
     650                };
     651
     652                TreeitemLink.prototype.handleBlur = function (event) {
     653                        var node = this.domNode;
     654                        if (this.isExpandable) {
     655                                node = node.firstElementChild;
     656                        }
     657                        node.classList.remove('focus');
     658                };
     659
     660                TreeitemLink.prototype.handleMouseOver = function (event) {
     661                        event.currentTarget.classList.add('hover');
     662                };
     663
     664                TreeitemLink.prototype.handleMouseOut = function (event) {
     665                        event.currentTarget.classList.remove('hover');
     666                };
     667
     668                return TreeitemLink;
     669        })();
     670
     671        /**
     672         * Creates a new TreeLinks.
     673         *
     674         * @since 4.9.0
     675         * @class
     676         * @private
     677         * @see {@link https://www.w3.org/TR/wai-aria-practices-1.1/examples/treeview/treeview-2/treeview-2b.html|W3C Treeview Example}
     678         * @license W3C-20150513
     679         */
     680        TreeLinks = (function () {
     681                /*
     682                 *   This content is licensed according to the W3C Software License at
     683                 *   https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
     684                 *
     685                 *   File:   TreeLinks.js
     686                 *
     687                 *   Desc:   Tree widget that implements ARIA Authoring Practices
     688                 *           for a tree being used as a file viewer
     689                 *
     690                 *   Author: Jon Gunderson, Ku Ja Eun and Nicholas Hoyt
     691                 */
     692
     693                /*
     694                 *   @constructor
     695                 *
     696                 *   @desc
     697                 *       Tree item object for representing the state and user interactions for a
     698                 *       tree widget
     699                 *
     700                 *   @param node
     701                 *       An element with the role=tree attribute
     702                 */
     703
     704                var TreeLinks = function (node) {
     705                        // Check whether node is a DOM element
     706                        if (typeof node !== 'object') {
     707                                return;
     708                        }
     709
     710                        this.domNode = node;
     711
     712                        this.treeitems = [];
     713                        this.firstChars = [];
     714
     715                        this.firstTreeitem = null;
     716                        this.lastTreeitem = null;
     717
     718                };
     719
     720                TreeLinks.prototype.init = function () {
     721
     722                        function findTreeitems(node, tree, group) {
     723
     724                                var elem = node.firstElementChild;
     725                                var ti = group;
     726
     727                                while (elem) {
     728
     729                                        if ((elem.tagName.toLowerCase() === 'li' && elem.firstElementChild.tagName.toLowerCase() === 'span') || elem.tagName.toLowerCase() === 'a') {
     730                                                ti = new TreeitemLink(elem, tree, group);
     731                                                ti.init();
     732                                                tree.treeitems.push(ti);
     733                                                tree.firstChars.push(ti.label.substring(0, 1).toLowerCase());
     734                                        }
     735
     736                                        if (elem.firstElementChild) {
     737                                                findTreeitems(elem, tree, ti);
     738                                        }
     739
     740                                        elem = elem.nextElementSibling;
     741                                }
     742                        }
     743
     744                        // initialize pop up menus
     745                        if (!this.domNode.getAttribute('role')) {
     746                                this.domNode.setAttribute('role', 'tree');
     747                        }
     748
     749                        findTreeitems(this.domNode, this, false);
     750
     751                        this.updateVisibleTreeitems();
     752
     753                        this.firstTreeitem.domNode.tabIndex = 0;
     754
     755                };
     756
     757                TreeLinks.prototype.setFocusToItem = function (treeitem) {
     758
     759                        for (var i = 0; i < this.treeitems.length; i++) {
     760                                var ti = this.treeitems[i];
     761
     762                                if (ti === treeitem) {
     763                                        ti.domNode.tabIndex = 0;
     764                                        ti.domNode.focus();
     765                                }
     766                                else {
     767                                        ti.domNode.tabIndex = -1;
     768                                }
     769                        }
     770
     771                };
     772
     773                TreeLinks.prototype.setFocusToNextItem = function (currentItem) {
     774
     775                        var nextItem = false;
     776
     777                        for (var i = (this.treeitems.length - 1); i >= 0; i--) {
     778                                var ti = this.treeitems[i];
     779                                if (ti === currentItem) {
     780                                        break;
     781                                }
     782                                if (ti.isVisible) {
     783                                        nextItem = ti;
     784                                }
     785                        }
     786
     787                        if (nextItem) {
     788                                this.setFocusToItem(nextItem);
     789                        }
     790
     791                };
     792
     793                TreeLinks.prototype.setFocusToPreviousItem = function (currentItem) {
     794
     795                        var prevItem = false;
     796
     797                        for (var i = 0; i < this.treeitems.length; i++) {
     798                                var ti = this.treeitems[i];
     799                                if (ti === currentItem) {
     800                                        break;
     801                                }
     802                                if (ti.isVisible) {
     803                                        prevItem = ti;
     804                                }
     805                        }
     806
     807                        if (prevItem) {
     808                                this.setFocusToItem(prevItem);
     809                        }
     810                };
     811
     812                TreeLinks.prototype.setFocusToParentItem = function (currentItem) {
     813
     814                        if (currentItem.groupTreeitem) {
     815                                this.setFocusToItem(currentItem.groupTreeitem);
     816                        }
     817                };
     818
     819                TreeLinks.prototype.setFocusToFirstItem = function () {
     820                        this.setFocusToItem(this.firstTreeitem);
     821                };
     822
     823                TreeLinks.prototype.setFocusToLastItem = function () {
     824                        this.setFocusToItem(this.lastTreeitem);
     825                };
     826
     827                TreeLinks.prototype.expandTreeitem = function (currentItem) {
     828
     829                        if (currentItem.isExpandable) {
     830                                currentItem.domNode.setAttribute('aria-expanded', true);
     831                                this.updateVisibleTreeitems();
     832                        }
     833
     834                };
     835
     836                TreeLinks.prototype.expandAllSiblingItems = function (currentItem) {
     837                        for (var i = 0; i < this.treeitems.length; i++) {
     838                                var ti = this.treeitems[i];
     839
     840                                if ((ti.groupTreeitem === currentItem.groupTreeitem) && ti.isExpandable) {
     841                                        this.expandTreeitem(ti);
     842                                }
     843                        }
     844
     845                };
     846
     847                TreeLinks.prototype.collapseTreeitem = function (currentItem) {
     848
     849                        var groupTreeitem = false;
     850
     851                        if (currentItem.isExpanded()) {
     852                                groupTreeitem = currentItem;
     853                        }
     854                        else {
     855                                groupTreeitem = currentItem.groupTreeitem;
     856                        }
     857
     858                        if (groupTreeitem) {
     859                                groupTreeitem.domNode.setAttribute('aria-expanded', false);
     860                                this.updateVisibleTreeitems();
     861                                this.setFocusToItem(groupTreeitem);
     862                        }
     863
     864                };
     865
     866                TreeLinks.prototype.updateVisibleTreeitems = function () {
     867
     868                        this.firstTreeitem = this.treeitems[0];
     869
     870                        for (var i = 0; i < this.treeitems.length; i++) {
     871                                var ti = this.treeitems[i];
     872
     873                                var parent = ti.domNode.parentNode;
     874
     875                                ti.isVisible = true;
     876
     877                                while (parent && (parent !== this.domNode)) {
     878
     879                                        if (parent.getAttribute('aria-expanded') == 'false') {
     880                                                ti.isVisible = false;
     881                                        }
     882                                        parent = parent.parentNode;
     883                                }
     884
     885                                if (ti.isVisible) {
     886                                        this.lastTreeitem = ti;
     887                                }
     888                        }
     889
     890                };
     891
     892                TreeLinks.prototype.setFocusByFirstCharacter = function (currentItem, _char) {
     893                        var start, index;
     894                        _char = _char.toLowerCase();
     895
     896                        // Get start index for search based on position of currentItem
     897                        start = this.treeitems.indexOf(currentItem) + 1;
     898                        if (start === this.treeitems.length) {
     899                                start = 0;
     900                        }
     901
     902                        // Check remaining slots in the menu
     903                        index = this.getIndexFirstChars(start, _char);
     904
     905                        // If not found in remaining slots, check from beginning
     906                        if (index === -1) {
     907                                index = this.getIndexFirstChars(0, _char);
     908                        }
     909
     910                        // If match was found...
     911                        if (index > -1) {
     912                                this.setFocusToItem(this.treeitems[index]);
     913                        }
     914                };
     915
     916                TreeLinks.prototype.getIndexFirstChars = function (startIndex, _char) {
     917                        for (var i = startIndex; i < this.firstChars.length; i++) {
     918                                if (this.treeitems[i].isVisible) {
     919                                        if (_char === this.firstChars[i]) {
     920                                                return i;
     921                                        }
     922                                }
     923                        }
     924                        return -1;
     925                };
     926
     927                return TreeLinks;
     928        })();
     929
     930        /* jshint ignore:end */
     931        /* jscs:enable */
     932        /* eslint-enable */
     933
    353934        return component;
    354935})( jQuery );
  • src/wp-admin/plugin-editor.php

    diff --git src/wp-admin/plugin-editor.php src/wp-admin/plugin-editor.php
    index 807cd5e09d..200ebe8f5d 100644
    if ( 'POST' === $_SERVER['REQUEST_METHOD'] ) { 
    231231</div>
    232232
    233233<div id="templateside">
    234         <h2><?php _e( 'Plugin Files' ); ?></h2>
     234        <h2 id="plugin-files-label"><?php _e( 'Plugin Files' ); ?></h2>
    235235
    236236        <?php
    237237        $plugin_editable_files = array();
    if ( 'POST' === $_SERVER['REQUEST_METHOD'] ) { 
    241241                }
    242242        }
    243243        ?>
    244         <ul>
    245         <?php foreach ( $plugin_editable_files as $plugin_file ) : ?>
    246                 <li class="<?php echo esc_attr( $file === $plugin_file ? 'notice notice-info' : '' ); ?>">
    247                         <a href="plugin-editor.php?file=<?php echo urlencode( $plugin_file ); ?>&amp;plugin=<?php echo urlencode( $plugin ); ?>"><?php echo esc_html( preg_replace( '#^.+?/#', '', $plugin_file ) ); ?></a>
    248                 </li>
    249         <?php endforeach; ?>
     244        <ul role="tree" aria-labelledby="plugin-files-label">
     245        <li role="treeitem" tabindex="-1" aria-expanded="true"
     246                aria-level="1"
     247                aria-posinset="1"
     248                aria-setsize="1">
     249                <ul role="group" style="padding-left: 0;">
     250                        <?php wp_print_plugin_file_tree( wp_make_plugin_file_tree( $plugin_editable_files ) ); ?>
     251                </ul>
    250252        </ul>
    251253</div>
    252254<form name="template" id="template" action="plugin-editor.php" method="post">
  • src/wp-admin/theme-editor.php

    diff --git src/wp-admin/theme-editor.php src/wp-admin/theme-editor.php
    index 70629d914d..e275af1fda 100644
    foreach ( $file_types as $type ) { 
    8989        }
    9090}
    9191
     92// Move functions.php and style.css to the top.
     93if ( isset( $allowed_files['functions.php'] ) ) {
     94        $allowed_files = array( 'functions.php' => $allowed_files['functions.php'] ) + $allowed_files;
     95}
     96if ( isset( $allowed_files['style.css'] ) ) {
     97        $allowed_files = array( 'style.css' => $allowed_files['style.css'] ) + $allowed_files;
     98}
     99
    92100if ( empty( $file ) ) {
    93101        $relative_file = 'style.css';
    94102        $file = $allowed_files['style.css'];
    foreach ( wp_get_themes( array( 'errors' => null ) ) as $a_stylesheet => $a_them 
    205213if ( $theme->errors() )
    206214        echo '<div class="error"><p><strong>' . __( 'This theme is broken.' ) . '</strong> ' . $theme->errors()->get_error_message() . '</p></div>';
    207215?>
    208         <div id="templateside">
    209 <?php
    210 if ( $allowed_files ) :
    211         $previous_file_type = '';
    212 
    213         foreach ( $allowed_files as $filename => $absolute_filename ) :
    214                 $file_type = substr( $filename, strrpos( $filename, '.' ) );
    215 
    216                 if ( $file_type !== $previous_file_type ) {
    217                         if ( '' !== $previous_file_type ) {
    218                                 echo "\t</ul>\n";
    219                         }
    220 
    221                         switch ( $file_type ) {
    222                                 case '.php':
    223                                         if ( $has_templates || $theme->parent() ) :
    224                                                 echo "\t<h2>" . __( 'Templates' ) . "</h2>\n";
    225                                                 if ( $theme->parent() ) {
    226                                                         echo '<p class="howto">' . sprintf( __( 'This child theme inherits templates from a parent theme, %s.' ),
    227                                                                 sprintf( '<a href="%s">%s</a>',
    228                                                                         self_admin_url( 'theme-editor.php?theme=' . urlencode( $theme->get_template() ) ),
    229                                                                         $theme->parent()->display( 'Name' )
    230                                                                 )
    231                                                         ) . "</p>\n";
    232                                                 }
    233                                         endif;
    234                                         break;
    235                                 case '.css':
    236                                         echo "\t<h2>" . _x( 'Styles', 'Theme stylesheets in theme editor' ) . "</h2>\n";
    237                                         break;
    238                                 default:
    239                                         /* translators: %s: file extension */
    240                                         echo "\t<h2>" . sprintf( __( '%s files' ), $file_type ) . "</h2>\n";
    241                                         break;
    242                         }
    243 
    244                         echo "\t<ul>\n";
     216<div id="templateside">
     217        <h2 id="theme-files-label"><?php _e( 'Theme Files' ); ?></h2>
     218        <?php
     219        if ( $has_templates || $theme->parent() ) :
     220                if ( $theme->parent() ) {
     221                        /* translators: %s: link to edit parent theme */
     222                        echo '<p class="howto">' . sprintf( __( 'This child theme inherits templates from a parent theme, %s.' ),
     223                                sprintf( '<a href="%s">%s</a>',
     224                                        self_admin_url( 'theme-editor.php?theme=' . urlencode( $theme->get_template() ) ),
     225                                        $theme->parent()->display( 'Name' )
     226                                )
     227                        ) . "</p>\n";
    245228                }
    246 
    247                 $file_description = esc_html( get_file_description( $filename ) );
    248                 if ( $filename !== basename( $absolute_filename ) || $file_description !== $filename ) {
    249                         $file_description .= '<br /><span class="nonessential">(' . esc_html( $filename ) . ')</span>';
    250                 }
    251 
    252                 if ( $absolute_filename === $file ) {
    253                         $file_description = '<span class="notice notice-info">' . $file_description . '</span>';
    254                 }
    255 
    256                 $previous_file_type = $file_type;
    257 ?>
    258                 <li><a href="theme-editor.php?file=<?php echo urlencode( $filename ) ?>&amp;theme=<?php echo urlencode( $stylesheet ) ?>"><?php echo $file_description; ?></a></li>
    259 <?php
    260         endforeach;
    261 ?>
    262 </ul>
    263 <?php endif; ?>
     229        endif;
     230        ?>
     231        <ul role="tree" aria-labelledby="theme-files-label">
     232                <li role="treeitem" tabindex="-1" aria-expanded="true"
     233                        aria-level="1"
     234                        aria-posinset="1"
     235                        aria-setsize="1">
     236                        <ul role="group" style="padding-left: 0;">
     237                                <?php wp_print_theme_file_tree( wp_make_theme_file_tree( $allowed_files ) ); ?>
     238                        </ul>
     239                </li>
     240        </ul>
    264241</div>
     242
    265243<?php if ( $error ) :
    266244        echo '<div class="error"><p>' . __('Oops, no such file exists! Double check the name and try again, merci.') . '</p></div>';
    267245else : ?>