WordPress.org

Make WordPress Core

Ticket #24048: 24048.10.diff

File 24048.10.diff, 29.8 KB (added by westonruter, 7 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 : ?>