Add a "jump nav" element to the homepage, for quick tool/object navigation
Summary: - Restore quick methods for getting to common features (upload file, create task, etc.) - Provide a flexible cli-like navigation element similar to stuff used at Facebook (bunny1 / lolbunny). Test Plan: Used jump nav and nav buttons. Reviewers: btrahan, fratrik Reviewed By: btrahan CC: aran, epriestley Differential Revision: https://secure.phabricator.com/D1619
This commit is contained in:
@@ -330,17 +330,6 @@ celerity_register_resource_map(array(
|
|||||||
),
|
),
|
||||||
'disk' => '/rsrc/js/javelin/lib/behavior.js',
|
'disk' => '/rsrc/js/javelin/lib/behavior.js',
|
||||||
),
|
),
|
||||||
0 =>
|
|
||||||
array(
|
|
||||||
'uri' => '/res/b6096fdd/rsrc/js/javelin/lib/__tests__/URI.js',
|
|
||||||
'type' => 'js',
|
|
||||||
'requires' =>
|
|
||||||
array(
|
|
||||||
0 => 'javelin-uri',
|
|
||||||
1 => 'javelin-php-serializer',
|
|
||||||
),
|
|
||||||
'disk' => '/rsrc/js/javelin/lib/__tests__/URI.js',
|
|
||||||
),
|
|
||||||
'javelin-behavior-aphront-basic-tokenizer' =>
|
'javelin-behavior-aphront-basic-tokenizer' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/9be30797/rsrc/js/application/core/behavior-tokenizer.js',
|
'uri' => '/res/9be30797/rsrc/js/application/core/behavior-tokenizer.js',
|
||||||
@@ -704,6 +693,17 @@ celerity_register_resource_map(array(
|
|||||||
),
|
),
|
||||||
'disk' => '/rsrc/js/application/owners/owners-path-editor.js',
|
'disk' => '/rsrc/js/application/owners/owners-path-editor.js',
|
||||||
),
|
),
|
||||||
|
'javelin-behavior-phabricator-autofocus' =>
|
||||||
|
array(
|
||||||
|
'uri' => '/res/2946bb89/rsrc/js/application/core/behavior-autofocus.js',
|
||||||
|
'type' => 'js',
|
||||||
|
'requires' =>
|
||||||
|
array(
|
||||||
|
0 => 'javelin-behavior',
|
||||||
|
1 => 'javelin-dom',
|
||||||
|
),
|
||||||
|
'disk' => '/rsrc/js/application/core/behavior-autofocus.js',
|
||||||
|
),
|
||||||
'javelin-behavior-phabricator-keyboard-pager' =>
|
'javelin-behavior-phabricator-keyboard-pager' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/56d64eff/rsrc/js/application/core/behavior-keyboard-pager.js',
|
'uri' => '/res/56d64eff/rsrc/js/application/core/behavior-keyboard-pager.js',
|
||||||
@@ -1413,6 +1413,15 @@ celerity_register_resource_map(array(
|
|||||||
),
|
),
|
||||||
'disk' => '/rsrc/css/application/feed/feed.css',
|
'disk' => '/rsrc/css/application/feed/feed.css',
|
||||||
),
|
),
|
||||||
|
'phabricator-jump-nav' =>
|
||||||
|
array(
|
||||||
|
'uri' => '/res/69238d2f/rsrc/css/application/directory/phabricator-jump-nav.css',
|
||||||
|
'type' => 'css',
|
||||||
|
'requires' =>
|
||||||
|
array(
|
||||||
|
),
|
||||||
|
'disk' => '/rsrc/css/application/directory/phabricator-jump-nav.css',
|
||||||
|
),
|
||||||
'phabricator-keyboard-shortcut' =>
|
'phabricator-keyboard-shortcut' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/beed38cd/rsrc/js/application/core/KeyboardShortcut.js',
|
'uri' => '/res/beed38cd/rsrc/js/application/core/KeyboardShortcut.js',
|
||||||
@@ -1520,6 +1529,17 @@ celerity_register_resource_map(array(
|
|||||||
),
|
),
|
||||||
'disk' => '/rsrc/js/application/core/ShapedRequest.js',
|
'disk' => '/rsrc/js/application/core/ShapedRequest.js',
|
||||||
),
|
),
|
||||||
|
0 =>
|
||||||
|
array(
|
||||||
|
'uri' => '/res/b6096fdd/rsrc/js/javelin/lib/__tests__/URI.js',
|
||||||
|
'type' => 'js',
|
||||||
|
'requires' =>
|
||||||
|
array(
|
||||||
|
0 => 'javelin-uri',
|
||||||
|
1 => 'javelin-php-serializer',
|
||||||
|
),
|
||||||
|
'disk' => '/rsrc/js/javelin/lib/__tests__/URI.js',
|
||||||
|
),
|
||||||
'phabricator-slowvote-css' =>
|
'phabricator-slowvote-css' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/94d20443/rsrc/css/application/slowvote/slowvote.css',
|
'uri' => '/res/94d20443/rsrc/css/application/slowvote/slowvote.css',
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ class AphrontDefaultApplicationConfiguration
|
|||||||
|
|
||||||
public function getURIMap() {
|
public function getURIMap() {
|
||||||
return $this->getResourceURIMapRules() + array(
|
return $this->getResourceURIMapRules() + array(
|
||||||
'/(?:(?P<filter>feed)/)?$' => 'PhabricatorDirectoryMainController',
|
'/(?:(?P<filter>(?:feed|jump))/)?$' =>
|
||||||
|
'PhabricatorDirectoryMainController',
|
||||||
'/directory/' => array(
|
'/directory/' => array(
|
||||||
'(?P<id>\d+)/$'
|
'(?P<id>\d+)/$'
|
||||||
=> 'PhabricatorDirectoryCategoryViewController',
|
=> 'PhabricatorDirectoryCategoryViewController',
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ abstract class PhabricatorDirectoryController extends PhabricatorController {
|
|||||||
|
|
||||||
$nav->addLabel('Phabricator');
|
$nav->addLabel('Phabricator');
|
||||||
$nav->addFilter('home', 'Tactical Command', '/');
|
$nav->addFilter('home', 'Tactical Command', '/');
|
||||||
|
$nav->addFilter('jump', 'Jump Nav');
|
||||||
$nav->addFilter('feed', 'Feed');
|
$nav->addFilter('feed', 'Feed');
|
||||||
$nav->addSpacer();
|
$nav->addSpacer();
|
||||||
$nav->addLabel('Applications');
|
$nav->addLabel('Applications');
|
||||||
|
|||||||
@@ -37,13 +37,24 @@ class PhabricatorDirectoryMainController
|
|||||||
$nav = $this->buildNav();
|
$nav = $this->buildNav();
|
||||||
$this->filter = $nav->selectFilter($this->filter, 'home');
|
$this->filter = $nav->selectFilter($this->filter, 'home');
|
||||||
|
|
||||||
|
switch ($this->filter) {
|
||||||
|
case 'jump':
|
||||||
|
break;
|
||||||
|
case 'home':
|
||||||
|
case 'feed':
|
||||||
$project_query = new PhabricatorProjectQuery();
|
$project_query = new PhabricatorProjectQuery();
|
||||||
$project_query->setMembers(array($user->getPHID()));
|
$project_query->setMembers(array($user->getPHID()));
|
||||||
$projects = $project_query->execute();
|
$projects = $project_query->execute();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception("Unknown filter '{$this->filter}'!");
|
||||||
|
}
|
||||||
|
|
||||||
switch ($this->filter) {
|
switch ($this->filter) {
|
||||||
case 'feed':
|
case 'feed':
|
||||||
return $this->buildFeedResponse($nav, $projects);
|
return $this->buildFeedResponse($nav, $projects);
|
||||||
|
case 'jump':
|
||||||
|
return $this->buildJumpResponse($nav);
|
||||||
default:
|
default:
|
||||||
return $this->buildMainResponse($nav, $projects);
|
return $this->buildMainResponse($nav, $projects);
|
||||||
}
|
}
|
||||||
@@ -53,6 +64,7 @@ class PhabricatorDirectoryMainController
|
|||||||
private function buildMainResponse($nav, $projects) {
|
private function buildMainResponse($nav, $projects) {
|
||||||
$unbreak_panel = $this->buildUnbreakNowPanel();
|
$unbreak_panel = $this->buildUnbreakNowPanel();
|
||||||
$triage_panel = $this->buildNeedsTriagePanel($projects);
|
$triage_panel = $this->buildNeedsTriagePanel($projects);
|
||||||
|
$jump_panel = $this->buildJumpPanel();
|
||||||
$revision_panel = $this->buildRevisionPanel();
|
$revision_panel = $this->buildRevisionPanel();
|
||||||
$tasks_panel = $this->buildTasksPanel();
|
$tasks_panel = $this->buildTasksPanel();
|
||||||
$feed_view = $this->buildFeedView($projects, $is_full = false);
|
$feed_view = $this->buildFeedView($projects, $is_full = false);
|
||||||
@@ -61,6 +73,7 @@ class PhabricatorDirectoryMainController
|
|||||||
$content = array(
|
$content = array(
|
||||||
$unbreak_panel,
|
$unbreak_panel,
|
||||||
$triage_panel,
|
$triage_panel,
|
||||||
|
$jump_panel,
|
||||||
$revision_panel,
|
$revision_panel,
|
||||||
$tasks_panel,
|
$tasks_panel,
|
||||||
$feed_view,
|
$feed_view,
|
||||||
@@ -75,6 +88,79 @@ class PhabricatorDirectoryMainController
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildJumpResponse($nav) {
|
||||||
|
$request = $this->getRequest();
|
||||||
|
|
||||||
|
if ($request->isFormPost()) {
|
||||||
|
$jump = $request->getStr('jump');
|
||||||
|
$jump = trim($jump);
|
||||||
|
|
||||||
|
$help_href = PhabricatorEnv::getDocLink(
|
||||||
|
'articles/Jump_Nav_User_Guide.html');
|
||||||
|
|
||||||
|
$patterns = array(
|
||||||
|
'/^help/i' => 'uri:'.$help_href,
|
||||||
|
'/^d$/i' => 'uri:/differential/',
|
||||||
|
'/^r$/i' => 'uri:/diffusion/',
|
||||||
|
'/^t$/i' => 'uri:/maniphest/',
|
||||||
|
'/r([A-Z]+)$/' => 'repository',
|
||||||
|
'/r([A-Z]+)(\S+)$/' => 'commit',
|
||||||
|
'/^d(\d+)$/i' => 'revision',
|
||||||
|
'/^t(\d+)$/i' => 'task',
|
||||||
|
|
||||||
|
// TODO: '/^p$/i' => 'uri:/projects/',
|
||||||
|
// TODO: '/^u$/i' => 'uri:/people/',
|
||||||
|
// TODO: '/^p\s+(\S+)$/i' => 'project',
|
||||||
|
// TODO: '/^u\s+(\S+)$/i' => 'user',
|
||||||
|
// TODO: '/^task:\s+(\S+)/i' => 'create-task',
|
||||||
|
// TODO: '/^(?:s|symbol)\s+(\S+)/i' => 'find-symbol',
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
foreach ($patterns as $pattern => $effect) {
|
||||||
|
$matches = null;
|
||||||
|
if (preg_match($pattern, $jump, $matches)) {
|
||||||
|
if (!strncmp($effect, 'uri:', 4)) {
|
||||||
|
return id(new AphrontRedirectResponse())
|
||||||
|
->setURI(substr($effect, 4));
|
||||||
|
} else {
|
||||||
|
switch ($effect) {
|
||||||
|
case 'repository':
|
||||||
|
return id(new AphrontRedirectResponse())
|
||||||
|
->setURI('/diffusion/'.$matches[1].'/');
|
||||||
|
case 'commit':
|
||||||
|
return id(new AphrontRedirectResponse())
|
||||||
|
->setURI('/'.$matches[0]);
|
||||||
|
case 'revision':
|
||||||
|
return id(new AphrontRedirectResponse())
|
||||||
|
->setURI('/D'.$matches[1]);
|
||||||
|
case 'task':
|
||||||
|
return id(new AphrontRedirectResponse())
|
||||||
|
->setURI('/T'.$matches[1]);
|
||||||
|
default:
|
||||||
|
throw new Exception("Unknown jump effect '{$effect}'!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = new PhabricatorSearchQuery();
|
||||||
|
$query->setQuery($jump);
|
||||||
|
$query->save();
|
||||||
|
|
||||||
|
return id(new AphrontRedirectResponse())
|
||||||
|
->setURI('/search/'.$query->getQueryKey().'/');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$nav->appendChild($this->buildJumpPanel());
|
||||||
|
return $this->buildStandardPageResponse(
|
||||||
|
$nav,
|
||||||
|
array(
|
||||||
|
'title' => 'Jump Nav',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
private function buildFeedResponse($nav, $projects) {
|
private function buildFeedResponse($nav, $projects) {
|
||||||
$view = $this->buildFeedView($projects, $is_full = true);
|
$view = $this->buildFeedView($projects, $is_full = true);
|
||||||
$nav->appendChild($view);
|
$nav->appendChild($view);
|
||||||
@@ -360,4 +446,76 @@ class PhabricatorDirectoryMainController
|
|||||||
'</div>';
|
'</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildJumpPanel() {
|
||||||
|
$request = $this->getRequest();
|
||||||
|
$user = $request->getUser();
|
||||||
|
|
||||||
|
$uniq_id = celerity_generate_unique_node_id();
|
||||||
|
|
||||||
|
Javelin::initBehavior(
|
||||||
|
'phabricator-autofocus',
|
||||||
|
array(
|
||||||
|
'id' => $uniq_id,
|
||||||
|
));
|
||||||
|
|
||||||
|
require_celerity_resource('phabricator-jump-nav');
|
||||||
|
|
||||||
|
$doc_href = PhabricatorEnv::getDocLink('articles/Jump_Nav_User_Guide.html');
|
||||||
|
$doc_link = phutil_render_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'href' => $doc_href,
|
||||||
|
),
|
||||||
|
'Jump Nav Use Guide');
|
||||||
|
|
||||||
|
$jump_input = phutil_render_tag(
|
||||||
|
'input',
|
||||||
|
array(
|
||||||
|
'type' => 'text',
|
||||||
|
'class' => 'phabricator-jump-nav',
|
||||||
|
'name' => 'jump',
|
||||||
|
'id' => $uniq_id,
|
||||||
|
)).
|
||||||
|
phutil_render_tag(
|
||||||
|
'p',
|
||||||
|
array(
|
||||||
|
'class' => 'phabricator-jump-nav-caption',
|
||||||
|
),
|
||||||
|
'Enter the name of an object like <tt>D123</tt> to quickly jump to '.
|
||||||
|
'it. See '.$doc_link.' or type <tt>help</tt>.');
|
||||||
|
|
||||||
|
$panel = new AphrontPanelView();
|
||||||
|
$panel->appendChild(
|
||||||
|
phabricator_render_form(
|
||||||
|
$user,
|
||||||
|
array(
|
||||||
|
'action' => '/jump/',
|
||||||
|
'method' => 'POST',
|
||||||
|
),
|
||||||
|
$jump_input));
|
||||||
|
|
||||||
|
$nav_buttons = array(
|
||||||
|
'/maniphest/task/create/' => 'Create a Task',
|
||||||
|
'/file/' => 'Upload a File',
|
||||||
|
'/paste/' => 'Create Paste',
|
||||||
|
'/w/' => 'Browse Wiki',
|
||||||
|
'/diffusion/' => 'Browse Code',
|
||||||
|
);
|
||||||
|
|
||||||
|
$panel->appendChild('<div class="phabricator-jump-nav-buttons">');
|
||||||
|
foreach ($nav_buttons as $uri => $name) {
|
||||||
|
$panel->appendChild(
|
||||||
|
phutil_render_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'href' => $uri,
|
||||||
|
'class' => 'button grey',
|
||||||
|
),
|
||||||
|
phutil_escape_html($name)));
|
||||||
|
}
|
||||||
|
$panel->appendChild('</div>');
|
||||||
|
|
||||||
|
return $panel;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||||
phutil_require_module('phabricator', 'applications/differential/query/revision');
|
phutil_require_module('phabricator', 'applications/differential/query/revision');
|
||||||
phutil_require_module('phabricator', 'applications/differential/view/revisionlist');
|
phutil_require_module('phabricator', 'applications/differential/view/revisionlist');
|
||||||
phutil_require_module('phabricator', 'applications/directory/controller/base');
|
phutil_require_module('phabricator', 'applications/directory/controller/base');
|
||||||
@@ -17,6 +18,11 @@ phutil_require_module('phabricator', 'applications/maniphest/query');
|
|||||||
phutil_require_module('phabricator', 'applications/maniphest/view/tasklist');
|
phutil_require_module('phabricator', 'applications/maniphest/view/tasklist');
|
||||||
phutil_require_module('phabricator', 'applications/phid/handle/data');
|
phutil_require_module('phabricator', 'applications/phid/handle/data');
|
||||||
phutil_require_module('phabricator', 'applications/project/query/project');
|
phutil_require_module('phabricator', 'applications/project/query/project');
|
||||||
|
phutil_require_module('phabricator', 'applications/search/storage/query');
|
||||||
|
phutil_require_module('phabricator', 'infrastructure/celerity/api');
|
||||||
|
phutil_require_module('phabricator', 'infrastructure/env');
|
||||||
|
phutil_require_module('phabricator', 'infrastructure/javelin/api');
|
||||||
|
phutil_require_module('phabricator', 'infrastructure/javelin/markup');
|
||||||
phutil_require_module('phabricator', 'view/layout/minipanel');
|
phutil_require_module('phabricator', 'view/layout/minipanel');
|
||||||
phutil_require_module('phabricator', 'view/layout/panel');
|
phutil_require_module('phabricator', 'view/layout/panel');
|
||||||
|
|
||||||
|
|||||||
21
src/docs/userguide/jump.diviner
Normal file
21
src/docs/userguide/jump.diviner
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
@title Jump Nav User Guide
|
||||||
|
@group userguide
|
||||||
|
|
||||||
|
Command reference for the jump nav.
|
||||||
|
|
||||||
|
= Overview =
|
||||||
|
|
||||||
|
The jump nav provides a quick way to navigate to tools and objects: just type
|
||||||
|
a navigational command into the box and press return.
|
||||||
|
|
||||||
|
= Supported Commands =
|
||||||
|
|
||||||
|
- **help** - Jump to this document.
|
||||||
|
- **T** - Jump to Maniphest.
|
||||||
|
- **T123** - Jump to Maniphest Task 123.
|
||||||
|
- **D** - Jump to Differential.
|
||||||
|
- **D123** - Jump to Differential Revision 123.
|
||||||
|
- **r** - Jump to Diffusion.
|
||||||
|
- **rXYZ** - Jump to Diffusion Repository XYZ.
|
||||||
|
- **rXYZabcdef** - Jump to Diffusion Commit rXYZabcdef.
|
||||||
|
- **(default)** - Search for input.
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* @provides phabricator-jump-nav
|
||||||
|
*/
|
||||||
|
|
||||||
|
.phabricator-jump-nav {
|
||||||
|
font-size: 18px;
|
||||||
|
width: 98%;
|
||||||
|
padding: .25em 1%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phabricator-jump-nav-caption {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #666666;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phabricator-jump-nav-buttons {
|
||||||
|
margin-top: 1em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phabricator-jump-nav-buttons a.button {
|
||||||
|
margin-right: .75em;
|
||||||
|
width: 125px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
8
webroot/rsrc/js/application/core/behavior-autofocus.js
Normal file
8
webroot/rsrc/js/application/core/behavior-autofocus.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* @provides javelin-behavior-phabricator-autofocus
|
||||||
|
* @requires javelin-behavior javelin-dom
|
||||||
|
*/
|
||||||
|
|
||||||
|
JX.behavior('phabricator-autofocus', function(config) {
|
||||||
|
try { JX.$(config.id).focus(); } catch (x) { }
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user