WordPress.org

Make WordPress Core

Changeset 41851


Ignore:
Timestamp:
10/13/17 02:38:19 (3 months ago)
Author:
westonruter
Message:

File Editors: Display list of theme/plugin files in scrollable directory tree.

Props WraithKenny, afercia, melchoyce, westonruter.
Amends [41721].
Fixes #24048.

Location:
trunk/src/wp-admin
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/css/common.css

    r41836 r41851  
    14671467    display: block; 
    14681468    margin: 0; 
    1469     padding: 5px 12px; 
     1469    padding: 5px 8px; 
    14701470    font-weight: 600; 
    14711471    text-decoration: none; 
     
    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 
     
    30373037    height: calc( 100vh - 280px ); 
    30383038} 
    3039 #templateside { 
    3040     margin-top: 31px; 
    3041     overflow: scroll; 
    3042 } 
     3039 
     3040#templateside > h2 { 
     3041    padding-top: 6px; 
     3042    padding-bottom: 6px; 
     3043    margin: 0; 
     3044    border-bottom: solid 1px #ccc; 
     3045} 
     3046 
     3047#templateside ol, 
     3048#templateside ul { 
     3049    margin: .5em 0; 
     3050    padding: 0; 
     3051} 
     3052#templateside > ul { 
     3053    margin-top: 0; 
     3054    overflow: auto; 
     3055    padding: 2px; 
     3056    height: calc(100vh - 280px); 
     3057} 
     3058#templateside ul ul { 
     3059    padding-left: 12px; 
     3060} 
     3061 
     3062/* 
     3063 * Styles for Theme and Plugin editors. 
     3064 */ 
     3065 
     3066/* Hide collapsed items. */ 
     3067[role="treeitem"][aria-expanded="false"] > ul { 
     3068    display: none; 
     3069} 
     3070 
     3071/* Use arrow dashicons for folder states, but hide from screen readers. */ 
     3072[role="treeitem"] span[aria-hidden] { 
     3073    display: inline; 
     3074    font-family: dashicons; 
     3075    font-size: 20px; 
     3076    position: absolute; 
     3077    pointer-events: none; 
     3078} 
     3079[role="treeitem"][aria-expanded="false"] > .folder-label .icon:after { 
     3080    content: "\f139"; 
     3081} 
     3082[role="treeitem"][aria-expanded="true"] > .folder-label .icon:after { 
     3083    content: "\f140"; 
     3084} 
     3085[role="treeitem"] .folder-label { 
     3086    display: block; 
     3087    padding: 3px 3px 3px 12px; 
     3088    cursor: pointer; 
     3089} 
     3090 
     3091/* Remove outline, and create our own focus and hover styles */ 
     3092[role="treeitem"] { 
     3093    outline: 0; 
     3094} 
     3095[role="treeitem"] .folder-label.focus { 
     3096    color: #124964; 
     3097    box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, .8); 
     3098} 
     3099[role="treeitem"].hover, 
     3100[role="treeitem"] .folder-label.hover { 
     3101    background-color: #DDDDDD; 
     3102} 
     3103 
     3104.tree-folder { 
     3105    margin: 0; 
     3106    position: relative; 
     3107} 
     3108[role="treeitem"] li { 
     3109    position: relative; 
     3110} 
     3111 
     3112/* Styles for folder indicators/depth */ 
     3113.tree-folder .tree-folder::after { 
     3114    content: ' '; 
     3115    display: block; 
     3116    position: absolute; 
     3117    left: 2px; 
     3118    border-left: 1px solid #ccc; 
     3119    top: -13px; 
     3120    bottom: 10px; 
     3121} 
     3122.tree-folder > li::before { 
     3123    content: ' '; 
     3124    position: absolute; 
     3125    display: block; 
     3126    border-left: 1px solid #ccc; 
     3127    left: 2px; 
     3128    top: -5px; 
     3129    height: 18px; 
     3130    width: 7px; 
     3131    border-bottom: 1px solid #ccc; 
     3132} 
     3133.tree-folder > li::after { 
     3134    content: ' '; 
     3135    position: absolute; 
     3136    display: block; 
     3137    border-left: 1px solid #ccc; 
     3138    left: 2px; 
     3139    bottom: -7px; 
     3140    top: 0; 
     3141} 
     3142 
     3143/* current-file needs to adjustment for .notice styles */ 
     3144#templateside .current-file { 
     3145    margin: -4px 0 -2px; 
     3146} 
     3147.tree-folder > .current-file::before { 
     3148    left: 4px; 
     3149    height: 15px; 
     3150    width: 6px; 
     3151    border-left: none; 
     3152    top: 3px; 
     3153} 
     3154.tree-folder > .current-file::after { 
     3155    bottom: -4px; 
     3156    height: 7px; 
     3157    left: 2px; 
     3158    top: auto; 
     3159} 
     3160 
     3161/* Lines shouldn't continue on last item */ 
     3162.tree-folder > li:last-child::after, 
     3163.tree-folder li:last-child > .tree-folder::after { 
     3164    display: none; 
     3165} 
     3166 
    30433167 
    30443168#theme-plugin-editor-label { 
     
    30913215} 
    30923216 
    3093 #templateside h2, 
    30943217#postcustomstuff p.submit { 
    30953218    margin: 0; 
     
    30983221#templateside h4 { 
    30993222    margin: 1em 0 0; 
    3100 } 
    3101  
    3102 #templateside ol, 
    3103 #templateside ul { 
    3104     margin: .5em 0; 
    3105     padding: 0; 
    31063223} 
    31073224 
     
    36543771    } 
    36553772 
     3773    #templateside ul ul { 
     3774        padding-left: 1.5em; 
     3775    } 
     3776    [role="treeitem"] .folder-label { 
     3777        display: block; 
     3778        padding: 5px; 
     3779    } 
     3780    .tree-folder > li::before, 
     3781    .tree-folder > li::after, 
     3782    .tree-folder .tree-folder::after { 
     3783        left: -8px; 
     3784    } 
     3785    .tree-folder > li::before { 
     3786        top: 0px; 
     3787        height: 13px; 
     3788    } 
     3789    .tree-folder > .current-file::before { 
     3790        left: -5px; 
     3791        top: 7px; 
     3792        width: 4px; 
     3793    } 
     3794    .tree-folder > .current-file::after { 
     3795        height: 9px; 
     3796        left: -8px; 
     3797    } 
     3798    .wrap #templateside span.notice { 
     3799        margin-left: -14px; 
     3800    } 
     3801 
    36563802    .fileedit-sub .alignright { 
    36573803        margin-top: 15px; 
  • trunk/src/wp-admin/includes/misc.php

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

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

    r41774 r41851  
    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 
     
    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> 
  • trunk/src/wp-admin/theme-editor.php

    r41806 r41851  
    8888            break; 
    8989    } 
     90} 
     91 
     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; 
    9098} 
    9199 
     
    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; ?> 
    264 </div> 
     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> 
     241</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>'; 
Note: See TracChangeset for help on using the changeset viewer.