WordPress.org

Make WordPress Core

Ticket #24048: 24048.10.diff

File 24048.10.diff, 29.8 KB (added by westonruter, 3 months ago)

Δ https://github.com/xwp/wordpress-develop/pull/281/files/39b8c83..443f28c

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