1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-10 00:42:41 +01:00

Provide basic keyboard navigation support for Differential.

Summary:
ReviewBoard has a fancier version of this feature that's more granular -- the
keyboard can focus on individual changes. I think that's good and intend to
implement something similar, but this gets us a step closer and gets rid of some
of the bookkeeping stuff like making shortcuts discoverable.

(I have another brnach with Maniphest merging which also uses fatcow icons,
which is why the README seems a little out of context.)

Test Plan:
Used "j" and "k" to jump between changesets. Pressed "?" and got a list of
available shortcuts.

Reviewed By: tuomaspelkonen
Reviewers: aran, jungejason, tuomaspelkonen
CC: moskov, aran, epriestley, tuomaspelkonen
Differential Revision: 412
This commit is contained in:
epriestley 2011-06-08 11:53:10 -07:00
parent 1e5fd3a386
commit 17306b7a92
14 changed files with 259 additions and 68 deletions

View file

@ -204,7 +204,7 @@ celerity_register_resource_map(array(
), ),
'differential-revision-detail-css' => 'differential-revision-detail-css' =>
array( array(
'uri' => '/res/ea9de420/rsrc/css/application/differential/revision-detail.css', 'uri' => '/res/33261274/rsrc/css/application/differential/revision-detail.css',
'type' => 'css', 'type' => 'css',
'requires' => 'requires' =>
array( array(
@ -425,6 +425,18 @@ celerity_register_resource_map(array(
), ),
'disk' => '/rsrc/js/application/differential/behavior-comment-preview.js', 'disk' => '/rsrc/js/application/differential/behavior-comment-preview.js',
), ),
'javelin-behavior-differential-keyboard-navigation' =>
array(
'uri' => '/res/dfa0f979/rsrc/js/application/differential/behavior-keyboard-nav.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'phabricator-keyboard-shortcut',
),
'disk' => '/rsrc/js/application/differential/behavior-keyboard-nav.js',
),
'javelin-behavior-differential-populate' => 'javelin-behavior-differential-populate' =>
array( array(
'uri' => '/res/025171e1/rsrc/js/application/differential/behavior-populate.js', 'uri' => '/res/025171e1/rsrc/js/application/differential/behavior-populate.js',
@ -966,7 +978,7 @@ celerity_register_resource_map(array(
), ),
'phabricator-keyboard-shortcut-manager' => 'phabricator-keyboard-shortcut-manager' =>
array( array(
'uri' => '/res/b32845bd/rsrc/js/application/core/KeyboardShortcutManager.js', 'uri' => '/res/b5d2aa16/rsrc/js/application/core/KeyboardShortcutManager.js',
'type' => 'js', 'type' => 'js',
'requires' => 'requires' =>
array( array(
@ -974,6 +986,7 @@ celerity_register_resource_map(array(
1 => 'javelin-util', 1 => 'javelin-util',
2 => 'javelin-stratcom', 2 => 'javelin-stratcom',
3 => 'javelin-dom', 3 => 'javelin-dom',
4 => 'javelin-vector',
), ),
'disk' => '/rsrc/js/application/core/KeyboardShortcutManager.js', 'disk' => '/rsrc/js/application/core/KeyboardShortcutManager.js',
), ),
@ -1019,7 +1032,7 @@ celerity_register_resource_map(array(
), ),
'phabricator-standard-page-view' => 'phabricator-standard-page-view' =>
array( array(
'uri' => '/res/02ae6920/rsrc/css/application/base/standard-page-view.css', 'uri' => '/res/b90eb694/rsrc/css/application/base/standard-page-view.css',
'type' => 'css', 'type' => 'css',
'requires' => 'requires' =>
array( array(
@ -1057,6 +1070,23 @@ celerity_register_resource_map(array(
'uri' => '/res/pkg/03ef179e/diffusion.pkg.css', 'uri' => '/res/pkg/03ef179e/diffusion.pkg.css',
'type' => 'css', 'type' => 'css',
), ),
'07e754e6' =>
array (
'name' => 'differential.pkg.css',
'symbols' =>
array (
0 => 'differential-core-view-css',
1 => 'differential-changeset-view-css',
2 => 'differential-revision-detail-css',
3 => 'differential-revision-history-css',
4 => 'differential-table-of-contents-css',
5 => 'differential-revision-comment-css',
6 => 'differential-revision-add-comment-css',
7 => 'differential-revision-comment-list-css',
),
'uri' => '/res/pkg/07e754e6/differential.pkg.css',
'type' => 'css',
),
'33f413ef' => '33f413ef' =>
array ( array (
'name' => 'typeahead.pkg.js', 'name' => 'typeahead.pkg.js',
@ -1073,7 +1103,7 @@ celerity_register_resource_map(array(
'uri' => '/res/pkg/33f413ef/typeahead.pkg.js', 'uri' => '/res/pkg/33f413ef/typeahead.pkg.js',
'type' => 'js', 'type' => 'js',
), ),
'64383b02' => 'be386945' =>
array ( array (
'name' => 'core.pkg.css', 'name' => 'core.pkg.css',
'symbols' => 'symbols' =>
@ -1094,42 +1124,9 @@ celerity_register_resource_map(array(
13 => 'phabricator-remarkup-css', 13 => 'phabricator-remarkup-css',
14 => 'syntax-highlighting-css', 14 => 'syntax-highlighting-css',
), ),
'uri' => '/res/pkg/64383b02/core.pkg.css', 'uri' => '/res/pkg/be386945/core.pkg.css',
'type' => 'css', 'type' => 'css',
), ),
'9b16dd9e' =>
array (
'name' => 'differential.pkg.css',
'symbols' =>
array (
0 => 'differential-core-view-css',
1 => 'differential-changeset-view-css',
2 => 'differential-revision-detail-css',
3 => 'differential-revision-history-css',
4 => 'differential-table-of-contents-css',
5 => 'differential-revision-comment-css',
6 => 'differential-revision-add-comment-css',
7 => 'differential-revision-comment-list-css',
),
'uri' => '/res/pkg/9b16dd9e/differential.pkg.css',
'type' => 'css',
),
'bb028d56' =>
array (
'name' => 'workflow.pkg.js',
'symbols' =>
array (
0 => 'javelin-mask',
1 => 'javelin-workflow',
2 => 'javelin-behavior-workflow',
3 => 'javelin-behavior-aphront-form-disable-on-submit',
4 => 'phabricator-keyboard-shortcut-manager',
5 => 'phabricator-keyboard-shortcut',
6 => 'javelin-behavior-phabricator-keyboard-shortcuts',
),
'uri' => '/res/pkg/bb028d56/workflow.pkg.js',
'type' => 'js',
),
'db95a6d0' => 'db95a6d0' =>
array ( array (
'name' => 'javelin.pkg.js', 'name' => 'javelin.pkg.js',
@ -1149,6 +1146,22 @@ celerity_register_resource_map(array(
'uri' => '/res/pkg/db95a6d0/javelin.pkg.js', 'uri' => '/res/pkg/db95a6d0/javelin.pkg.js',
'type' => 'js', 'type' => 'js',
), ),
'e82756c0' =>
array (
'name' => 'workflow.pkg.js',
'symbols' =>
array (
0 => 'javelin-mask',
1 => 'javelin-workflow',
2 => 'javelin-behavior-workflow',
3 => 'javelin-behavior-aphront-form-disable-on-submit',
4 => 'phabricator-keyboard-shortcut-manager',
5 => 'phabricator-keyboard-shortcut',
6 => 'javelin-behavior-phabricator-keyboard-shortcuts',
),
'uri' => '/res/pkg/e82756c0/workflow.pkg.js',
'type' => 'js',
),
'f292b274' => 'f292b274' =>
array ( array (
'name' => 'differential.pkg.js', 'name' => 'differential.pkg.js',
@ -1166,39 +1179,39 @@ celerity_register_resource_map(array(
), ),
'reverse' => 'reverse' =>
array ( array (
'aphront-crumbs-view-css' => '64383b02', 'aphront-crumbs-view-css' => 'be386945',
'aphront-dialog-view-css' => '64383b02', 'aphront-dialog-view-css' => 'be386945',
'aphront-form-view-css' => '64383b02', 'aphront-form-view-css' => 'be386945',
'aphront-list-filter-view-css' => '64383b02', 'aphront-list-filter-view-css' => 'be386945',
'aphront-panel-view-css' => '64383b02', 'aphront-panel-view-css' => 'be386945',
'aphront-side-nav-view-css' => '64383b02', 'aphront-side-nav-view-css' => 'be386945',
'aphront-table-view-css' => '64383b02', 'aphront-table-view-css' => 'be386945',
'aphront-tokenizer-control-css' => '64383b02', 'aphront-tokenizer-control-css' => 'be386945',
'aphront-typeahead-control-css' => '64383b02', 'aphront-typeahead-control-css' => 'be386945',
'differential-changeset-view-css' => '9b16dd9e', 'differential-changeset-view-css' => '07e754e6',
'differential-core-view-css' => '9b16dd9e', 'differential-core-view-css' => '07e754e6',
'differential-revision-add-comment-css' => '9b16dd9e', 'differential-revision-add-comment-css' => '07e754e6',
'differential-revision-comment-css' => '9b16dd9e', 'differential-revision-comment-css' => '07e754e6',
'differential-revision-comment-list-css' => '9b16dd9e', 'differential-revision-comment-list-css' => '07e754e6',
'differential-revision-detail-css' => '9b16dd9e', 'differential-revision-detail-css' => '07e754e6',
'differential-revision-history-css' => '9b16dd9e', 'differential-revision-history-css' => '07e754e6',
'differential-table-of-contents-css' => '9b16dd9e', 'differential-table-of-contents-css' => '07e754e6',
'diffusion-commit-view-css' => '03ef179e', 'diffusion-commit-view-css' => '03ef179e',
'javelin-behavior' => 'db95a6d0', 'javelin-behavior' => 'db95a6d0',
'javelin-behavior-aphront-basic-tokenizer' => '33f413ef', 'javelin-behavior-aphront-basic-tokenizer' => '33f413ef',
'javelin-behavior-aphront-form-disable-on-submit' => 'bb028d56', 'javelin-behavior-aphront-form-disable-on-submit' => 'e82756c0',
'javelin-behavior-differential-diff-radios' => 'f292b274', 'javelin-behavior-differential-diff-radios' => 'f292b274',
'javelin-behavior-differential-edit-inline-comments' => 'f292b274', 'javelin-behavior-differential-edit-inline-comments' => 'f292b274',
'javelin-behavior-differential-feedback-preview' => 'f292b274', 'javelin-behavior-differential-feedback-preview' => 'f292b274',
'javelin-behavior-differential-populate' => 'f292b274', 'javelin-behavior-differential-populate' => 'f292b274',
'javelin-behavior-differential-show-more' => 'f292b274', 'javelin-behavior-differential-show-more' => 'f292b274',
'javelin-behavior-phabricator-keyboard-shortcuts' => 'bb028d56', 'javelin-behavior-phabricator-keyboard-shortcuts' => 'e82756c0',
'javelin-behavior-workflow' => 'bb028d56', 'javelin-behavior-workflow' => 'e82756c0',
'javelin-dom' => 'db95a6d0', 'javelin-dom' => 'db95a6d0',
'javelin-event' => 'db95a6d0', 'javelin-event' => 'db95a6d0',
'javelin-install' => 'db95a6d0', 'javelin-install' => 'db95a6d0',
'javelin-json' => 'db95a6d0', 'javelin-json' => 'db95a6d0',
'javelin-mask' => 'bb028d56', 'javelin-mask' => 'e82756c0',
'javelin-request' => 'db95a6d0', 'javelin-request' => 'db95a6d0',
'javelin-stratcom' => 'db95a6d0', 'javelin-stratcom' => 'db95a6d0',
'javelin-tokenizer' => '33f413ef', 'javelin-tokenizer' => '33f413ef',
@ -1210,14 +1223,14 @@ celerity_register_resource_map(array(
'javelin-uri' => 'db95a6d0', 'javelin-uri' => 'db95a6d0',
'javelin-util' => 'db95a6d0', 'javelin-util' => 'db95a6d0',
'javelin-vector' => 'db95a6d0', 'javelin-vector' => 'db95a6d0',
'javelin-workflow' => 'bb028d56', 'javelin-workflow' => 'e82756c0',
'phabricator-core-buttons-css' => '64383b02', 'phabricator-core-buttons-css' => 'be386945',
'phabricator-core-css' => '64383b02', 'phabricator-core-css' => 'be386945',
'phabricator-directory-css' => '64383b02', 'phabricator-directory-css' => 'be386945',
'phabricator-keyboard-shortcut' => 'bb028d56', 'phabricator-keyboard-shortcut' => 'e82756c0',
'phabricator-keyboard-shortcut-manager' => 'bb028d56', 'phabricator-keyboard-shortcut-manager' => 'e82756c0',
'phabricator-remarkup-css' => '64383b02', 'phabricator-remarkup-css' => 'be386945',
'phabricator-standard-page-view' => '64383b02', 'phabricator-standard-page-view' => 'be386945',
'syntax-highlighting-css' => '64383b02', 'syntax-highlighting-css' => 'be386945',
), ),
)); ));

View file

@ -45,6 +45,7 @@ phutil_register_library_map(array(
'AphrontHeadsupActionView' => 'view/layout/headsup/action', 'AphrontHeadsupActionView' => 'view/layout/headsup/action',
'AphrontIsolatedDatabaseConnection' => 'storage/connection/isolated', 'AphrontIsolatedDatabaseConnection' => 'storage/connection/isolated',
'AphrontIsolatedDatabaseConnectionTestCase' => 'storage/connection/isolated/__tests__', 'AphrontIsolatedDatabaseConnectionTestCase' => 'storage/connection/isolated/__tests__',
'AphrontKeyboardShortcutsAvailableView' => 'view/widget/keyboardshortcuts',
'AphrontListFilterView' => 'view/layout/listfilter', 'AphrontListFilterView' => 'view/layout/listfilter',
'AphrontMySQLDatabaseConnection' => 'storage/connection/mysql', 'AphrontMySQLDatabaseConnection' => 'storage/connection/mysql',
'AphrontNullView' => 'view/null', 'AphrontNullView' => 'view/null',
@ -571,6 +572,7 @@ phutil_register_library_map(array(
'AphrontHeadsupActionView' => 'AphrontView', 'AphrontHeadsupActionView' => 'AphrontView',
'AphrontIsolatedDatabaseConnection' => 'AphrontDatabaseConnection', 'AphrontIsolatedDatabaseConnection' => 'AphrontDatabaseConnection',
'AphrontIsolatedDatabaseConnectionTestCase' => 'PhabricatorTestCase', 'AphrontIsolatedDatabaseConnectionTestCase' => 'PhabricatorTestCase',
'AphrontKeyboardShortcutsAvailableView' => 'AphrontView',
'AphrontListFilterView' => 'AphrontView', 'AphrontListFilterView' => 'AphrontView',
'AphrontMySQLDatabaseConnection' => 'AphrontDatabaseConnection', 'AphrontMySQLDatabaseConnection' => 'AphrontDatabaseConnection',
'AphrontNullView' => 'AphrontView', 'AphrontNullView' => 'AphrontView',

View file

@ -136,6 +136,8 @@ class DifferentialChangesetListView extends AphrontView {
)); ));
} }
Javelin::initBehavior('differential-keyboard-navigation', array());
return return
'<div class="differential-review-stage" id="differential-review-stage">'. '<div class="differential-review-stage" id="differential-review-stage">'.
implode("\n", $output). implode("\n", $output).

View file

@ -82,6 +82,9 @@ final class DifferentialRevisionDetailView extends AphrontView {
return return
'<div class="differential-revision-detail differential-panel">'. '<div class="differential-revision-detail differential-panel">'.
$action_list->render(). $action_list->render().
'<div class="differential-keyboard-shortcuts">'.
id(new AphrontKeyboardShortcutsAvailableView())->render().
'</div>'.
'<div class="differential-revision-detail-core">'. '<div class="differential-revision-detail-core">'.
'<h1>'.phutil_escape_html($revision->getTitle()).'</h1>'. '<h1>'.phutil_escape_html($revision->getTitle()).'</h1>'.
$properties. $properties.

View file

@ -10,6 +10,7 @@ phutil_require_module('phabricator', 'infrastructure/celerity/api');
phutil_require_module('phabricator', 'view/base'); phutil_require_module('phabricator', 'view/base');
phutil_require_module('phabricator', 'view/layout/headsup/action'); phutil_require_module('phabricator', 'view/layout/headsup/action');
phutil_require_module('phabricator', 'view/layout/headsup/actionlist'); phutil_require_module('phabricator', 'view/layout/headsup/actionlist');
phutil_require_module('phabricator', 'view/widget/keyboardshortcuts');
phutil_require_module('phutil', 'markup'); phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'utils'); phutil_require_module('phutil', 'utils');

View file

@ -0,0 +1,31 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class AphrontKeyboardShortcutsAvailableView extends AphrontView {
public function render() {
return
'<table class="keyboard-shortcuts-available">'.
'<tr>'.
'<th>Press <strong>?</strong> to show keyboard shortcuts.</th>'.
'<td></td>'.
'</tr>'.
'</table>';
}
}

View file

@ -0,0 +1,12 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'view/base');
phutil_require_source('AphrontKeyboardShortcutsAvailableView.php');

View file

@ -121,3 +121,32 @@ td.phabricator-login-details {
font-weight: bold; font-weight: bold;
border: 1px solid #555555; border: 1px solid #555555;
} }
.keyboard-focus-focus-reticle {
z-index: 32;
border: 1px solid #999999;
position: absolute;
-webkit-box-shadow: 0 0 3px #000;
-mox-box-shadow: 0 0 3px #000;
box-shadow: 0 0 3px #000;
}
.keyboard-shortcuts-available {
width: 100%;
}
.keyboard-shortcuts-available th {
width: 100%;
vertical-align: middle;
color: #666666;
text-align: right;
padding-right: 8px;
font-size: 11px;
}
.keyboard-shortcuts-available td {
padding: 8px;
background: url('/rsrc/image/icon/fatcow/key_question.png') 0px 0px no-repeat;
}

View file

@ -85,3 +85,7 @@
padding: 4px 8px; padding: 4px 8px;
color: #666666; color: #666666;
} }
.differential-keyboard-shortcuts {
float: right;
}

View file

@ -0,0 +1,11 @@
These icons come from the FatCow icon set:
http://www.fatcow.com/free-icons
They are available under the Creative Commons Attribution 3.0 License:
http://creativecommons.org/licenses/by/3.0/us/
Some icons have been adapted from the FatCow set for use in Phabricator:
key_question.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 B

View file

@ -1,3 +1,7 @@
These icons come from the Tango Desktop Project: These icons come from the Tango Desktop Project:
http://tango.freedesktop.org/ http://tango.freedesktop.org/
They are available in the public domain:
http://tango.freedesktop.org/Frequently_Asked_Questions

View file

@ -4,6 +4,7 @@
* javelin-util * javelin-util
* javelin-stratcom * javelin-stratcom
* javelin-dom * javelin-dom
* javelin-vector
* @javelin * @javelin
*/ */
@ -29,6 +30,7 @@ JX.install('KeyboardShortcutManager', {
members : { members : {
_shortcuts : null, _shortcuts : null,
_focusReticle : null,
/** /**
* Instead of calling this directly, you should call * Instead of calling this directly, you should call
@ -47,6 +49,35 @@ JX.install('KeyboardShortcutManager', {
} }
return desc; return desc;
}, },
/**
* Scroll an element into view.
*/
scrollTo : function(node) {
window.scrollTo(0, JX.$V(node).y - 40);
},
/**
* Move the keyboard shortcut focus to an element.
*/
focusOn : function(node) {
this._clearReticle();
var r = JX.$N('div', {className : 'keyboard-focus-focus-reticle'});
// Outset the reticle 8 pixels away from the element, so there's some
// space between the focused element and the outline.
JX.Vector.getPos(node).add(-8, -8).setPos(r);
JX.Vector.getDim(node).add(16, 16).setDim(r);
document.body.appendChild(r);
this._focusReticle = r;
},
_clearReticle : function() {
this._focusReticle && JX.DOM.remove(this._focusReticle);
this._focusReticle = null;
},
_onkeypress : function(e) { _onkeypress : function(e) {
var raw = e.getRawEvent(); var raw = e.getRawEvent();

View file

@ -0,0 +1,48 @@
/**
* @provides javelin-behavior-differential-keyboard-navigation
* @requires javelin-behavior
* javelin-dom
* phabricator-keyboard-shortcut
*/
JX.behavior('differential-keyboard-navigation', function(config) {
var cursor = null;
var changesets;
function init() {
if (changesets) {
return;
}
changesets = JX.DOM.scry(document.body, 'div', 'differential-changeset');
}
function jump(manager, delta) {
init();
if (cursor === null) {
cursor = -1;
}
cursor = (cursor + changesets.length + delta) % changesets.length;
var selected = changesets[cursor];
manager.scrollTo(selected);
manager.focusOn(selected);
}
new JX.KeyboardShortcut('j', 'Jump to next change.')
.setHandler(function(manager) {
jump(manager, 1);
})
.register();
new JX.KeyboardShortcut('k', 'Jump to previous change.')
.setHandler(function(manager) {
jump(manager, -1);
})
.register();
});