Make WordPress Core

Ticket #24048: 24048.8.diff

File 24048.8.diff, 30.7 KB (added by westonruter, 7 years ago)

Re-add tree JS omitted from patch

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

    diff --git src/wp-admin/css/common.css src/wp-admin/css/common.css
    index 3a11a58e30..8bc9f1d883 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..755d481cac 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  $tree  List of file/folder paths.
     301 * @param string $label Name of file or folder to print.
     302 * @param int    $level Current level.
     303 * @param int    $index Current level.
     304 * @param int    $size  Current level.
     305 */
     306function wp_print_theme_file_tree( $tree, $label = false, $level = 2, $index = 1, $size = 1 ) {
     307        global $relative_file, $stylesheet;
     308
     309        if ( is_array( $tree ) ) {
     310                $index = 0;
     311                $size = count( $tree );
     312                foreach ( $tree as $label => $theme_file ) :
     313                        $index++;
     314                        if ( ! is_array( $theme_file ) ) {
     315                                wp_print_theme_file_tree( $theme_file, $label, $level, $index, $size );
     316                                continue;
     317                        }
     318                        ?>
     319                        <li role="treeitem" aria-expanded="true" tabindex="-1"
     320                                aria-level="<?php echo esc_attr( $level ); ?>"
     321                                aria-setsize="<?php echo esc_attr( $size ); ?>"
     322                                aria-posinset="<?php echo esc_attr( $index ); ?>">
     323                                <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>
     324                                <ul role="group" class="tree-folder"><?php wp_print_theme_file_tree( $theme_file, false, $level + 1, $index, $size ); ?></ul>
     325                        </li>
     326                        <?php
     327                endforeach;
     328        } else {
     329                $filename = $tree;
     330                $url = add_query_arg(
     331                        array(
     332                                'file' => rawurlencode( $tree ),
     333                                'theme' => rawurlencode( $stylesheet ),
     334                        ),
     335                        admin_url( 'theme-editor.php' )
     336                );
     337                ?>
     338                <li role="none" class="<?php echo esc_attr( $relative_file === $filename ? 'current-file' : '' ); ?>">
     339                        <a role="treeitem" tabindex="<?php echo esc_attr( $relative_file === $filename ? '0' : '-1' ); ?>"
     340                                href="<?php echo esc_url( $url ); ?>"
     341                                aria-level="<?php echo esc_attr( $level ); ?>"
     342                                aria-setsize="<?php echo esc_attr( $size ); ?>"
     343                                aria-posinset="<?php echo esc_attr( $index ); ?>">
     344                                <?php
     345                                $file_description = esc_html( get_file_description( $filename ) );
     346                                if ( $file_description !== $filename && basename( $filename ) !== $file_description ) {
     347                                        $file_description .= '<br /><span class="nonessential">(' . esc_html( $filename ) . ')</span>';
     348                                }
     349
     350                                if ( $relative_file === $filename ) {
     351                                        echo '<span class="notice notice-info">' . $file_description . '</span>';
     352                                } else {
     353                                        echo $file_description;
     354                                }
     355                                ?>
     356                        </a>
     357                </li>
     358                <?php
     359        }
     360}
     361
     362/**
     363 * Makes a tree structure for the Plugin Editor's file list.
     364 *
     365 * @since 4.9.0
     366 * @access private
     367 *
     368 * @param string $plugin_editable_files List of plugin file paths.
     369 *
     370 * @return array Tree structure for listing plugin files.
     371 */
     372function wp_make_plugin_file_tree( $plugin_editable_files ) {
     373        $tree_list = array();
     374        foreach ( $plugin_editable_files as $plugin_file ) {
     375                $list = explode( '/', preg_replace( '#^.+?/#', '', $plugin_file ) );
     376                $last_dir = &$tree_list;
     377                foreach ( $list as $dir ) {
     378                        $last_dir =& $last_dir[ $dir ];
     379                }
     380                $last_dir = $plugin_file;
     381        }
     382        return $tree_list;
     383}
     384
     385/**
     386 * Outputs the formatted file list for the Plugin Editor.
     387 *
     388 * @since 4.9.0
     389 * @access private
     390 *
     391 * @param array  $tree  List of file/folder paths.
     392 * @param string $label Name of file or folder to print.
     393 * @param int    $level Current level.
     394 * @param int    $index Current level.
     395 * @param int    $size  Current level.
     396 */
     397function wp_print_plugin_file_tree( $tree, $label = false, $level = 2, $index = 1, $size = 1 ) {
     398        global $file, $plugin;
     399        if ( is_array( $tree ) ) {
     400                $index = 0;
     401                $size = count( $tree );
     402                foreach ( $tree as $label => $plugin_file ) :
     403                        $index++;
     404                        if ( ! is_array( $plugin_file ) ) {
     405                                wp_print_plugin_file_tree( $plugin_file, $label, $level, $index, $size );
     406                                continue;
     407                        }
     408                        ?>
     409                        <li role="treeitem" aria-expanded="true" tabindex="-1"
     410                                aria-level="<?php echo esc_attr( $level ); ?>"
     411                                aria-setsize="<?php echo esc_attr( $size ); ?>"
     412                                aria-posinset="<?php echo esc_attr( $index ); ?>">
     413                                <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>
     414                                <ul role="group" class="tree-folder"><?php wp_print_plugin_file_tree( $plugin_file, false, $level + 1, $index, $size ); ?></ul>
     415                        </li>
     416                        <?php
     417                endforeach;
     418        } else {
     419                $url = add_query_arg(
     420                        array(
     421                                'file' => rawurlencode( $tree ),
     422                                'plugin' => rawurlencode( $plugin ),
     423                        ),
     424                        admin_url( 'plugin-editor.php' )
     425                );
     426                ?>
     427                <li role="none" class="<?php echo esc_attr( $file === $tree ? 'current-file' : '' ); ?>">
     428                        <a role="treeitem" tabindex="<?php echo esc_attr( $file === $tree ? '0' : '-1' ); ?>"
     429                                href="<?php echo esc_url( $url ); ?>"
     430                                aria-level="<?php echo esc_attr( $level ); ?>"
     431                                aria-setsize="<?php echo esc_attr( $size ); ?>"
     432                                aria-posinset="<?php echo esc_attr( $index ); ?>">
     433                                <?php
     434                                if ( $file === $tree ) {
     435                                        echo '<span class="notice notice-info">' . esc_html( $label ) . '</span>';
     436                                } else {
     437                                        echo esc_html( $label );
     438                                }
     439                                ?>
     440                        </a>
     441                </li>
     442                <?php
     443        }
     444}
     445
    272446/**
    273447 * Flushes rewrite rules if siteurl, home or page_on_front changed.
    274448 *
  • new file src/wp-admin/js/tree-links.js

    diff --git src/wp-admin/js/tree-links.js src/wp-admin/js/tree-links.js
    new file mode 100644
    index 0000000000..ff9ce2359a
    - +  
     1/*
     2*   This content is licensed according to the W3C Software License at
     3*   https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
     4*
     5*   File:   TreeLinks.js
     6*
     7*   Desc:   Tree widget that implements ARIA Authoring Practices
     8*           for a tree being used as a file viewer
     9*
     10*   Author: Jon Gunderson, Ku Ja Eun and Nicholas Hoyt
     11*/
     12
     13/**
     14 * ARIA Treeview example
     15 * @function onload
     16 * @desc  after page has loaded initializ all treeitems based on the role=treeitem
     17 */
     18
     19window.addEventListener('load', function () {
     20
     21    var trees = document.querySelectorAll('[role="tree"]');
     22
     23    for (var i = 0; i < trees.length; i++) {
     24        var t = new TreeLinks(trees[i]);
     25        t.init();
     26    }
     27
     28});
     29
     30/*
     31*   @constructor
     32*
     33*   @desc
     34*       Tree item object for representing the state and user interactions for a
     35*       tree widget
     36*
     37*   @param node
     38*       An element with the role=tree attribute
     39*/
     40
     41var TreeLinks = function (node) {
     42    // Check whether node is a DOM element
     43    if (typeof node !== 'object') {
     44        return;
     45    }
     46
     47    this.domNode = node;
     48
     49    this.treeitems = [];
     50    this.firstChars = [];
     51
     52    this.firstTreeitem = null;
     53    this.lastTreeitem = null;
     54
     55};
     56
     57TreeLinks.prototype.init = function () {
     58
     59    function findTreeitems(node, tree, group) {
     60
     61        var elem = node.firstElementChild;
     62        var ti = group;
     63
     64        while (elem) {
     65
     66            if ((elem.tagName.toLowerCase() === 'li' && elem.firstElementChild.tagName.toLowerCase() === 'span') || elem.tagName.toLowerCase() === 'a') {
     67                ti = new TreeitemLink(elem, tree, group);
     68                ti.init();
     69                tree.treeitems.push(ti);
     70                tree.firstChars.push(ti.label.substring(0, 1).toLowerCase());
     71            }
     72
     73            if (elem.firstElementChild) {
     74                findTreeitems(elem, tree, ti);
     75            }
     76
     77            elem = elem.nextElementSibling;
     78        }
     79    }
     80
     81    // initialize pop up menus
     82    if (!this.domNode.getAttribute('role')) {
     83        this.domNode.setAttribute('role', 'tree');
     84    }
     85
     86    findTreeitems(this.domNode, this, false);
     87
     88    this.updateVisibleTreeitems();
     89
     90    this.firstTreeitem.domNode.tabIndex = 0;
     91
     92};
     93
     94TreeLinks.prototype.setFocusToItem = function (treeitem) {
     95
     96    for (var i = 0; i < this.treeitems.length; i++) {
     97        var ti = this.treeitems[i];
     98
     99        if (ti === treeitem) {
     100            ti.domNode.tabIndex = 0;
     101            ti.domNode.focus();
     102        }
     103        else {
     104            ti.domNode.tabIndex = -1;
     105        }
     106    }
     107
     108};
     109
     110TreeLinks.prototype.setFocusToNextItem = function (currentItem) {
     111
     112    var nextItem = false;
     113
     114    for (var i = (this.treeitems.length - 1); i >= 0; i--) {
     115        var ti = this.treeitems[i];
     116        if (ti === currentItem) {
     117            break;
     118        }
     119        if (ti.isVisible) {
     120            nextItem = ti;
     121        }
     122    }
     123
     124    if (nextItem) {
     125        this.setFocusToItem(nextItem);
     126    }
     127
     128};
     129
     130TreeLinks.prototype.setFocusToPreviousItem = function (currentItem) {
     131
     132    var prevItem = false;
     133
     134    for (var i = 0; i < this.treeitems.length; i++) {
     135        var ti = this.treeitems[i];
     136        if (ti === currentItem) {
     137            break;
     138        }
     139        if (ti.isVisible) {
     140            prevItem = ti;
     141        }
     142    }
     143
     144    if (prevItem) {
     145        this.setFocusToItem(prevItem);
     146    }
     147};
     148
     149TreeLinks.prototype.setFocusToParentItem = function (currentItem) {
     150
     151    if (currentItem.groupTreeitem) {
     152        this.setFocusToItem(currentItem.groupTreeitem);
     153    }
     154};
     155
     156TreeLinks.prototype.setFocusToFirstItem = function () {
     157    this.setFocusToItem(this.firstTreeitem);
     158};
     159
     160TreeLinks.prototype.setFocusToLastItem = function () {
     161    this.setFocusToItem(this.lastTreeitem);
     162};
     163
     164TreeLinks.prototype.expandTreeitem = function (currentItem) {
     165
     166    if (currentItem.isExpandable) {
     167        currentItem.domNode.setAttribute('aria-expanded', true);
     168        this.updateVisibleTreeitems();
     169    }
     170
     171};
     172
     173TreeLinks.prototype.expandAllSiblingItems = function (currentItem) {
     174    for (var i = 0; i < this.treeitems.length; i++) {
     175        var ti = this.treeitems[i];
     176
     177        if ((ti.groupTreeitem === currentItem.groupTreeitem) && ti.isExpandable) {
     178            this.expandTreeitem(ti);
     179        }
     180    }
     181
     182};
     183
     184TreeLinks.prototype.collapseTreeitem = function (currentItem) {
     185
     186    var groupTreeitem = false;
     187
     188    if (currentItem.isExpanded()) {
     189        groupTreeitem = currentItem;
     190    }
     191    else {
     192        groupTreeitem = currentItem.groupTreeitem;
     193    }
     194
     195    if (groupTreeitem) {
     196        groupTreeitem.domNode.setAttribute('aria-expanded', false);
     197        this.updateVisibleTreeitems();
     198        this.setFocusToItem(groupTreeitem);
     199    }
     200
     201};
     202
     203TreeLinks.prototype.updateVisibleTreeitems = function () {
     204
     205    this.firstTreeitem = this.treeitems[0];
     206
     207    for (var i = 0; i < this.treeitems.length; i++) {
     208        var ti = this.treeitems[i];
     209
     210        var parent = ti.domNode.parentNode;
     211
     212        ti.isVisible = true;
     213
     214        while (parent && (parent !== this.domNode)) {
     215
     216            if (parent.getAttribute('aria-expanded') == 'false') {
     217                ti.isVisible = false;
     218            }
     219            parent = parent.parentNode;
     220        }
     221
     222        if (ti.isVisible) {
     223            this.lastTreeitem = ti;
     224        }
     225    }
     226
     227};
     228
     229TreeLinks.prototype.setFocusByFirstCharacter = function (currentItem, char) {
     230    var start, index, char = char.toLowerCase();
     231
     232    // Get start index for search based on position of currentItem
     233    start = this.treeitems.indexOf(currentItem) + 1;
     234    if (start === this.treeitems.length) {
     235        start = 0;
     236    }
     237
     238    // Check remaining slots in the menu
     239    index = this.getIndexFirstChars(start, char);
     240
     241    // If not found in remaining slots, check from beginning
     242    if (index === -1) {
     243        index = this.getIndexFirstChars(0, char);
     244    }
     245
     246    // If match was found...
     247    if (index > -1) {
     248        this.setFocusToItem(this.treeitems[index]);
     249    }
     250};
     251
     252TreeLinks.prototype.getIndexFirstChars = function (startIndex, char) {
     253    for (var i = startIndex; i < this.firstChars.length; i++) {
     254        if (this.treeitems[i].isVisible) {
     255            if (char === this.firstChars[i]) {
     256                return i;
     257            }
     258        }
     259    }
     260    return -1;
     261};
     262 No newline at end of file
  • new file src/wp-admin/js/treeitem-links.js

    diff --git src/wp-admin/js/treeitem-links.js src/wp-admin/js/treeitem-links.js
    new file mode 100644
    index 0000000000..2d0831cde0
    - +  
     1/*
     2*   This content is licensed according to the W3C Software License at
     3*   https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
     4*
     5*   File:   TreeitemLink.js
     6*
     7*   Desc:   Treeitem widget that implements ARIA Authoring Practices
     8*           for a tree being used as a file viewer
     9*
     10*   Author: Jon Gunderson, Ku Ja Eun and Nicholas Hoyt
     11*/
     12
     13/*
     14*   @constructor
     15*
     16*   @desc
     17*       Treeitem object for representing the state and user interactions for a
     18*       treeItem widget
     19*
     20*   @param node
     21*       An element with the role=tree attribute
     22*/
     23
     24var TreeitemLink = function (node, treeObj, group) {
     25
     26    // Check whether node is a DOM element
     27    if (typeof node !== 'object') {
     28        return;
     29    }
     30
     31    node.tabIndex = -1;
     32    this.tree = treeObj;
     33    this.groupTreeitem = group;
     34    this.domNode = node;
     35    this.label = node.textContent.trim();
     36    this.stopDefaultClick = false;
     37
     38    if (node.getAttribute('aria-label')) {
     39        this.label = node.getAttribute('aria-label').trim();
     40    }
     41
     42    this.isExpandable = false;
     43    this.isVisible = false;
     44    this.inGroup = false;
     45
     46    if (group) {
     47        this.inGroup = true;
     48    }
     49
     50    var elem = node.firstElementChild;
     51
     52    while (elem) {
     53
     54        if (elem.tagName.toLowerCase() == 'ul') {
     55            elem.setAttribute('role', 'group');
     56            this.isExpandable = true;
     57            break;
     58        }
     59
     60        elem = elem.nextElementSibling;
     61    }
     62
     63    this.keyCode = Object.freeze({
     64        RETURN: 13,
     65        SPACE: 32,
     66        PAGEUP: 33,
     67        PAGEDOWN: 34,
     68        END: 35,
     69        HOME: 36,
     70        LEFT: 37,
     71        UP: 38,
     72        RIGHT: 39,
     73        DOWN: 40
     74    });
     75};
     76
     77TreeitemLink.prototype.init = function () {
     78    this.domNode.tabIndex = -1;
     79
     80    if (!this.domNode.getAttribute('role')) {
     81        this.domNode.setAttribute('role', 'treeitem');
     82    }
     83
     84    this.domNode.addEventListener('keydown', this.handleKeydown.bind(this));
     85    this.domNode.addEventListener('click', this.handleClick.bind(this));
     86    this.domNode.addEventListener('focus', this.handleFocus.bind(this));
     87    this.domNode.addEventListener('blur', this.handleBlur.bind(this));
     88
     89    if (this.isExpandable) {
     90        this.domNode.firstElementChild.addEventListener('mouseover', this.handleMouseOver.bind(this));
     91        this.domNode.firstElementChild.addEventListener('mouseout', this.handleMouseOut.bind(this));
     92    }
     93    else {
     94        this.domNode.addEventListener('mouseover', this.handleMouseOver.bind(this));
     95        this.domNode.addEventListener('mouseout', this.handleMouseOut.bind(this));
     96    }
     97};
     98
     99TreeitemLink.prototype.isExpanded = function () {
     100
     101    if (this.isExpandable) {
     102        return this.domNode.getAttribute('aria-expanded') === 'true';
     103    }
     104
     105    return false;
     106
     107};
     108
     109/* EVENT HANDLERS */
     110
     111TreeitemLink.prototype.handleKeydown = function (event) {
     112    var tgt = event.currentTarget,
     113        flag = false,
     114        char = event.key,
     115        clickEvent;
     116
     117    function isPrintableCharacter(str) {
     118        return str.length === 1 && str.match(/\S/);
     119    }
     120
     121    function printableCharacter(item) {
     122        if (char == '*') {
     123            item.tree.expandAllSiblingItems(item);
     124            flag = true;
     125        }
     126        else {
     127            if (isPrintableCharacter(char)) {
     128                item.tree.setFocusByFirstCharacter(item, char);
     129                flag = true;
     130            }
     131        }
     132    }
     133
     134    this.stopDefaultClick = false;
     135
     136    if (event.altKey || event.ctrlKey || event.metaKey) {
     137        return;
     138    }
     139
     140    if (event.shift) {
     141        if (event.keyCode == this.keyCode.SPACE || event.keyCode == this.keyCode.RETURN) {
     142            event.stopPropagation();
     143            this.stopDefaultClick = true;
     144        }
     145        else {
     146            if (isPrintableCharacter(char)) {
     147                printableCharacter(this);
     148            }
     149        }
     150    }
     151    else {
     152        switch (event.keyCode) {
     153            case this.keyCode.SPACE:
     154            case this.keyCode.RETURN:
     155                if (this.isExpandable) {
     156                    if (this.isExpanded()) {
     157                        this.tree.collapseTreeitem(this);
     158                    }
     159                    else {
     160                        this.tree.expandTreeitem(this);
     161                    }
     162                    flag = true;
     163                }
     164                else {
     165                    event.stopPropagation();
     166                    this.stopDefaultClick = true;
     167                }
     168                break;
     169
     170            case this.keyCode.UP:
     171                this.tree.setFocusToPreviousItem(this);
     172                flag = true;
     173                break;
     174
     175            case this.keyCode.DOWN:
     176                this.tree.setFocusToNextItem(this);
     177                flag = true;
     178                break;
     179
     180            case this.keyCode.RIGHT:
     181                if (this.isExpandable) {
     182                    if (this.isExpanded()) {
     183                        this.tree.setFocusToNextItem(this);
     184                    }
     185                    else {
     186                        this.tree.expandTreeitem(this);
     187                    }
     188                }
     189                flag = true;
     190                break;
     191
     192            case this.keyCode.LEFT:
     193                if (this.isExpandable && this.isExpanded()) {
     194                    this.tree.collapseTreeitem(this);
     195                    flag = true;
     196                }
     197                else {
     198                    if (this.inGroup) {
     199                        this.tree.setFocusToParentItem(this);
     200                        flag = true;
     201                    }
     202                }
     203                break;
     204
     205            case this.keyCode.HOME:
     206                this.tree.setFocusToFirstItem();
     207                flag = true;
     208                break;
     209
     210            case this.keyCode.END:
     211                this.tree.setFocusToLastItem();
     212                flag = true;
     213                break;
     214
     215            default:
     216                if (isPrintableCharacter(char)) {
     217                    printableCharacter(this);
     218                }
     219                break;
     220        }
     221    }
     222
     223    if (flag) {
     224        event.stopPropagation();
     225        event.preventDefault();
     226    }
     227};
     228
     229TreeitemLink.prototype.handleClick = function (event) {
     230
     231    // only process click events that directly happened on this treeitem
     232    if (event.target !== this.domNode && event.target !== this.domNode.firstElementChild) {
     233        return;
     234    }
     235
     236    if (this.isExpandable) {
     237        if (this.isExpanded()) {
     238            this.tree.collapseTreeitem(this);
     239        }
     240        else {
     241            this.tree.expandTreeitem(this);
     242        }
     243        event.stopPropagation();
     244    }
     245};
     246
     247TreeitemLink.prototype.handleFocus = function (event) {
     248    var node = this.domNode;
     249    if (this.isExpandable) {
     250        node = node.firstElementChild;
     251    }
     252    node.classList.add('focus');
     253};
     254
     255TreeitemLink.prototype.handleBlur = function (event) {
     256    var node = this.domNode;
     257    if (this.isExpandable) {
     258        node = node.firstElementChild;
     259    }
     260    node.classList.remove('focus');
     261};
     262
     263TreeitemLink.prototype.handleMouseOver = function (event) {
     264    event.currentTarget.classList.add('hover');
     265};
     266
     267TreeitemLink.prototype.handleMouseOut = function (event) {
     268    event.currentTarget.classList.remove('hover');
     269};
     270 No newline at end of file
  • src/wp-admin/plugin-editor.php

    diff --git src/wp-admin/plugin-editor.php src/wp-admin/plugin-editor.php
    index 807cd5e09d..1ed03286ca 100644
    if ( 'POST' === $_SERVER['REQUEST_METHOD'] ) { 
    144144        wp_add_inline_script( 'wp-theme-plugin-editor', sprintf( 'jQuery( function( $ ) { wp.themePluginEditor.init( $( "#template" ), %s ); } )', wp_json_encode( $settings ) ) );
    145145        wp_add_inline_script( 'wp-theme-plugin-editor', sprintf( 'wp.themePluginEditor.themeOrPlugin = "plugin";' ) );
    146146
     147        wp_enqueue_script( 'tree-links' );
     148        wp_add_inline_script( 'tree-links', 'jQuery( function( $ ) { $(\'#templateside [role="group"]\').parent().attr(\'aria-expanded\', false); $(\'#templateside .notice\').parents(\'[aria-expanded]\').attr(\'aria-expanded\', true); } )' );
     149
    147150        require_once(ABSPATH . 'wp-admin/admin-header.php');
    148151
    149152        update_recently_edited(WP_PLUGIN_DIR . '/' . $file);
    if ( 'POST' === $_SERVER['REQUEST_METHOD'] ) { 
    231234</div>
    232235
    233236<div id="templateside">
    234         <h2><?php _e( 'Plugin Files' ); ?></h2>
     237        <h2 id="plugin-files-label"><?php _e( 'Plugin Files' ); ?></h2>
    235238
    236239        <?php
    237240        $plugin_editable_files = array();
    if ( 'POST' === $_SERVER['REQUEST_METHOD'] ) { 
    241244                }
    242245        }
    243246        ?>
    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; ?>
     247        <ul role="tree" aria-labelledby="plugin-files-label">
     248        <li role="treeitem" tabindex="-1" aria-expanded="true"
     249                aria-level="1"
     250                aria-posinset="1"
     251                aria-setsize="1">
     252                <ul role="group" style="padding-left: 0;">
     253                        <?php wp_print_plugin_file_tree( wp_make_plugin_file_tree( $plugin_editable_files ) ); ?>
     254                </ul>
    250255        </ul>
    251256</div>
    252257<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..06ed64c46a 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'];
    if ( 'POST' === $_SERVER['REQUEST_METHOD'] ) { 
    129137        wp_add_inline_script( 'wp-theme-plugin-editor', sprintf( 'jQuery( function( $ ) { wp.themePluginEditor.init( $( "#template" ), %s ); } )', wp_json_encode( $settings ) ) );
    130138        wp_add_inline_script( 'wp-theme-plugin-editor', 'wp.themePluginEditor.themeOrPlugin = "theme";' );
    131139
     140        wp_enqueue_script( 'tree-links' );
     141        wp_add_inline_script( 'tree-links', 'jQuery( function( $ ) { $(\'#templateside [role="group"]\').parent().attr(\'aria-expanded\', false); $(\'#templateside .notice\').parents(\'[aria-expanded]\').attr(\'aria-expanded\', true); } )' );
     142
    132143        require_once( ABSPATH . 'wp-admin/admin-header.php' );
    133144
    134145        update_recently_edited( $file );
    foreach ( wp_get_themes( array( 'errors' => null ) ) as $a_stylesheet => $a_them 
    205216if ( $theme->errors() )
    206217        echo '<div class="error"><p><strong>' . __( 'This theme is broken.' ) . '</strong> ' . $theme->errors()->get_error_message() . '</p></div>';
    207218?>
    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";
     219<div id="templateside">
     220        <h2 id="theme-files-label"><?php _e( 'Theme Files' ); ?></h2>
     221        <?php
     222        if ( $has_templates || $theme->parent() ) :
     223                if ( $theme->parent() ) {
     224                        /* translators: %s: link to edit parent theme */
     225                        echo '<p class="howto">' . sprintf( __( 'This child theme inherits templates from a parent theme, %s.' ),
     226                                sprintf( '<a href="%s">%s</a>',
     227                                        self_admin_url( 'theme-editor.php?theme=' . urlencode( $theme->get_template() ) ),
     228                                        $theme->parent()->display( 'Name' )
     229                                )
     230                        ) . "</p>\n";
    245231                }
    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; ?>
     232        endif;
     233        ?>
     234        <ul role="tree" aria-labelledby="theme-files-label">
     235                <li role="treeitem" tabindex="-1" aria-expanded="true"
     236                        aria-level="1"
     237                        aria-posinset="1"
     238                        aria-setsize="1">
     239                        <ul role="group" style="padding-left: 0;">
     240                                <?php wp_print_theme_file_tree( wp_make_theme_file_tree( $allowed_files ) ); ?>
     241                        </ul>
     242                </li>
     243        </ul>
    264244</div>
     245
    265246<?php if ( $error ) :
    266247        echo '<div class="error"><p>' . __('Oops, no such file exists! Double check the name and try again, merci.') . '</p></div>';
    267248else : ?>
  • src/wp-includes/script-loader.php

    diff --git src/wp-includes/script-loader.php src/wp-includes/script-loader.php
    index 067b941e6d..3b002044f9 100644
    function wp_default_scripts( &$scripts ) { 
    886886                $scripts->add( 'media-gallery', "/wp-admin/js/media-gallery$suffix.js", array('jquery'), false, 1 );
    887887
    888888                $scripts->add( 'svg-painter', '/wp-admin/js/svg-painter.js', array( 'jquery' ), false, 1 );
     889
     890                $scripts->add( 'treeitem-links', "/wp-admin/js/treeitem-links$suffix.js", array( 'jquery' ), false, 1 );
     891                $scripts->add( 'tree-links', "/wp-admin/js/tree-links$suffix.js", array( 'treeitem-links' ), false, 1 );
    889892        }
    890893}
    891894