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

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

Built from https://develop.svn.wordpress.org/trunk@41851


git-svn-id: http://core.svn.wordpress.org/trunk@41685 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Weston Ruter 2017-10-13 02:39:47 +00:00
parent 95cfb7f5e4
commit 0659de4e21
10 changed files with 1124 additions and 99 deletions

View File

@ -1466,17 +1466,17 @@ div.error {
.wrap #templateside .notice {
display: block;
margin: 0;
padding: 5px 12px;
padding: 5px 8px;
font-weight: 600;
text-decoration: none;
}
.wrap #templateside span.notice {
margin-right: -12px;
margin-right: -12px;
}
#templateside li.notice a {
padding: 0;
padding: 0;
}
/* Update icon. */
@ -3036,11 +3036,135 @@ img {
width: 97%;
height: calc( 100vh - 280px );
}
#templateside {
margin-top: 31px;
overflow: scroll;
#templateside > h2 {
padding-top: 6px;
padding-bottom: 6px;
margin: 0;
border-bottom: solid 1px #ccc;
}
#templateside ol,
#templateside ul {
margin: .5em 0;
padding: 0;
}
#templateside > ul {
margin-top: 0;
overflow: auto;
padding: 2px;
height: calc(100vh - 280px);
}
#templateside ul ul {
padding-right: 12px;
}
/*
* Styles for Theme and Plugin editors.
*/
/* Hide collapsed items. */
[role="treeitem"][aria-expanded="false"] > ul {
display: none;
}
/* Use arrow dashicons for folder states, but hide from screen readers. */
[role="treeitem"] span[aria-hidden] {
display: inline;
font-family: dashicons;
font-size: 20px;
position: absolute;
pointer-events: none;
}
[role="treeitem"][aria-expanded="false"] > .folder-label .icon:after {
content: "\f141";
}
[role="treeitem"][aria-expanded="true"] > .folder-label .icon:after {
content: "\f140";
}
[role="treeitem"] .folder-label {
display: block;
padding: 3px 12px 3px 3px;
cursor: pointer;
}
/* Remove outline, and create our own focus and hover styles */
[role="treeitem"] {
outline: 0;
}
[role="treeitem"] .folder-label.focus {
color: #124964;
box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, .8);
}
[role="treeitem"].hover,
[role="treeitem"] .folder-label.hover {
background-color: #DDDDDD;
}
.tree-folder {
margin: 0;
position: relative;
}
[role="treeitem"] li {
position: relative;
}
/* Styles for folder indicators/depth */
.tree-folder .tree-folder::after {
content: ' ';
display: block;
position: absolute;
right: 2px;
border-right: 1px solid #ccc;
top: -13px;
bottom: 10px;
}
.tree-folder > li::before {
content: ' ';
position: absolute;
display: block;
border-right: 1px solid #ccc;
right: 2px;
top: -5px;
height: 18px;
width: 7px;
border-bottom: 1px solid #ccc;
}
.tree-folder > li::after {
content: ' ';
position: absolute;
display: block;
border-right: 1px solid #ccc;
right: 2px;
bottom: -7px;
top: 0;
}
/* current-file needs to adjustment for .notice styles */
#templateside .current-file {
margin: -4px 0 -2px;
}
.tree-folder > .current-file::before {
right: 4px;
height: 15px;
width: 6px;
border-right: none;
top: 3px;
}
.tree-folder > .current-file::after {
bottom: -4px;
height: 7px;
right: 2px;
top: auto;
}
/* Lines shouldn't continue on last item */
.tree-folder > li:last-child::after,
.tree-folder li:last-child > .tree-folder::after {
display: none;
}
#theme-plugin-editor-label {
display: inline-block;
margin-bottom: 1em;
@ -3090,7 +3214,6 @@ img {
word-wrap: break-word;
}
#templateside h2,
#postcustomstuff p.submit {
margin: 0;
}
@ -3099,12 +3222,6 @@ img {
margin: 1em 0 0;
}
#templateside ol,
#templateside ul {
margin: .5em 0;
padding: 0;
}
#templateside li {
margin: 4px 0;
}
@ -3653,6 +3770,35 @@ img {
width: 100%;
}
#templateside ul ul {
padding-right: 1.5em;
}
[role="treeitem"] .folder-label {
display: block;
padding: 5px;
}
.tree-folder > li::before,
.tree-folder > li::after,
.tree-folder .tree-folder::after {
right: -8px;
}
.tree-folder > li::before {
top: 0px;
height: 13px;
}
.tree-folder > .current-file::before {
right: -5px;
top: 7px;
width: 4px;
}
.tree-folder > .current-file::after {
height: 9px;
right: -8px;
}
.wrap #templateside span.notice {
margin-right: -14px;
}
.fileedit-sub .alignright {
margin-top: 15px;
}

File diff suppressed because one or more lines are too long

View File

@ -1466,17 +1466,17 @@ div.error {
.wrap #templateside .notice {
display: block;
margin: 0;
padding: 5px 12px;
padding: 5px 8px;
font-weight: 600;
text-decoration: none;
}
.wrap #templateside span.notice {
margin-left: -12px;
margin-left: -12px;
}
#templateside li.notice a {
padding: 0;
padding: 0;
}
/* Update icon. */
@ -3036,11 +3036,135 @@ img {
width: 97%;
height: calc( 100vh - 280px );
}
#templateside {
margin-top: 31px;
overflow: scroll;
#templateside > h2 {
padding-top: 6px;
padding-bottom: 6px;
margin: 0;
border-bottom: solid 1px #ccc;
}
#templateside ol,
#templateside ul {
margin: .5em 0;
padding: 0;
}
#templateside > ul {
margin-top: 0;
overflow: auto;
padding: 2px;
height: calc(100vh - 280px);
}
#templateside ul ul {
padding-left: 12px;
}
/*
* Styles for Theme and Plugin editors.
*/
/* Hide collapsed items. */
[role="treeitem"][aria-expanded="false"] > ul {
display: none;
}
/* Use arrow dashicons for folder states, but hide from screen readers. */
[role="treeitem"] span[aria-hidden] {
display: inline;
font-family: dashicons;
font-size: 20px;
position: absolute;
pointer-events: none;
}
[role="treeitem"][aria-expanded="false"] > .folder-label .icon:after {
content: "\f139";
}
[role="treeitem"][aria-expanded="true"] > .folder-label .icon:after {
content: "\f140";
}
[role="treeitem"] .folder-label {
display: block;
padding: 3px 3px 3px 12px;
cursor: pointer;
}
/* Remove outline, and create our own focus and hover styles */
[role="treeitem"] {
outline: 0;
}
[role="treeitem"] .folder-label.focus {
color: #124964;
box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, .8);
}
[role="treeitem"].hover,
[role="treeitem"] .folder-label.hover {
background-color: #DDDDDD;
}
.tree-folder {
margin: 0;
position: relative;
}
[role="treeitem"] li {
position: relative;
}
/* Styles for folder indicators/depth */
.tree-folder .tree-folder::after {
content: ' ';
display: block;
position: absolute;
left: 2px;
border-left: 1px solid #ccc;
top: -13px;
bottom: 10px;
}
.tree-folder > li::before {
content: ' ';
position: absolute;
display: block;
border-left: 1px solid #ccc;
left: 2px;
top: -5px;
height: 18px;
width: 7px;
border-bottom: 1px solid #ccc;
}
.tree-folder > li::after {
content: ' ';
position: absolute;
display: block;
border-left: 1px solid #ccc;
left: 2px;
bottom: -7px;
top: 0;
}
/* current-file needs to adjustment for .notice styles */
#templateside .current-file {
margin: -4px 0 -2px;
}
.tree-folder > .current-file::before {
left: 4px;
height: 15px;
width: 6px;
border-left: none;
top: 3px;
}
.tree-folder > .current-file::after {
bottom: -4px;
height: 7px;
left: 2px;
top: auto;
}
/* Lines shouldn't continue on last item */
.tree-folder > li:last-child::after,
.tree-folder li:last-child > .tree-folder::after {
display: none;
}
#theme-plugin-editor-label {
display: inline-block;
margin-bottom: 1em;
@ -3090,7 +3214,6 @@ img {
word-wrap: break-word;
}
#templateside h2,
#postcustomstuff p.submit {
margin: 0;
}
@ -3099,12 +3222,6 @@ img {
margin: 1em 0 0;
}
#templateside ol,
#templateside ul {
margin: .5em 0;
padding: 0;
}
#templateside li {
margin: 4px 0;
}
@ -3653,6 +3770,35 @@ img {
width: 100%;
}
#templateside ul ul {
padding-left: 1.5em;
}
[role="treeitem"] .folder-label {
display: block;
padding: 5px;
}
.tree-folder > li::before,
.tree-folder > li::after,
.tree-folder .tree-folder::after {
left: -8px;
}
.tree-folder > li::before {
top: 0px;
height: 13px;
}
.tree-folder > .current-file::before {
left: -5px;
top: 7px;
width: 4px;
}
.tree-folder > .current-file::after {
height: 9px;
left: -8px;
}
.wrap #templateside span.notice {
margin-left: -14px;
}
.fileedit-sub .alignright {
margin-top: 15px;
}

File diff suppressed because one or more lines are too long

View File

@ -269,6 +269,178 @@ function update_recently_edited( $file ) {
update_option( 'recently_edited', $oldfiles );
}
/**
* Makes a tree structure for the Theme Editor's file list.
*
* @since 4.9.0
* @access private
*
* @param array $allowed_files List of theme file paths.
* @return array Tree structure for listing theme files.
*/
function wp_make_theme_file_tree( $allowed_files ) {
$tree_list = array();
foreach ( $allowed_files as $file_name => $absolute_filename ) {
$list = explode( '/', $file_name );
$last_dir = &$tree_list;
foreach ( $list as $dir ) {
$last_dir =& $last_dir[ $dir ];
}
$last_dir = $file_name;
}
return $tree_list;
}
/**
* Outputs the formatted file list for the Theme Editor.
*
* @since 4.9.0
* @access private
*
* @param array|string $tree List of file/folder paths, or filename.
* @param int $level The aria-level for the current iteration.
* @param int $size The aria-setsize for the current iteration.
* @param int $index The aria-posinset for the current iteration.
*/
function wp_print_theme_file_tree( $tree, $level = 2, $size = 1, $index = 1 ) {
global $relative_file, $stylesheet;
if ( is_array( $tree ) ) {
$index = 0;
$size = count( $tree );
foreach ( $tree as $label => $theme_file ) :
$index++;
if ( ! is_array( $theme_file ) ) {
wp_print_theme_file_tree( $theme_file, $level, $index, $size );
continue;
}
?>
<li role="treeitem" aria-expanded="true" tabindex="-1"
aria-level="<?php echo esc_attr( $level ); ?>"
aria-setsize="<?php echo esc_attr( $size ); ?>"
aria-posinset="<?php echo esc_attr( $index ); ?>">
<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>
<ul role="group" class="tree-folder"><?php wp_print_theme_file_tree( $theme_file, $level + 1, $index, $size ); ?></ul>
</li>
<?php
endforeach;
} else {
$filename = $tree;
$url = add_query_arg(
array(
'file' => rawurlencode( $tree ),
'theme' => rawurlencode( $stylesheet ),
),
admin_url( 'theme-editor.php' )
);
?>
<li role="none" class="<?php echo esc_attr( $relative_file === $filename ? 'current-file' : '' ); ?>">
<a role="treeitem" tabindex="<?php echo esc_attr( $relative_file === $filename ? '0' : '-1' ); ?>"
href="<?php echo esc_url( $url ); ?>"
aria-level="<?php echo esc_attr( $level ); ?>"
aria-setsize="<?php echo esc_attr( $size ); ?>"
aria-posinset="<?php echo esc_attr( $index ); ?>">
<?php
$file_description = esc_html( get_file_description( $filename ) );
if ( $file_description !== $filename && basename( $filename ) !== $file_description ) {
$file_description .= '<br /><span class="nonessential">(' . esc_html( $filename ) . ')</span>';
}
if ( $relative_file === $filename ) {
echo '<span class="notice notice-info">' . $file_description . '</span>';
} else {
echo $file_description;
}
?>
</a>
</li>
<?php
}
}
/**
* Makes a tree structure for the Plugin Editor's file list.
*
* @since 4.9.0
* @access private
*
* @param string $plugin_editable_files List of plugin file paths.
* @return array Tree structure for listing plugin files.
*/
function wp_make_plugin_file_tree( $plugin_editable_files ) {
$tree_list = array();
foreach ( $plugin_editable_files as $plugin_file ) {
$list = explode( '/', preg_replace( '#^.+?/#', '', $plugin_file ) );
$last_dir = &$tree_list;
foreach ( $list as $dir ) {
$last_dir =& $last_dir[ $dir ];
}
$last_dir = $plugin_file;
}
return $tree_list;
}
/**
* Outputs the formatted file list for the Plugin Editor.
*
* @since 4.9.0
* @access private
*
* @param array|string $tree List of file/folder paths, or filename.
* @param string $label Name of file or folder to print.
* @param int $level The aria-level for the current iteration.
* @param int $size The aria-setsize for the current iteration.
* @param int $index The aria-posinset for the current iteration.
*/
function wp_print_plugin_file_tree( $tree, $label = '', $level = 2, $size = 1, $index = 1 ) {
global $file, $plugin;
if ( is_array( $tree ) ) {
$index = 0;
$size = count( $tree );
foreach ( $tree as $label => $plugin_file ) :
$index++;
if ( ! is_array( $plugin_file ) ) {
wp_print_plugin_file_tree( $plugin_file, $label, $level, $index, $size );
continue;
}
?>
<li role="treeitem" aria-expanded="true" tabindex="-1"
aria-level="<?php echo esc_attr( $level ); ?>"
aria-setsize="<?php echo esc_attr( $size ); ?>"
aria-posinset="<?php echo esc_attr( $index ); ?>">
<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>
<ul role="group" class="tree-folder"><?php wp_print_plugin_file_tree( $plugin_file, '', $level + 1, $index, $size ); ?></ul>
</li>
<?php
endforeach;
} else {
$url = add_query_arg(
array(
'file' => rawurlencode( $tree ),
'plugin' => rawurlencode( $plugin ),
),
admin_url( 'plugin-editor.php' )
);
?>
<li role="none" class="<?php echo esc_attr( $file === $tree ? 'current-file' : '' ); ?>">
<a role="treeitem" tabindex="<?php echo esc_attr( $file === $tree ? '0' : '-1' ); ?>"
href="<?php echo esc_url( $url ); ?>"
aria-level="<?php echo esc_attr( $level ); ?>"
aria-setsize="<?php echo esc_attr( $size ); ?>"
aria-posinset="<?php echo esc_attr( $index ); ?>">
<?php
if ( $file === $tree ) {
echo '<span class="notice notice-info">' . esc_html( $label ) . '</span>';
} else {
echo esc_html( $label );
}
?>
</a>
</li>
<?php
}
}
/**
* Flushes rewrite rules if siteurl, home or page_on_front changed.
*

View File

@ -6,8 +6,9 @@ if ( ! window.wp ) {
wp.themePluginEditor = (function( $ ) {
'use strict';
var component, TreeLinks;
var component = {
component = {
l10n: {
lintError: {
singular: '',
@ -66,6 +67,8 @@ wp.themePluginEditor = (function( $ ) {
} );
}
$( component.initFileBrowser );
$( window ).on( 'beforeunload', function() {
if ( component.dirty ) {
return component.l10n.saveAlert;
@ -86,11 +89,11 @@ wp.themePluginEditor = (function( $ ) {
pointer: component.themeOrPlugin + '_editor_notice'
});
// hide modal
// Hide modal.
component.warning.remove();
$( 'body' ).removeClass( 'modal-open' );
// return focus - is this a trap?
// Return focus - is this a trap?
component.instance.codemirror.focus();
};
@ -148,7 +151,7 @@ wp.themePluginEditor = (function( $ ) {
component.removeNotice( component.lastSaveNoticeCode );
}
request.done( function ( response ) {
request.done( function( response ) {
component.lastSaveNoticeCode = 'file_saved';
component.addNotice({
code: component.lastSaveNoticeCode,
@ -159,7 +162,7 @@ wp.themePluginEditor = (function( $ ) {
component.dirty = false;
} );
request.fail( function ( response ) {
request.fail( function( response ) {
var notice = $.extend(
{
code: 'save_error',
@ -350,5 +353,583 @@ wp.themePluginEditor = (function( $ ) {
component.instance = editor;
};
/**
* Initialization of the file browser's folder states.
*
* @since 4.9.0
* @returns {void}
*/
component.initFileBrowser = function initFileBrowser() {
var $templateside = $( '#templateside' );
// Collapse all folders.
$templateside.find( '[role="group"]' ).parent().attr( 'aria-expanded', false );
// Expand ancestors to the current file.
$templateside.find( '.notice' ).parents( '[aria-expanded]' ).attr( 'aria-expanded', true );
// Find Tree elements and enhance them.
$templateside.find( '[role="tree"]' ).each( function() {
var treeLinks = new TreeLinks( this );
treeLinks.init();
} );
// Scroll the current file into view.
$templateside.find( '.current-file' ).each( function() {
this.scrollIntoView( false );
} );
};
/* jshint ignore:start */
/* jscs:disable */
/* eslint-disable */
/**
* Creates a new TreeitemLink.
*
* @since 4.9.0
* @class
* @private
* @see {@link https://www.w3.org/TR/wai-aria-practices-1.1/examples/treeview/treeview-2/treeview-2b.html|W3C Treeview Example}
* @license W3C-20150513
*/
var TreeitemLink = (function () {
/**
* This content is licensed according to the W3C Software License at
* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
*
* File: TreeitemLink.js
*
* Desc: Treeitem widget that implements ARIA Authoring Practices
* for a tree being used as a file viewer
*
* Author: Jon Gunderson, Ku Ja Eun and Nicholas Hoyt
*/
/**
* @constructor
*
* @desc
* Treeitem object for representing the state and user interactions for a
* treeItem widget
*
* @param node
* An element with the role=tree attribute
*/
var TreeitemLink = function (node, treeObj, group) {
// Check whether node is a DOM element
if (typeof node !== 'object') {
return;
}
node.tabIndex = -1;
this.tree = treeObj;
this.groupTreeitem = group;
this.domNode = node;
this.label = node.textContent.trim();
this.stopDefaultClick = false;
if (node.getAttribute('aria-label')) {
this.label = node.getAttribute('aria-label').trim();
}
this.isExpandable = false;
this.isVisible = false;
this.inGroup = false;
if (group) {
this.inGroup = true;
}
var elem = node.firstElementChild;
while (elem) {
if (elem.tagName.toLowerCase() == 'ul') {
elem.setAttribute('role', 'group');
this.isExpandable = true;
break;
}
elem = elem.nextElementSibling;
}
this.keyCode = Object.freeze({
RETURN: 13,
SPACE: 32,
PAGEUP: 33,
PAGEDOWN: 34,
END: 35,
HOME: 36,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40
});
};
TreeitemLink.prototype.init = function () {
this.domNode.tabIndex = -1;
if (!this.domNode.getAttribute('role')) {
this.domNode.setAttribute('role', 'treeitem');
}
this.domNode.addEventListener('keydown', this.handleKeydown.bind(this));
this.domNode.addEventListener('click', this.handleClick.bind(this));
this.domNode.addEventListener('focus', this.handleFocus.bind(this));
this.domNode.addEventListener('blur', this.handleBlur.bind(this));
if (this.isExpandable) {
this.domNode.firstElementChild.addEventListener('mouseover', this.handleMouseOver.bind(this));
this.domNode.firstElementChild.addEventListener('mouseout', this.handleMouseOut.bind(this));
}
else {
this.domNode.addEventListener('mouseover', this.handleMouseOver.bind(this));
this.domNode.addEventListener('mouseout', this.handleMouseOut.bind(this));
}
};
TreeitemLink.prototype.isExpanded = function () {
if (this.isExpandable) {
return this.domNode.getAttribute('aria-expanded') === 'true';
}
return false;
};
/* EVENT HANDLERS */
TreeitemLink.prototype.handleKeydown = function (event) {
var tgt = event.currentTarget,
flag = false,
_char = event.key,
clickEvent;
function isPrintableCharacter(str) {
return str.length === 1 && str.match(/\S/);
}
function printableCharacter(item) {
if (_char == '*') {
item.tree.expandAllSiblingItems(item);
flag = true;
}
else {
if (isPrintableCharacter(_char)) {
item.tree.setFocusByFirstCharacter(item, _char);
flag = true;
}
}
}
this.stopDefaultClick = false;
if (event.altKey || event.ctrlKey || event.metaKey) {
return;
}
if (event.shift) {
if (event.keyCode == this.keyCode.SPACE || event.keyCode == this.keyCode.RETURN) {
event.stopPropagation();
this.stopDefaultClick = true;
}
else {
if (isPrintableCharacter(_char)) {
printableCharacter(this);
}
}
}
else {
switch (event.keyCode) {
case this.keyCode.SPACE:
case this.keyCode.RETURN:
if (this.isExpandable) {
if (this.isExpanded()) {
this.tree.collapseTreeitem(this);
}
else {
this.tree.expandTreeitem(this);
}
flag = true;
}
else {
event.stopPropagation();
this.stopDefaultClick = true;
}
break;
case this.keyCode.UP:
this.tree.setFocusToPreviousItem(this);
flag = true;
break;
case this.keyCode.DOWN:
this.tree.setFocusToNextItem(this);
flag = true;
break;
case this.keyCode.RIGHT:
if (this.isExpandable) {
if (this.isExpanded()) {
this.tree.setFocusToNextItem(this);
}
else {
this.tree.expandTreeitem(this);
}
}
flag = true;
break;
case this.keyCode.LEFT:
if (this.isExpandable && this.isExpanded()) {
this.tree.collapseTreeitem(this);
flag = true;
}
else {
if (this.inGroup) {
this.tree.setFocusToParentItem(this);
flag = true;
}
}
break;
case this.keyCode.HOME:
this.tree.setFocusToFirstItem();
flag = true;
break;
case this.keyCode.END:
this.tree.setFocusToLastItem();
flag = true;
break;
default:
if (isPrintableCharacter(_char)) {
printableCharacter(this);
}
break;
}
}
if (flag) {
event.stopPropagation();
event.preventDefault();
}
};
TreeitemLink.prototype.handleClick = function (event) {
// only process click events that directly happened on this treeitem
if (event.target !== this.domNode && event.target !== this.domNode.firstElementChild) {
return;
}
if (this.isExpandable) {
if (this.isExpanded()) {
this.tree.collapseTreeitem(this);
}
else {
this.tree.expandTreeitem(this);
}
event.stopPropagation();
}
};
TreeitemLink.prototype.handleFocus = function (event) {
var node = this.domNode;
if (this.isExpandable) {
node = node.firstElementChild;
}
node.classList.add('focus');
};
TreeitemLink.prototype.handleBlur = function (event) {
var node = this.domNode;
if (this.isExpandable) {
node = node.firstElementChild;
}
node.classList.remove('focus');
};
TreeitemLink.prototype.handleMouseOver = function (event) {
event.currentTarget.classList.add('hover');
};
TreeitemLink.prototype.handleMouseOut = function (event) {
event.currentTarget.classList.remove('hover');
};
return TreeitemLink;
})();
/**
* Creates a new TreeLinks.
*
* @since 4.9.0
* @class
* @private
* @see {@link https://www.w3.org/TR/wai-aria-practices-1.1/examples/treeview/treeview-2/treeview-2b.html|W3C Treeview Example}
* @license W3C-20150513
*/
TreeLinks = (function () {
/*
* This content is licensed according to the W3C Software License at
* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
*
* File: TreeLinks.js
*
* Desc: Tree widget that implements ARIA Authoring Practices
* for a tree being used as a file viewer
*
* Author: Jon Gunderson, Ku Ja Eun and Nicholas Hoyt
*/
/*
* @constructor
*
* @desc
* Tree item object for representing the state and user interactions for a
* tree widget
*
* @param node
* An element with the role=tree attribute
*/
var TreeLinks = function (node) {
// Check whether node is a DOM element
if (typeof node !== 'object') {
return;
}
this.domNode = node;
this.treeitems = [];
this.firstChars = [];
this.firstTreeitem = null;
this.lastTreeitem = null;
};
TreeLinks.prototype.init = function () {
function findTreeitems(node, tree, group) {
var elem = node.firstElementChild;
var ti = group;
while (elem) {
if ((elem.tagName.toLowerCase() === 'li' && elem.firstElementChild.tagName.toLowerCase() === 'span') || elem.tagName.toLowerCase() === 'a') {
ti = new TreeitemLink(elem, tree, group);
ti.init();
tree.treeitems.push(ti);
tree.firstChars.push(ti.label.substring(0, 1).toLowerCase());
}
if (elem.firstElementChild) {
findTreeitems(elem, tree, ti);
}
elem = elem.nextElementSibling;
}
}
// initialize pop up menus
if (!this.domNode.getAttribute('role')) {
this.domNode.setAttribute('role', 'tree');
}
findTreeitems(this.domNode, this, false);
this.updateVisibleTreeitems();
this.firstTreeitem.domNode.tabIndex = 0;
};
TreeLinks.prototype.setFocusToItem = function (treeitem) {
for (var i = 0; i < this.treeitems.length; i++) {
var ti = this.treeitems[i];
if (ti === treeitem) {
ti.domNode.tabIndex = 0;
ti.domNode.focus();
}
else {
ti.domNode.tabIndex = -1;
}
}
};
TreeLinks.prototype.setFocusToNextItem = function (currentItem) {
var nextItem = false;
for (var i = (this.treeitems.length - 1); i >= 0; i--) {
var ti = this.treeitems[i];
if (ti === currentItem) {
break;
}
if (ti.isVisible) {
nextItem = ti;
}
}
if (nextItem) {
this.setFocusToItem(nextItem);
}
};
TreeLinks.prototype.setFocusToPreviousItem = function (currentItem) {
var prevItem = false;
for (var i = 0; i < this.treeitems.length; i++) {
var ti = this.treeitems[i];
if (ti === currentItem) {
break;
}
if (ti.isVisible) {
prevItem = ti;
}
}
if (prevItem) {
this.setFocusToItem(prevItem);
}
};
TreeLinks.prototype.setFocusToParentItem = function (currentItem) {
if (currentItem.groupTreeitem) {
this.setFocusToItem(currentItem.groupTreeitem);
}
};
TreeLinks.prototype.setFocusToFirstItem = function () {
this.setFocusToItem(this.firstTreeitem);
};
TreeLinks.prototype.setFocusToLastItem = function () {
this.setFocusToItem(this.lastTreeitem);
};
TreeLinks.prototype.expandTreeitem = function (currentItem) {
if (currentItem.isExpandable) {
currentItem.domNode.setAttribute('aria-expanded', true);
this.updateVisibleTreeitems();
}
};
TreeLinks.prototype.expandAllSiblingItems = function (currentItem) {
for (var i = 0; i < this.treeitems.length; i++) {
var ti = this.treeitems[i];
if ((ti.groupTreeitem === currentItem.groupTreeitem) && ti.isExpandable) {
this.expandTreeitem(ti);
}
}
};
TreeLinks.prototype.collapseTreeitem = function (currentItem) {
var groupTreeitem = false;
if (currentItem.isExpanded()) {
groupTreeitem = currentItem;
}
else {
groupTreeitem = currentItem.groupTreeitem;
}
if (groupTreeitem) {
groupTreeitem.domNode.setAttribute('aria-expanded', false);
this.updateVisibleTreeitems();
this.setFocusToItem(groupTreeitem);
}
};
TreeLinks.prototype.updateVisibleTreeitems = function () {
this.firstTreeitem = this.treeitems[0];
for (var i = 0; i < this.treeitems.length; i++) {
var ti = this.treeitems[i];
var parent = ti.domNode.parentNode;
ti.isVisible = true;
while (parent && (parent !== this.domNode)) {
if (parent.getAttribute('aria-expanded') == 'false') {
ti.isVisible = false;
}
parent = parent.parentNode;
}
if (ti.isVisible) {
this.lastTreeitem = ti;
}
}
};
TreeLinks.prototype.setFocusByFirstCharacter = function (currentItem, _char) {
var start, index;
_char = _char.toLowerCase();
// Get start index for search based on position of currentItem
start = this.treeitems.indexOf(currentItem) + 1;
if (start === this.treeitems.length) {
start = 0;
}
// Check remaining slots in the menu
index = this.getIndexFirstChars(start, _char);
// If not found in remaining slots, check from beginning
if (index === -1) {
index = this.getIndexFirstChars(0, _char);
}
// If match was found...
if (index > -1) {
this.setFocusToItem(this.treeitems[index]);
}
};
TreeLinks.prototype.getIndexFirstChars = function (startIndex, _char) {
for (var i = startIndex; i < this.firstChars.length; i++) {
if (this.treeitems[i].isVisible) {
if (_char === this.firstChars[i]) {
return i;
}
}
}
return -1;
};
return TreeLinks;
})();
/* jshint ignore:end */
/* jscs:enable */
/* eslint-enable */
return component;
})( jQuery );

File diff suppressed because one or more lines are too long

View File

@ -231,7 +231,7 @@ if ( 'POST' === $_SERVER['REQUEST_METHOD'] ) {
</div>
<div id="templateside">
<h2><?php _e( 'Plugin Files' ); ?></h2>
<h2 id="plugin-files-label"><?php _e( 'Plugin Files' ); ?></h2>
<?php
$plugin_editable_files = array();
@ -241,12 +241,14 @@ if ( 'POST' === $_SERVER['REQUEST_METHOD'] ) {
}
}
?>
<ul>
<?php foreach ( $plugin_editable_files as $plugin_file ) : ?>
<li class="<?php echo esc_attr( $file === $plugin_file ? 'notice notice-info' : '' ); ?>">
<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>
</li>
<?php endforeach; ?>
<ul role="tree" aria-labelledby="plugin-files-label">
<li role="treeitem" tabindex="-1" aria-expanded="true"
aria-level="1"
aria-posinset="1"
aria-setsize="1">
<ul role="group" style="padding-left: 0;">
<?php wp_print_plugin_file_tree( wp_make_plugin_file_tree( $plugin_editable_files ) ); ?>
</ul>
</ul>
</div>
<form name="template" id="template" action="plugin-editor.php" method="post">

View File

@ -89,6 +89,14 @@ foreach ( $file_types as $type ) {
}
}
// Move functions.php and style.css to the top.
if ( isset( $allowed_files['functions.php'] ) ) {
$allowed_files = array( 'functions.php' => $allowed_files['functions.php'] ) + $allowed_files;
}
if ( isset( $allowed_files['style.css'] ) ) {
$allowed_files = array( 'style.css' => $allowed_files['style.css'] ) + $allowed_files;
}
if ( empty( $file ) ) {
$relative_file = 'style.css';
$file = $allowed_files['style.css'];
@ -205,63 +213,33 @@ foreach ( wp_get_themes( array( 'errors' => null ) ) as $a_stylesheet => $a_them
if ( $theme->errors() )
echo '<div class="error"><p><strong>' . __( 'This theme is broken.' ) . '</strong> ' . $theme->errors()->get_error_message() . '</p></div>';
?>
<div id="templateside">
<?php
if ( $allowed_files ) :
$previous_file_type = '';
foreach ( $allowed_files as $filename => $absolute_filename ) :
$file_type = substr( $filename, strrpos( $filename, '.' ) );
if ( $file_type !== $previous_file_type ) {
if ( '' !== $previous_file_type ) {
echo "\t</ul>\n";
}
switch ( $file_type ) {
case '.php':
if ( $has_templates || $theme->parent() ) :
echo "\t<h2>" . __( 'Templates' ) . "</h2>\n";
if ( $theme->parent() ) {
echo '<p class="howto">' . sprintf( __( 'This child theme inherits templates from a parent theme, %s.' ),
sprintf( '<a href="%s">%s</a>',
self_admin_url( 'theme-editor.php?theme=' . urlencode( $theme->get_template() ) ),
$theme->parent()->display( 'Name' )
)
) . "</p>\n";
}
endif;
break;
case '.css':
echo "\t<h2>" . _x( 'Styles', 'Theme stylesheets in theme editor' ) . "</h2>\n";
break;
default:
/* translators: %s: file extension */
echo "\t<h2>" . sprintf( __( '%s files' ), $file_type ) . "</h2>\n";
break;
}
echo "\t<ul>\n";
<div id="templateside">
<h2 id="theme-files-label"><?php _e( 'Theme Files' ); ?></h2>
<?php
if ( $has_templates || $theme->parent() ) :
if ( $theme->parent() ) {
/* translators: %s: link to edit parent theme */
echo '<p class="howto">' . sprintf( __( 'This child theme inherits templates from a parent theme, %s.' ),
sprintf( '<a href="%s">%s</a>',
self_admin_url( 'theme-editor.php?theme=' . urlencode( $theme->get_template() ) ),
$theme->parent()->display( 'Name' )
)
) . "</p>\n";
}
$file_description = esc_html( get_file_description( $filename ) );
if ( $filename !== basename( $absolute_filename ) || $file_description !== $filename ) {
$file_description .= '<br /><span class="nonessential">(' . esc_html( $filename ) . ')</span>';
}
if ( $absolute_filename === $file ) {
$file_description = '<span class="notice notice-info">' . $file_description . '</span>';
}
$previous_file_type = $file_type;
?>
<li><a href="theme-editor.php?file=<?php echo urlencode( $filename ) ?>&amp;theme=<?php echo urlencode( $stylesheet ) ?>"><?php echo $file_description; ?></a></li>
<?php
endforeach;
?>
</ul>
<?php endif; ?>
endif;
?>
<ul role="tree" aria-labelledby="theme-files-label">
<li role="treeitem" tabindex="-1" aria-expanded="true"
aria-level="1"
aria-posinset="1"
aria-setsize="1">
<ul role="group" style="padding-left: 0;">
<?php wp_print_theme_file_tree( wp_make_theme_file_tree( $allowed_files ) ); ?>
</ul>
</li>
</ul>
</div>
<?php if ( $error ) :
echo '<div class="error"><p>' . __('Oops, no such file exists! Double check the name and try again, merci.') . '</p></div>';
else : ?>

View File

@ -4,7 +4,7 @@
*
* @global string $wp_version
*/
$wp_version = '4.9-beta2-41850';
$wp_version = '4.9-beta2-41851';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.