diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index a9e23c8b04..de98ac2516 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -330,17 +330,6 @@ celerity_register_resource_map(array( ), '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' => array( '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', ), + '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' => array( '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', ), + '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' => array( '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', ), + 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' => array( 'uri' => '/res/94d20443/rsrc/css/application/slowvote/slowvote.css', diff --git a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php index bfdd855a01..1262b41267 100644 --- a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php @@ -32,7 +32,8 @@ class AphrontDefaultApplicationConfiguration public function getURIMap() { return $this->getResourceURIMapRules() + array( - '/(?:(?Pfeed)/)?$' => 'PhabricatorDirectoryMainController', + '/(?:(?P(?:feed|jump))/)?$' => + 'PhabricatorDirectoryMainController', '/directory/' => array( '(?P\d+)/$' => 'PhabricatorDirectoryCategoryViewController', diff --git a/src/applications/directory/controller/base/PhabricatorDirectoryController.php b/src/applications/directory/controller/base/PhabricatorDirectoryController.php index c426e267dd..62ee1b3272 100644 --- a/src/applications/directory/controller/base/PhabricatorDirectoryController.php +++ b/src/applications/directory/controller/base/PhabricatorDirectoryController.php @@ -44,6 +44,7 @@ abstract class PhabricatorDirectoryController extends PhabricatorController { $nav->addLabel('Phabricator'); $nav->addFilter('home', 'Tactical Command', '/'); + $nav->addFilter('jump', 'Jump Nav'); $nav->addFilter('feed', 'Feed'); $nav->addSpacer(); $nav->addLabel('Applications'); diff --git a/src/applications/directory/controller/main/PhabricatorDirectoryMainController.php b/src/applications/directory/controller/main/PhabricatorDirectoryMainController.php index 951cca84c5..2b65d6da7d 100644 --- a/src/applications/directory/controller/main/PhabricatorDirectoryMainController.php +++ b/src/applications/directory/controller/main/PhabricatorDirectoryMainController.php @@ -37,13 +37,24 @@ class PhabricatorDirectoryMainController $nav = $this->buildNav(); $this->filter = $nav->selectFilter($this->filter, 'home'); - $project_query = new PhabricatorProjectQuery(); - $project_query->setMembers(array($user->getPHID())); - $projects = $project_query->execute(); + switch ($this->filter) { + case 'jump': + break; + case 'home': + case 'feed': + $project_query = new PhabricatorProjectQuery(); + $project_query->setMembers(array($user->getPHID())); + $projects = $project_query->execute(); + break; + default: + throw new Exception("Unknown filter '{$this->filter}'!"); + } switch ($this->filter) { case 'feed': return $this->buildFeedResponse($nav, $projects); + case 'jump': + return $this->buildJumpResponse($nav); default: return $this->buildMainResponse($nav, $projects); } @@ -53,6 +64,7 @@ class PhabricatorDirectoryMainController private function buildMainResponse($nav, $projects) { $unbreak_panel = $this->buildUnbreakNowPanel(); $triage_panel = $this->buildNeedsTriagePanel($projects); + $jump_panel = $this->buildJumpPanel(); $revision_panel = $this->buildRevisionPanel(); $tasks_panel = $this->buildTasksPanel(); $feed_view = $this->buildFeedView($projects, $is_full = false); @@ -61,6 +73,7 @@ class PhabricatorDirectoryMainController $content = array( $unbreak_panel, $triage_panel, + $jump_panel, $revision_panel, $tasks_panel, $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) { $view = $this->buildFeedView($projects, $is_full = true); $nav->appendChild($view); @@ -360,4 +446,76 @@ class PhabricatorDirectoryMainController ''; } + 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 D123 to quickly jump to '. + 'it. See '.$doc_link.' or type help.'); + + $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('
'); + foreach ($nav_buttons as $uri => $name) { + $panel->appendChild( + phutil_render_tag( + 'a', + array( + 'href' => $uri, + 'class' => 'button grey', + ), + phutil_escape_html($name))); + } + $panel->appendChild('
'); + + return $panel; + } + } diff --git a/src/applications/directory/controller/main/__init__.php b/src/applications/directory/controller/main/__init__.php index cef30ecd4d..8f12294a0e 100644 --- a/src/applications/directory/controller/main/__init__.php +++ b/src/applications/directory/controller/main/__init__.php @@ -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/view/revisionlist'); 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/phid/handle/data'); 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/panel'); diff --git a/src/docs/userguide/jump.diviner b/src/docs/userguide/jump.diviner new file mode 100644 index 0000000000..e5910515ab --- /dev/null +++ b/src/docs/userguide/jump.diviner @@ -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. \ No newline at end of file diff --git a/webroot/rsrc/css/application/directory/phabricator-jump-nav.css b/webroot/rsrc/css/application/directory/phabricator-jump-nav.css new file mode 100644 index 0000000000..2e67dfa036 --- /dev/null +++ b/webroot/rsrc/css/application/directory/phabricator-jump-nav.css @@ -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; +} diff --git a/webroot/rsrc/js/application/core/behavior-autofocus.js b/webroot/rsrc/js/application/core/behavior-autofocus.js new file mode 100644 index 0000000000..07f432e7fc --- /dev/null +++ b/webroot/rsrc/js/application/core/behavior-autofocus.js @@ -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) { } +});