1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-01-14 00:31:05 +01:00

Add a basic search typeahead

Summary:
This needs a bunch of refinement but pretty much works. Currently shows only users and applications. Plans:

  - Show actual search results too.
  - Clean up the datasource endpoint so it's less of a mess.
  - Make other typeaheads look more like this one.
  - Improve sorting.
  - Make object names hit the named objects as the first match.

Test Plan: Will attach screenshots.

Reviewers: btrahan, vrana, chad

Reviewed By: vrana

CC: aran

Maniphest Tasks: T1569

Differential Revision: https://secure.phabricator.com/D3110
This commit is contained in:
epriestley 2012-07-31 17:58:21 -07:00
parent b43e6f2a5f
commit 852ecc2102
7 changed files with 261 additions and 26 deletions

View file

@ -1439,6 +1439,21 @@ celerity_register_resource_map(array(
),
'disk' => '/rsrc/js/application/core/behavior-oncopy.js',
),
'javelin-behavior-phabricator-search-typeahead' =>
array(
'uri' => '/res/9ceffb09/rsrc/js/application/core/behavior-search-typeahead.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-typeahead-ondemand-source',
2 => 'javelin-typeahead',
3 => 'javelin-dom',
4 => 'javelin-uri',
5 => 'javelin-stratcom',
),
'disk' => '/rsrc/js/application/core/behavior-search-typeahead.js',
),
'javelin-behavior-phabricator-tooltips' =>
array(
'uri' => '/res/49f92a92/rsrc/js/application/core/behavior-tooltip.js',
@ -2263,7 +2278,7 @@ celerity_register_resource_map(array(
),
'phabricator-main-menu-view' =>
array(
'uri' => '/res/795788ca/rsrc/css/application/base/main-menu-view.css',
'uri' => '/res/5bae3234/rsrc/css/application/base/main-menu-view.css',
'type' => 'css',
'requires' =>
array(

View file

@ -30,15 +30,35 @@ abstract class PhabricatorApplication {
public function getName() {
return substr(__CLASS__, strlen('PhabricatorApplication'));
return substr(get_class($this), strlen('PhabricatorApplication'));
}
public function getShortDescription() {
return $this->getName().' Application';
}
public function isEnabled() {
return true;
}
public function getPHID() {
return 'PHID-APPS-'.get_class($this);
}
/* -( Application Information )-------------------------------------------- */
public function getTypeaheadURI() {
return $this->getBaseURI();
}
public function getBaseURI() {
return null;
}
public function getIconURI() {
return PhabricatorUser::getDefaultProfileImageURI();
}
/* -( URI Routing )-------------------------------------------------------- */
public function getRoutes() {

View file

@ -24,5 +24,13 @@ final class PhabricatorApplicationDifferential extends PhabricatorApplication {
);
}
public function getBaseURI() {
return '/differential/';
}
public function getShortDescription() {
return 'Code Review Application';
}
}

View file

@ -28,7 +28,10 @@ final class PhabricatorTypeaheadCommonDatasourceController
$request = $this->getRequest();
$query = $request->getStr('q');
$need_rich_data = false;
$need_users = false;
$need_applications = false;
$need_all_users = false;
$need_lists = false;
$need_projs = false;
@ -38,6 +41,11 @@ final class PhabricatorTypeaheadCommonDatasourceController
$need_arcanist_projects = false;
$need_noproject = false;
switch ($this->type) {
case 'mainsearch':
$need_users = true;
$need_applications = true;
$need_rich_data = true;
break;
case 'searchowner':
$need_users = true;
$need_upforgrabs = true;
@ -78,9 +86,20 @@ final class PhabricatorTypeaheadCommonDatasourceController
case 'arcanistprojects':
$need_arcanist_projects = true;
break;
}
// TODO: We transfer these fields without keys as an opitimization, but this
// function is hard to read as a result. Until we can sort it out, here's
// what the position arguments mean:
//
// 0: (required) name to match against what the user types
// 1: (optional) URI
// 2: (required) PHID
// 3: (optional) priority matching string
// 4: (optional) display name [overrides position 0]
// 5: (optional) display type
// 6: (optional) image URI
$data = array();
if ($need_upforgrabs) {
@ -106,11 +125,16 @@ final class PhabricatorTypeaheadCommonDatasourceController
'userName',
'realName',
'phid');
if ($need_rich_data) {
$columns[] = 'profileImagePHID';
}
if ($query) {
$conn_r = id(new PhabricatorUser())->establishConnection('r');
$ids = queryfx_all(
$conn_r,
'SELECT DISTINCT userID FROM %T WHERE token LIKE %>',
'SELECT DISTINCT userID FROM %T WHERE token LIKE %> OR 1 = 1',
PhabricatorUser::NAMETOKEN_TABLE,
$query);
$ids = ipull($ids, 'userID');
@ -125,6 +149,12 @@ final class PhabricatorTypeaheadCommonDatasourceController
} else {
$users = id(new PhabricatorUser())->loadColumns($columns);
}
if ($need_rich_data) {
$phids = mpull($users, 'getPHID');
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
}
foreach ($users as $user) {
if (!$need_all_users) {
if ($user->getIsSystemAgent()) {
@ -134,12 +164,18 @@ final class PhabricatorTypeaheadCommonDatasourceController
continue;
}
}
$data[] = array(
$spec = array(
$user->getUsername().' ('.$user->getRealName().')',
'/p/'.$user->getUsername(),
$user->getPHID(),
$user->getUsername(),
null,
'User',
);
if ($need_rich_data) {
$spec[] = $handles[$user->getPHID()]->getImageURI();
}
$data[] = $spec;
}
}
@ -201,6 +237,33 @@ final class PhabricatorTypeaheadCommonDatasourceController
}
}
if ($need_applications) {
$applications = PhabricatorApplication::getAllInstalledApplications();
foreach ($applications as $application) {
$uri = $application->getTypeaheadURI();
if (!$uri) {
continue;
}
$data[] = array(
$application->getName().' '.$application->getShortDescription(),
$uri,
$application->getPHID(),
$application->getName(),
$application->getName(),
$application->getShortDescription(),
$application->getIconURI(),
);
}
}
if (!$need_rich_data) {
foreach ($data as $key => $info) {
unset($data[$key][4]);
unset($data[$key][5]);
unset($data[$key][6]);
}
}
return id(new AphrontAjaxResponse())
->setContent($data);
}

View file

@ -42,6 +42,7 @@ final class PhabricatorMainMenuSearchView extends AphrontView {
public function render() {
$user = $this->user;
$target_id = celerity_generate_unique_node_id();
$search_id = $this->getID();
$input = phutil_render_tag(
@ -49,16 +50,28 @@ final class PhabricatorMainMenuSearchView extends AphrontView {
array(
'type' => 'text',
'name' => 'query',
'id' => $search_id,
'id' => $search_id,
'autocomplete' => 'off',
));
$scope = $this->scope;
Javelin::initBehavior(
'placeholder',
$target = javelin_render_tag(
'div',
array(
'id' => $search_id,
'text' => PhabricatorSearchScope::getScopePlaceholder($scope),
'id' => $target_id,
'class' => 'phabricator-main-menu-search-target',
),
'');
Javelin::initBehavior(
'phabricator-search-typeahead',
array(
'id' => $target_id,
'input' => $search_id,
'src' => '/typeahead/common/mainsearch/',
'limit' => 10,
'placeholder' => PhabricatorSearchScope::getScopePlaceholder($scope),
));
$scope_input = phutil_render_tag(
@ -79,6 +92,7 @@ final class PhabricatorMainMenuSearchView extends AphrontView {
$input.
'<button>Search</button>'.
$scope_input.
$target.
'</div>');
$group = new PhabricatorMainMenuGroupView();

View file

@ -14,7 +14,6 @@
background: #33393d;
position: relative;
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.25);
overflow: hidden;
height: 44px;
}
@ -49,21 +48,27 @@
*/
.phabricator-main-menu-group {
height: 44px;
position: relative;
}
.device-desktop .phabricator-main-menu-group {
display: inline-block;
text-align: left;
height: 44px;
}
.device-tablet .phabricator-main-menu-group,
.device-phone .phabricator-main-menu-group {
clear: both;
width: 100%;
overflow: hidden;
border-bottom: 1px solid #33393d;
display: block;
}
.device-tablet .phabricator-main-menu-group + .phabricator-main-menu-group,
.device-phone .phabricator-main-menu-group + .phabricator-main-menu-group {
margin-top: 1px;
}
/* - Logo ----------------------------------------------------------------------
@ -72,7 +77,7 @@
*/
.phabricator-main-menu-group-logo {
.device-desktop .phabricator-main-menu-group-logo {
float: left;
}
@ -144,8 +149,6 @@
margin: 9px;
display: inline-block;
background-repeat: no-repeat;
position: relative;
overflow: hidden;
}
.device-desktop .phabricator-main-menu-icon-label {
@ -156,18 +159,17 @@
.device-phone .phabricator-main-menu-icon-label {
font-weight: bold;
color: #ffffff;
margin-left: 40px;
height: 26px;
margin: 15px 9px 3px 60px;
position: absolute;
display: block;
height: 26px;
padding: 15px 0 3px;
left: 60px;
right: 0px;
top: 0px;
}
.device-tablet .phabricator-main-menu-icon,
.device-phone .phabricator-main-menu-icon {
font-weight: bold;
color: white;
text-decoration: none;
border: 0;
margin-left: 24px;
position: absolute;
}
@ -189,6 +191,23 @@
height: 24px;
}
.phabricator-main-menu-search-target {
position: absolute;
top: 46px;
}
.device-desktop .phabricator-main-menu-search-target {
width: 320px;
margin-left: -150px;
}
.device-tablet .phabricator-main-menu-search-target,
.device-phone .phabricator-main-menu-search-target {
width: 100%;
margin-left: -25px;
}
.device-desktop .phabricator-main-menu-search-container {
margin: 0 8px 0 50px;
}
@ -237,6 +256,50 @@
right: 6px;
}
.phabricator-main-menu-search-target div.jx-typeahead-results {
border-radius: 4px;
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.35);
border: 1px solid #33393d;
}
.phabricator-main-menu-search-target div.jx-typeahead-results a.jx-result {
border: 0;
}
.phabricator-main-menu-search-target div.jx-typeahead-results a.focused,
.phabricator-main-menu-search-target div.jx-typeahead-results a:hover {
background: #3875d7;
}
.phabricator-main-search-typeahead-result {
display: block;
padding: 4px 4px 4px 38px;
background-position: 4px 4px;
background-size: 25px 25px;
background-repeat: no-repeat;
}
.phabricator-main-search-typeahead-result .result-name {
display: block;
font-weight: bold;
color: #444444;
}
.focused .phabricator-main-search-typeahead-result .result-name,
a:hover .phabricator-main-search-typeahead-result .result-name {
color: #eeeeee;
}
.phabricator-main-search-typeahead-result .result-type {
color: #888888;
}
.focused .phabricator-main-search-typeahead-result .result-type,
a:hover .phabricator-main-search-typeahead-result .result-type {
color: #dddddd;
}
/* - Collapsible ---------------------------------------------------------------

View file

@ -0,0 +1,52 @@
/**
* @provides javelin-behavior-phabricator-search-typeahead
* @requires javelin-behavior
* javelin-typeahead-ondemand-source
* javelin-typeahead
* javelin-dom
* javelin-uri
* javelin-stratcom
*/
JX.behavior('phabricator-search-typeahead', function(config) {
var datasource = new JX.TypeaheadOnDemandSource(config.src);
function transform(object) {
var attr = {
className: 'phabricator-main-search-typeahead-result'
}
if (object[6]) {
attr.style = {backgroundImage: 'url('+object[6]+')'};
}
var render = JX.$N(
'span',
attr,
[
JX.$N('span', {className: 'result-name'}, object[4] || object[0]),
JX.$N('span', {className: 'result-type'}, object[5])
]);
return {
name : object[0],
display : render,
uri : object[1],
id : object[2]
};
}
datasource.setTransformer(transform);
var typeahead = new JX.Typeahead(JX.$(config.id), JX.$(config.input));
typeahead.setDatasource(datasource);
typeahead.setPlaceholder(config.placeholder);
typeahead.listen('choose', function(r) {
JX.$U(r.href).go();
JX.Stratcom.context().kill();
});
typeahead.start();
});