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:
parent
1e5fd3a386
commit
17306b7a92
14 changed files with 259 additions and 68 deletions
|
@ -204,7 +204,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'differential-revision-detail-css' =>
|
||||
array(
|
||||
'uri' => '/res/ea9de420/rsrc/css/application/differential/revision-detail.css',
|
||||
'uri' => '/res/33261274/rsrc/css/application/differential/revision-detail.css',
|
||||
'type' => 'css',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -425,6 +425,18 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'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' =>
|
||||
array(
|
||||
'uri' => '/res/025171e1/rsrc/js/application/differential/behavior-populate.js',
|
||||
|
@ -966,7 +978,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'phabricator-keyboard-shortcut-manager' =>
|
||||
array(
|
||||
'uri' => '/res/b32845bd/rsrc/js/application/core/KeyboardShortcutManager.js',
|
||||
'uri' => '/res/b5d2aa16/rsrc/js/application/core/KeyboardShortcutManager.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -974,6 +986,7 @@ celerity_register_resource_map(array(
|
|||
1 => 'javelin-util',
|
||||
2 => 'javelin-stratcom',
|
||||
3 => 'javelin-dom',
|
||||
4 => 'javelin-vector',
|
||||
),
|
||||
'disk' => '/rsrc/js/application/core/KeyboardShortcutManager.js',
|
||||
),
|
||||
|
@ -1019,7 +1032,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'phabricator-standard-page-view' =>
|
||||
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',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -1057,6 +1070,23 @@ celerity_register_resource_map(array(
|
|||
'uri' => '/res/pkg/03ef179e/diffusion.pkg.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' =>
|
||||
array (
|
||||
'name' => 'typeahead.pkg.js',
|
||||
|
@ -1073,7 +1103,7 @@ celerity_register_resource_map(array(
|
|||
'uri' => '/res/pkg/33f413ef/typeahead.pkg.js',
|
||||
'type' => 'js',
|
||||
),
|
||||
'64383b02' =>
|
||||
'be386945' =>
|
||||
array (
|
||||
'name' => 'core.pkg.css',
|
||||
'symbols' =>
|
||||
|
@ -1094,42 +1124,9 @@ celerity_register_resource_map(array(
|
|||
13 => 'phabricator-remarkup-css',
|
||||
14 => 'syntax-highlighting-css',
|
||||
),
|
||||
'uri' => '/res/pkg/64383b02/core.pkg.css',
|
||||
'uri' => '/res/pkg/be386945/core.pkg.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' =>
|
||||
array (
|
||||
'name' => 'javelin.pkg.js',
|
||||
|
@ -1149,6 +1146,22 @@ celerity_register_resource_map(array(
|
|||
'uri' => '/res/pkg/db95a6d0/javelin.pkg.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' =>
|
||||
array (
|
||||
'name' => 'differential.pkg.js',
|
||||
|
@ -1166,39 +1179,39 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'reverse' =>
|
||||
array (
|
||||
'aphront-crumbs-view-css' => '64383b02',
|
||||
'aphront-dialog-view-css' => '64383b02',
|
||||
'aphront-form-view-css' => '64383b02',
|
||||
'aphront-list-filter-view-css' => '64383b02',
|
||||
'aphront-panel-view-css' => '64383b02',
|
||||
'aphront-side-nav-view-css' => '64383b02',
|
||||
'aphront-table-view-css' => '64383b02',
|
||||
'aphront-tokenizer-control-css' => '64383b02',
|
||||
'aphront-typeahead-control-css' => '64383b02',
|
||||
'differential-changeset-view-css' => '9b16dd9e',
|
||||
'differential-core-view-css' => '9b16dd9e',
|
||||
'differential-revision-add-comment-css' => '9b16dd9e',
|
||||
'differential-revision-comment-css' => '9b16dd9e',
|
||||
'differential-revision-comment-list-css' => '9b16dd9e',
|
||||
'differential-revision-detail-css' => '9b16dd9e',
|
||||
'differential-revision-history-css' => '9b16dd9e',
|
||||
'differential-table-of-contents-css' => '9b16dd9e',
|
||||
'aphront-crumbs-view-css' => 'be386945',
|
||||
'aphront-dialog-view-css' => 'be386945',
|
||||
'aphront-form-view-css' => 'be386945',
|
||||
'aphront-list-filter-view-css' => 'be386945',
|
||||
'aphront-panel-view-css' => 'be386945',
|
||||
'aphront-side-nav-view-css' => 'be386945',
|
||||
'aphront-table-view-css' => 'be386945',
|
||||
'aphront-tokenizer-control-css' => 'be386945',
|
||||
'aphront-typeahead-control-css' => 'be386945',
|
||||
'differential-changeset-view-css' => '07e754e6',
|
||||
'differential-core-view-css' => '07e754e6',
|
||||
'differential-revision-add-comment-css' => '07e754e6',
|
||||
'differential-revision-comment-css' => '07e754e6',
|
||||
'differential-revision-comment-list-css' => '07e754e6',
|
||||
'differential-revision-detail-css' => '07e754e6',
|
||||
'differential-revision-history-css' => '07e754e6',
|
||||
'differential-table-of-contents-css' => '07e754e6',
|
||||
'diffusion-commit-view-css' => '03ef179e',
|
||||
'javelin-behavior' => 'db95a6d0',
|
||||
'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-edit-inline-comments' => 'f292b274',
|
||||
'javelin-behavior-differential-feedback-preview' => 'f292b274',
|
||||
'javelin-behavior-differential-populate' => 'f292b274',
|
||||
'javelin-behavior-differential-show-more' => 'f292b274',
|
||||
'javelin-behavior-phabricator-keyboard-shortcuts' => 'bb028d56',
|
||||
'javelin-behavior-workflow' => 'bb028d56',
|
||||
'javelin-behavior-phabricator-keyboard-shortcuts' => 'e82756c0',
|
||||
'javelin-behavior-workflow' => 'e82756c0',
|
||||
'javelin-dom' => 'db95a6d0',
|
||||
'javelin-event' => 'db95a6d0',
|
||||
'javelin-install' => 'db95a6d0',
|
||||
'javelin-json' => 'db95a6d0',
|
||||
'javelin-mask' => 'bb028d56',
|
||||
'javelin-mask' => 'e82756c0',
|
||||
'javelin-request' => 'db95a6d0',
|
||||
'javelin-stratcom' => 'db95a6d0',
|
||||
'javelin-tokenizer' => '33f413ef',
|
||||
|
@ -1210,14 +1223,14 @@ celerity_register_resource_map(array(
|
|||
'javelin-uri' => 'db95a6d0',
|
||||
'javelin-util' => 'db95a6d0',
|
||||
'javelin-vector' => 'db95a6d0',
|
||||
'javelin-workflow' => 'bb028d56',
|
||||
'phabricator-core-buttons-css' => '64383b02',
|
||||
'phabricator-core-css' => '64383b02',
|
||||
'phabricator-directory-css' => '64383b02',
|
||||
'phabricator-keyboard-shortcut' => 'bb028d56',
|
||||
'phabricator-keyboard-shortcut-manager' => 'bb028d56',
|
||||
'phabricator-remarkup-css' => '64383b02',
|
||||
'phabricator-standard-page-view' => '64383b02',
|
||||
'syntax-highlighting-css' => '64383b02',
|
||||
'javelin-workflow' => 'e82756c0',
|
||||
'phabricator-core-buttons-css' => 'be386945',
|
||||
'phabricator-core-css' => 'be386945',
|
||||
'phabricator-directory-css' => 'be386945',
|
||||
'phabricator-keyboard-shortcut' => 'e82756c0',
|
||||
'phabricator-keyboard-shortcut-manager' => 'e82756c0',
|
||||
'phabricator-remarkup-css' => 'be386945',
|
||||
'phabricator-standard-page-view' => 'be386945',
|
||||
'syntax-highlighting-css' => 'be386945',
|
||||
),
|
||||
));
|
||||
|
|
|
@ -45,6 +45,7 @@ phutil_register_library_map(array(
|
|||
'AphrontHeadsupActionView' => 'view/layout/headsup/action',
|
||||
'AphrontIsolatedDatabaseConnection' => 'storage/connection/isolated',
|
||||
'AphrontIsolatedDatabaseConnectionTestCase' => 'storage/connection/isolated/__tests__',
|
||||
'AphrontKeyboardShortcutsAvailableView' => 'view/widget/keyboardshortcuts',
|
||||
'AphrontListFilterView' => 'view/layout/listfilter',
|
||||
'AphrontMySQLDatabaseConnection' => 'storage/connection/mysql',
|
||||
'AphrontNullView' => 'view/null',
|
||||
|
@ -571,6 +572,7 @@ phutil_register_library_map(array(
|
|||
'AphrontHeadsupActionView' => 'AphrontView',
|
||||
'AphrontIsolatedDatabaseConnection' => 'AphrontDatabaseConnection',
|
||||
'AphrontIsolatedDatabaseConnectionTestCase' => 'PhabricatorTestCase',
|
||||
'AphrontKeyboardShortcutsAvailableView' => 'AphrontView',
|
||||
'AphrontListFilterView' => 'AphrontView',
|
||||
'AphrontMySQLDatabaseConnection' => 'AphrontDatabaseConnection',
|
||||
'AphrontNullView' => 'AphrontView',
|
||||
|
|
|
@ -136,6 +136,8 @@ class DifferentialChangesetListView extends AphrontView {
|
|||
));
|
||||
}
|
||||
|
||||
Javelin::initBehavior('differential-keyboard-navigation', array());
|
||||
|
||||
return
|
||||
'<div class="differential-review-stage" id="differential-review-stage">'.
|
||||
implode("\n", $output).
|
||||
|
|
|
@ -82,6 +82,9 @@ final class DifferentialRevisionDetailView extends AphrontView {
|
|||
return
|
||||
'<div class="differential-revision-detail differential-panel">'.
|
||||
$action_list->render().
|
||||
'<div class="differential-keyboard-shortcuts">'.
|
||||
id(new AphrontKeyboardShortcutsAvailableView())->render().
|
||||
'</div>'.
|
||||
'<div class="differential-revision-detail-core">'.
|
||||
'<h1>'.phutil_escape_html($revision->getTitle()).'</h1>'.
|
||||
$properties.
|
||||
|
|
|
@ -10,6 +10,7 @@ phutil_require_module('phabricator', 'infrastructure/celerity/api');
|
|||
phutil_require_module('phabricator', 'view/base');
|
||||
phutil_require_module('phabricator', 'view/layout/headsup/action');
|
||||
phutil_require_module('phabricator', 'view/layout/headsup/actionlist');
|
||||
phutil_require_module('phabricator', 'view/widget/keyboardshortcuts');
|
||||
|
||||
phutil_require_module('phutil', 'markup');
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
|
|
@ -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>';
|
||||
}
|
||||
|
||||
}
|
12
src/view/widget/keyboardshortcuts/__init__.php
Normal file
12
src/view/widget/keyboardshortcuts/__init__.php
Normal 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');
|
|
@ -121,3 +121,32 @@ td.phabricator-login-details {
|
|||
font-weight: bold;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -85,3 +85,7 @@
|
|||
padding: 4px 8px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.differential-keyboard-shortcuts {
|
||||
float: right;
|
||||
}
|
||||
|
|
11
webroot/rsrc/image/icon/fatcow/README
Normal file
11
webroot/rsrc/image/icon/fatcow/README
Normal 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
|
BIN
webroot/rsrc/image/icon/fatcow/key_question.png
Normal file
BIN
webroot/rsrc/image/icon/fatcow/key_question.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 516 B |
|
@ -1,3 +1,7 @@
|
|||
These icons come from the Tango Desktop Project:
|
||||
|
||||
http://tango.freedesktop.org/
|
||||
|
||||
They are available in the public domain:
|
||||
|
||||
http://tango.freedesktop.org/Frequently_Asked_Questions
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* javelin-util
|
||||
* javelin-stratcom
|
||||
* javelin-dom
|
||||
* javelin-vector
|
||||
* @javelin
|
||||
*/
|
||||
|
||||
|
@ -29,6 +30,7 @@ JX.install('KeyboardShortcutManager', {
|
|||
|
||||
members : {
|
||||
_shortcuts : null,
|
||||
_focusReticle : null,
|
||||
|
||||
/**
|
||||
* Instead of calling this directly, you should call
|
||||
|
@ -47,6 +49,35 @@ JX.install('KeyboardShortcutManager', {
|
|||
}
|
||||
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) {
|
||||
var raw = e.getRawEvent();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
});
|
||||
|
Loading…
Reference in a new issue