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

Change All Search Boxes into Jump Navs

Summary:
- all search boxes are now jump navs (old functionality retained if none
  of the jump nav patterns match)
- added global keyboard shortcut '/' to focus the search box in the upper
  right

Test Plan:
- pressed '/' and noticed the search box gains keyboard focus
- triggered jump nav functionality from search box and saw it worked
- did a search which did not match a jump nav pattern and saw it worked
  (and searched in the selected context)
NOTE: The search box on the /search/ page is also changed to have jump
nav functionality. Old functionality is not impared. Still, this may not
be desirable.

Reviewers: epriestley, btrahan

Reviewed By: epriestley

CC: aran, epriestley

Differential Revision: https://secure.phabricator.com/D1794
This commit is contained in:
David Fisher 2012-03-05 19:51:16 -08:00 committed by epriestley
parent 831133e880
commit 639ed0faa6
13 changed files with 242 additions and 212 deletions

View file

@ -788,7 +788,7 @@ celerity_register_resource_map(array(
),
'javelin-behavior-phabricator-keyboard-shortcuts' =>
array(
'uri' => '/res/94b009e2/rsrc/js/application/core/behavior-keyboard-shortcuts.js',
'uri' => '/res/ea3ea05e/rsrc/js/application/core/behavior-keyboard-shortcuts.js',
'type' => 'js',
'requires' =>
array(
@ -1542,7 +1542,7 @@ celerity_register_resource_map(array(
),
'phabricator-keyboard-shortcut-manager' =>
array(
'uri' => '/res/04767571/rsrc/js/application/core/KeyboardShortcutManager.js',
'uri' => '/res/0be80136/rsrc/js/application/core/KeyboardShortcutManager.js',
'type' => 'js',
'requires' =>
array(
@ -1984,28 +1984,6 @@ celerity_register_resource_map(array(
'uri' => '/res/pkg/8d8e1030/differential.pkg.js',
'type' => 'js',
),
'adcb05b5' =>
array(
'name' => 'core.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',
7 => 'javelin-behavior-refresh-csrf',
8 => 'javelin-behavior-phabricator-watch-anchor',
9 => 'javelin-behavior-phabricator-autofocus',
10 => 'phabricator-paste-file-upload',
11 => 'phabricator-menu-item',
12 => 'phabricator-dropdown-menu',
),
'uri' => '/res/pkg/adcb05b5/core.pkg.js',
'type' => 'js',
),
'ae7ea233' =>
array(
'name' => 'typeahead.pkg.js',
@ -2063,6 +2041,28 @@ celerity_register_resource_map(array(
'uri' => '/res/pkg/31583232/maniphest.pkg.css',
'type' => 'css',
),
95944588 =>
array(
'name' => 'core.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',
7 => 'javelin-behavior-refresh-csrf',
8 => 'javelin-behavior-phabricator-watch-anchor',
9 => 'javelin-behavior-phabricator-autofocus',
10 => 'phabricator-paste-file-upload',
11 => 'phabricator-menu-item',
12 => 'phabricator-dropdown-menu',
),
'uri' => '/res/pkg/95944588/core.pkg.js',
'type' => 'js',
),
),
'reverse' =>
array(
@ -2093,7 +2093,7 @@ celerity_register_resource_map(array(
'javelin-behavior-aphront-basic-tokenizer' => 'ae7ea233',
'javelin-behavior-aphront-drag-and-drop' => '8d8e1030',
'javelin-behavior-aphront-drag-and-drop-textarea' => '8d8e1030',
'javelin-behavior-aphront-form-disable-on-submit' => 'adcb05b5',
'javelin-behavior-aphront-form-disable-on-submit' => '95944588',
'javelin-behavior-buoyant' => '8d8e1030',
'javelin-behavior-differential-accept-with-errors' => '8d8e1030',
'javelin-behavior-differential-add-reviewers-and-ccs' => '8d8e1030',
@ -2109,17 +2109,17 @@ celerity_register_resource_map(array(
'javelin-behavior-maniphest-transaction-controls' => '0ed3e020',
'javelin-behavior-maniphest-transaction-expand' => '0ed3e020',
'javelin-behavior-maniphest-transaction-preview' => '0ed3e020',
'javelin-behavior-phabricator-autofocus' => 'adcb05b5',
'javelin-behavior-phabricator-keyboard-shortcuts' => 'adcb05b5',
'javelin-behavior-phabricator-autofocus' => '95944588',
'javelin-behavior-phabricator-keyboard-shortcuts' => '95944588',
'javelin-behavior-phabricator-object-selector' => '8d8e1030',
'javelin-behavior-phabricator-watch-anchor' => 'adcb05b5',
'javelin-behavior-refresh-csrf' => 'adcb05b5',
'javelin-behavior-workflow' => 'adcb05b5',
'javelin-behavior-phabricator-watch-anchor' => '95944588',
'javelin-behavior-refresh-csrf' => '95944588',
'javelin-behavior-workflow' => '95944588',
'javelin-dom' => '4fbae2af',
'javelin-event' => '4fbae2af',
'javelin-install' => '4fbae2af',
'javelin-json' => '4fbae2af',
'javelin-mask' => 'adcb05b5',
'javelin-mask' => '95944588',
'javelin-request' => '4fbae2af',
'javelin-stratcom' => '4fbae2af',
'javelin-tokenizer' => 'ae7ea233',
@ -2131,7 +2131,7 @@ celerity_register_resource_map(array(
'javelin-uri' => '4fbae2af',
'javelin-util' => '4fbae2af',
'javelin-vector' => '4fbae2af',
'javelin-workflow' => 'adcb05b5',
'javelin-workflow' => '95944588',
'maniphest-task-detail-css' => '31583232',
'maniphest-task-summary-css' => '31583232',
'maniphest-transaction-detail-css' => '31583232',
@ -2141,13 +2141,13 @@ celerity_register_resource_map(array(
'phabricator-core-css' => 'e2934828',
'phabricator-directory-css' => 'e2934828',
'phabricator-drag-and-drop-file-upload' => '8d8e1030',
'phabricator-dropdown-menu' => 'adcb05b5',
'phabricator-dropdown-menu' => '95944588',
'phabricator-jump-nav' => 'e2934828',
'phabricator-keyboard-shortcut' => 'adcb05b5',
'phabricator-keyboard-shortcut-manager' => 'adcb05b5',
'phabricator-menu-item' => 'adcb05b5',
'phabricator-keyboard-shortcut' => '95944588',
'phabricator-keyboard-shortcut-manager' => '95944588',
'phabricator-menu-item' => '95944588',
'phabricator-object-selector-css' => '09c86840',
'phabricator-paste-file-upload' => 'adcb05b5',
'phabricator-paste-file-upload' => '95944588',
'phabricator-remarkup-css' => 'e2934828',
'phabricator-shaped-request' => '8d8e1030',
'phabricator-standard-page-view' => 'e2934828',

View file

@ -584,6 +584,7 @@ phutil_register_library_map(array(
'PhabricatorImageTransformer' => 'applications/files/transform',
'PhabricatorInfrastructureTestCase' => 'infrastructure/__tests__',
'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/javelin',
'PhabricatorJumpNavHandler' => 'applications/search/engine/jumpnav',
'PhabricatorLintEngine' => 'infrastructure/lint/engine',
'PhabricatorLiskDAO' => 'applications/base/storage/lisk',
'PhabricatorLocalDiskFileStorageEngine' => 'applications/files/engine/localdisk',
@ -696,7 +697,6 @@ phutil_register_library_map(array(
'PhabricatorProjectProfileController' => 'applications/project/controller/profile',
'PhabricatorProjectProfileEditController' => 'applications/project/controller/profileedit',
'PhabricatorProjectQuery' => 'applications/project/query/project',
'PhabricatorProjectQueryUtil' => 'applications/project/query/util',
'PhabricatorProjectStatus' => 'applications/project/constants/status',
'PhabricatorProjectSubproject' => 'applications/project/storage/subproject',
'PhabricatorProjectTransaction' => 'applications/project/storage/transaction',

View file

@ -1,7 +1,7 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,6 +18,8 @@
/**
* @group console
* @phutil-external-symbol function xhprof_enable
* @phutil-external-symbol function xhprof_disable
*/
final class DarkConsoleXHProfPluginAPI {

View file

@ -105,76 +105,11 @@ class PhabricatorDirectoryMainController
if ($request->isFormPost()) {
$jump = $request->getStr('jump');
$jump = trim($jump);
$help_href = PhabricatorEnv::getDocLink(
'article/Jump_Nav_User_Guide.html');
$patterns = array(
'/^help/i' => 'uri:'.$help_href,
'/^d$/i' => 'uri:/differential/',
'/^r$/i' => 'uri:/diffusion/',
'/^t$/i' => 'uri:/maniphest/',
'/^p$/i' => 'uri:/project/',
'/^u$/i' => 'uri:/people/',
'/^r([A-Z]+)$/' => 'repository',
'/^r([A-Z]+)(\S+)$/' => 'commit',
'/^d(\d+)$/i' => 'revision',
'/^t(\d+)$/i' => 'task',
'/^p\s+(.+)$/i' => 'project',
'/^u\s+(\S+)$/i' => 'user',
'/^task:\s*(.+)/i' => 'create-task',
'/^(?: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));
$response = PhabricatorJumpNavHandler::jumpPostResponse($jump);
if ($response) {
return $response;
} 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]);
case 'user':
return id(new AphrontRedirectResponse())
->setURI('/p/'.$matches[1].'/');
case 'project':
$project = PhabricatorProjectQueryUtil
::findCloselyNamedProject($matches[1]);
if ($project) {
return id(new AphrontRedirectResponse())
->setURI('/project/view/'.$project->getID().'/');
} else {
$jump = $matches[1];
}
break;
case 'find-symbol':
return id(new AphrontRedirectResponse())
->setURI('/diffusion/symbol/'.$matches[1].'/?jump=true');
case 'create-task':
return id(new AphrontRedirectResponse())
->setURI('/maniphest/task/create/?title='
.phutil_escape_uri($matches[1]));
default:
throw new Exception("Unknown jump effect '{$effect}'!");
}
}
}
}
$query = new PhabricatorSearchQuery();
$query->setQuery($jump);
$query->save();
@ -182,6 +117,7 @@ class PhabricatorDirectoryMainController
return id(new AphrontRedirectResponse())
->setURI('/search/'.$query->getQueryKey().'/');
}
}
$nav->appendChild($this->buildJumpPanel());

View file

@ -22,7 +22,7 @@ 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/project/query/util');
phutil_require_module('phabricator', 'applications/search/engine/jumpnav');
phutil_require_module('phabricator', 'applications/search/storage/query');
phutil_require_module('phabricator', 'infrastructure/celerity/api');
phutil_require_module('phabricator', 'infrastructure/env');

View file

@ -1,46 +0,0 @@
<?php
/*
* Copyright 2012 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.
*/
final class PhabricatorProjectQueryUtil {
public static function findCloselyNamedProject($name) {
$project = id(new PhabricatorProject())->loadOneWhere(
'name = %s',
name);
if ($project) {
return $project;
} else { // no exact match, try a fuzzy match
$projects = id(new PhabricatorProject())->loadAllWhere(
'name LIKE %~',
$name);
if ($projects) {
$min_name_length = PHP_INT_MAX;
$best_project = null;
foreach ($projects as $project) {
$name_length = strlen($project->getName());
if ($name_length <= $min_name_length) {
$min_name_length = $name_length;
$best_project = $project;
}
}
return $best_project;
} else {
return null;
}
}
}
}

View file

@ -20,7 +20,6 @@
* @group search
*/
class PhabricatorSearchController extends PhabricatorSearchBaseController {
private $key;
public function willProcessRequest(array $data) {
@ -42,7 +41,12 @@ class PhabricatorSearchController extends PhabricatorSearchBaseController {
$query = new PhabricatorSearchQuery();
if ($request->isFormPost()) {
$query->setQuery($request->getStr('query'));
$query_str = $request->getStr('query');
$response = PhabricatorJumpNavHandler::jumpPostResponse($query_str);
if ($response) {
return $response;
} else {
$query->setQuery($query_str);
if ($request->getStr('scope')) {
switch ($request->getStr('scope')) {
@ -98,6 +102,7 @@ class PhabricatorSearchController extends PhabricatorSearchBaseController {
->setURI('/search/'.$query->getQueryKey().'/');
}
}
}
$more = PhabricatorEnv::getEnvConfig('search.more-document-types', array());

View file

@ -12,6 +12,7 @@ phutil_require_module('phabricator', 'applications/phid/constants');
phutil_require_module('phabricator', 'applications/phid/handle/data');
phutil_require_module('phabricator', 'applications/search/constants/scope');
phutil_require_module('phabricator', 'applications/search/controller/base');
phutil_require_module('phabricator', 'applications/search/engine/jumpnav');
phutil_require_module('phabricator', 'applications/search/selector/base');
phutil_require_module('phabricator', 'applications/search/storage/query');
phutil_require_module('phabricator', 'applications/search/view/searchresult');

View file

@ -0,0 +1,118 @@
<?php
/*
* Copyright 2012 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.
*/
final class PhabricatorJumpNavHandler {
public static function jumpPostResponse($jump) {
$jump = trim($jump);
$help_href = PhabricatorEnv::getDocLink(
'article/Jump_Nav_User_Guide.html');
$patterns = array(
'/^help/i' => 'uri:'.$help_href,
'/^d$/i' => 'uri:/differential/',
'/^r$/i' => 'uri:/diffusion/',
'/^t$/i' => 'uri:/maniphest/',
'/^p$/i' => 'uri:/project/',
'/^u$/i' => 'uri:/people/',
'/^r([A-Z]+)$/' => 'repository',
'/^r([A-Z]+)(\S+)$/' => 'commit',
'/^d(\d+)$/i' => 'revision',
'/^t(\d+)$/i' => 'task',
'/^p\s+(.+)$/i' => 'project',
'/^u\s+(\S+)$/i' => 'user',
'/^task:\s*(.+)/i' => 'create-task',
'/^(?: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]);
case 'user':
return id(new AphrontRedirectResponse())
->setURI('/p/'.$matches[1].'/');
case 'project':
$project = static::findCloselyNamedProject($matches[1]);
if ($project) {
return id(new AphrontRedirectResponse())
->setURI('/project/view/'.$project->getID().'/');
} else {
$jump = $matches[1];
}
break;
case 'find-symbol':
return id(new AphrontRedirectResponse())
->setURI('/diffusion/symbol/'.$matches[1].'/?jump=true');
case 'create-task':
return id(new AphrontRedirectResponse())
->setURI('/maniphest/task/create/?title='
.phutil_escape_uri($matches[1]));
default:
throw new Exception("Unknown jump effect '{$effect}'!");
}
}
}
}
return null;
}
private static function findCloselyNamedProject($name) {
$project = id(new PhabricatorProject())->loadOneWhere(
'name = %s',
name);
if ($project) {
return $project;
} else { // no exact match, try a fuzzy match
$projects = id(new PhabricatorProject())->loadAllWhere(
'name LIKE %~',
$name);
if ($projects) {
$min_name_length = PHP_INT_MAX;
$best_project = null;
foreach ($projects as $project) {
$name_length = strlen($project->getName());
if ($name_length <= $min_name_length) {
$min_name_length = $name_length;
$best_project = $project;
}
}
return $best_project;
} else {
return null;
}
}
}
}

View file

@ -6,9 +6,12 @@
phutil_require_module('phabricator', 'aphront/response/redirect');
phutil_require_module('phabricator', 'applications/project/storage/project');
phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorProjectQueryUtil.php');
phutil_require_source('PhabricatorJumpNavHandler.php');

View file

@ -288,7 +288,7 @@ class PhabricatorStandardPageView extends AphrontPageView {
'method' => 'post',
'style' => 'display: inline',
),
'<input type="text" name="query" />'.
'<input type="text" name="query" id="standard-search-box" />'.
' in '.
AphrontFormSelectControl::renderSelectTag(
$this->getSearchDefaultScope(),

View file

@ -122,6 +122,7 @@ JX.install('KeyboardShortcutManager', {
for (var jj = 0; jj < keys.length; jj++) {
if (keys[jj] == key) {
shortcuts[ii].getHandler()(this);
e.kill(); // Consume the event
return;
}
}

View file

@ -28,4 +28,14 @@ JX.behavior('phabricator-keyboard-shortcuts', function(config) {
workflow.start();
})
.register();
desc = 'Give keyboard focus to the search box.';
new JX.KeyboardShortcut('/', desc)
.setHandler(function() {
var search = JX.$("standard-search-box");
search.focus();
search.select();
})
.register();
});