mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-28 16:30:59 +01:00
Implement a scope selector for the global search
Summary: See M1433. Fixes T7266. Fixes T4475. Ref T7314. Future work/notes/etc: - Write the User Guide (see TODO). - This might needs some design tweaks -- I think it's functionally almost-equivalent to the mock, but the UI isn't quite the same. - (Mobile design is a touch off-looking I think?) - When you use a custom query, the duplicate "magnifying glass" icons are a little weird. Maybe change one or the other. - Maybe worth adding an "Open Documents in Current Application" option? Planning to wait for feedback on that. - Need a Quicksand integration to change the current application at some point. - Searching in "Current Application" from, e.g., the 404 page just searches all documents. Current plan is to just document this behavior, since the icon is a pretty good callout and it seems plausible that this is intuitive enough that users won't have a hard time with it. Test Plan: New dropdown: {F379150} Device-ish: {F379151} Normal search (current application, from maniphest, selects tasks): {F379153} Application search from non-application: {F379154} Reviewers: btrahan, chad Reviewed By: chad Subscribers: johnny-bit, epriestley Maniphest Tasks: T7266, T7314, T4475 Differential Revision: https://secure.phabricator.com/D12509
This commit is contained in:
parent
d8b4f32d04
commit
3a2c2ae3c3
18 changed files with 403 additions and 75 deletions
|
@ -7,9 +7,9 @@
|
|||
*/
|
||||
return array(
|
||||
'names' => array(
|
||||
'core.pkg.css' => 'a2a90172',
|
||||
'core.pkg.js' => '8e62b4aa',
|
||||
'darkconsole.pkg.js' => 'b0a3ba93',
|
||||
'core.pkg.css' => 'f7d01efc',
|
||||
'core.pkg.js' => 'a1f9db42',
|
||||
'darkconsole.pkg.js' => '8ab24e01',
|
||||
'differential.pkg.css' => '3500921f',
|
||||
'differential.pkg.js' => 'c0506961',
|
||||
'diffusion.pkg.css' => '591664fa',
|
||||
|
@ -34,7 +34,7 @@ return array(
|
|||
'rsrc/css/aphront/typeahead.css' => '0e403212',
|
||||
'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af',
|
||||
'rsrc/css/application/auth/auth.css' => '1e655982',
|
||||
'rsrc/css/application/base/main-menu-view.css' => 'c648b2f5',
|
||||
'rsrc/css/application/base/main-menu-view.css' => '31e66da9',
|
||||
'rsrc/css/application/base/notification-menu.css' => '3c9d8aa1',
|
||||
'rsrc/css/application/base/phabricator-application-launch-view.css' => '16ca323f',
|
||||
'rsrc/css/application/base/standard-page-view.css' => 'd3e1abe9',
|
||||
|
@ -350,7 +350,7 @@ return array(
|
|||
'rsrc/image/texture/table_header_hover.png' => '038ec3b9',
|
||||
'rsrc/image/texture/table_header_tall.png' => 'd56b434f',
|
||||
'rsrc/js/application/aphlict/Aphlict.js' => '30a6303c',
|
||||
'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'ee37f73a',
|
||||
'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '572566ae',
|
||||
'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'b1a59974',
|
||||
'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761',
|
||||
'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18',
|
||||
|
@ -460,7 +460,7 @@ return array(
|
|||
'rsrc/js/core/behavior-autofocus.js' => '7319e029',
|
||||
'rsrc/js/core/behavior-choose-control.js' => '6153c708',
|
||||
'rsrc/js/core/behavior-crop.js' => 'fa0f4fc2',
|
||||
'rsrc/js/core/behavior-dark-console.js' => 'b8df5663',
|
||||
'rsrc/js/core/behavior-dark-console.js' => '08883e8b',
|
||||
'rsrc/js/core/behavior-device.js' => 'a205cf28',
|
||||
'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '6d49590e',
|
||||
'rsrc/js/core/behavior-error-log.js' => '6882e80a',
|
||||
|
@ -486,7 +486,7 @@ return array(
|
|||
'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e',
|
||||
'rsrc/js/core/behavior-reveal-content.js' => '60821bc7',
|
||||
'rsrc/js/core/behavior-scrollbar.js' => '834a1173',
|
||||
'rsrc/js/core/behavior-search-typeahead.js' => '724b1247',
|
||||
'rsrc/js/core/behavior-search-typeahead.js' => 'bc965352',
|
||||
'rsrc/js/core/behavior-select-on-click.js' => '4e3e79a6',
|
||||
'rsrc/js/core/behavior-toggle-class.js' => 'e566f52c',
|
||||
'rsrc/js/core/behavior-tokenizer.js' => 'b3a4b884',
|
||||
|
@ -550,7 +550,7 @@ return array(
|
|||
'inline-comment-summary-css' => 'eb5f8e8c',
|
||||
'javelin-aphlict' => '30a6303c',
|
||||
'javelin-behavior' => '61cbc29a',
|
||||
'javelin-behavior-aphlict-dropdown' => 'ee37f73a',
|
||||
'javelin-behavior-aphlict-dropdown' => '572566ae',
|
||||
'javelin-behavior-aphlict-listen' => 'b1a59974',
|
||||
'javelin-behavior-aphlict-status' => 'ea681761',
|
||||
'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884',
|
||||
|
@ -567,7 +567,7 @@ return array(
|
|||
'javelin-behavior-conpherence-pontificate' => '21ba5861',
|
||||
'javelin-behavior-conpherence-widget-pane' => '93568464',
|
||||
'javelin-behavior-countdown-timer' => 'e4cc26b3',
|
||||
'javelin-behavior-dark-console' => 'b8df5663',
|
||||
'javelin-behavior-dark-console' => '08883e8b',
|
||||
'javelin-behavior-dashboard-async-panel' => '469c0d9e',
|
||||
'javelin-behavior-dashboard-move-panels' => '82439934',
|
||||
'javelin-behavior-dashboard-query-panel-select' => '453c5375',
|
||||
|
@ -628,7 +628,7 @@ return array(
|
|||
'javelin-behavior-phabricator-oncopy' => '2926fff2',
|
||||
'javelin-behavior-phabricator-remarkup-assist' => 'e32d14ab',
|
||||
'javelin-behavior-phabricator-reveal-content' => '60821bc7',
|
||||
'javelin-behavior-phabricator-search-typeahead' => '724b1247',
|
||||
'javelin-behavior-phabricator-search-typeahead' => 'bc965352',
|
||||
'javelin-behavior-phabricator-show-older-transactions' => 'dbbf48b6',
|
||||
'javelin-behavior-phabricator-tooltips' => '3ee3408b',
|
||||
'javelin-behavior-phabricator-transaction-comment-form' => '9f7309fb',
|
||||
|
@ -737,7 +737,7 @@ return array(
|
|||
'phabricator-hovercard-view-css' => '44394670',
|
||||
'phabricator-keyboard-shortcut' => '1ae869f2',
|
||||
'phabricator-keyboard-shortcut-manager' => 'c1700f6f',
|
||||
'phabricator-main-menu-view' => 'c648b2f5',
|
||||
'phabricator-main-menu-view' => '31e66da9',
|
||||
'phabricator-nav-view-css' => '7aeaf435',
|
||||
'phabricator-notification' => '0c6946e7',
|
||||
'phabricator-notification-css' => '9c279160',
|
||||
|
@ -869,6 +869,14 @@ return array(
|
|||
'phabricator-shaped-request',
|
||||
'conpherence-thread-manager',
|
||||
),
|
||||
'08883e8b' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
'javelin-util',
|
||||
'javelin-dom',
|
||||
'javelin-request',
|
||||
'phabricator-keyboard-shortcut',
|
||||
),
|
||||
'0a3f3021' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -1190,6 +1198,16 @@ return array(
|
|||
'javelin-vector',
|
||||
'javelin-dom',
|
||||
),
|
||||
'572566ae' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-request',
|
||||
'javelin-stratcom',
|
||||
'javelin-vector',
|
||||
'javelin-dom',
|
||||
'javelin-uri',
|
||||
'javelin-behavior-device',
|
||||
'phabricator-title',
|
||||
),
|
||||
58562350 => array(
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
|
@ -1349,16 +1367,6 @@ return array(
|
|||
'javelin-vector',
|
||||
'javelin-util',
|
||||
),
|
||||
'724b1247' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-typeahead-ondemand-source',
|
||||
'javelin-typeahead',
|
||||
'javelin-dom',
|
||||
'javelin-uri',
|
||||
'javelin-util',
|
||||
'javelin-stratcom',
|
||||
'phabricator-prefab',
|
||||
),
|
||||
'7319e029' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
|
@ -1719,14 +1727,6 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-util',
|
||||
),
|
||||
'b8df5663' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
'javelin-util',
|
||||
'javelin-dom',
|
||||
'javelin-request',
|
||||
'phabricator-keyboard-shortcut',
|
||||
),
|
||||
'bba9eedf' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -1739,6 +1739,16 @@ return array(
|
|||
'javelin-mask',
|
||||
'phabricator-drag-and-drop-file-upload',
|
||||
),
|
||||
'bc965352' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-typeahead-ondemand-source',
|
||||
'javelin-typeahead',
|
||||
'javelin-dom',
|
||||
'javelin-uri',
|
||||
'javelin-util',
|
||||
'javelin-stratcom',
|
||||
'phabricator-prefab',
|
||||
),
|
||||
'bd4c8dca' => array(
|
||||
'javelin-install',
|
||||
'javelin-util',
|
||||
|
@ -1940,16 +1950,6 @@ return array(
|
|||
'javelin-stratcom',
|
||||
'javelin-vector',
|
||||
),
|
||||
'ee37f73a' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-request',
|
||||
'javelin-stratcom',
|
||||
'javelin-vector',
|
||||
'javelin-dom',
|
||||
'javelin-uri',
|
||||
'javelin-behavior-device',
|
||||
'phabricator-title',
|
||||
),
|
||||
'efe49472' => array(
|
||||
'javelin-install',
|
||||
'javelin-util',
|
||||
|
|
|
@ -582,4 +582,8 @@ abstract class PhabricatorApplication implements PhabricatorPolicyInterface {
|
|||
}
|
||||
}
|
||||
|
||||
public function getApplicationSearchDocumentTypes() {
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -205,4 +205,10 @@ EOTEXT
|
|||
);
|
||||
}
|
||||
|
||||
public function getApplicationSearchDocumentTypes() {
|
||||
return array(
|
||||
DifferentialRevisionPHIDType::TYPECONST,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -161,4 +161,10 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication {
|
|||
);
|
||||
}
|
||||
|
||||
public function getApplicationSearchDocumentTypes() {
|
||||
return array(
|
||||
PhabricatorRepositoryCommitPHIDType::TYPECONST,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -62,4 +62,10 @@ final class PhabricatorFundApplication extends PhabricatorApplication {
|
|||
);
|
||||
}
|
||||
|
||||
public function getApplicationSearchDocumentTypes() {
|
||||
return array(
|
||||
FundInitiativePHIDType::TYPECONST,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -158,4 +158,10 @@ final class PhabricatorManiphestApplication extends PhabricatorApplication {
|
|||
);
|
||||
}
|
||||
|
||||
public function getApplicationSearchDocumentTypes() {
|
||||
return array(
|
||||
ManiphestTaskPHIDType::TYPECONST,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -57,4 +57,10 @@ final class PhabricatorPassphraseApplication extends PhabricatorApplication {
|
|||
);
|
||||
}
|
||||
|
||||
public function getApplicationSearchDocumentTypes() {
|
||||
return array(
|
||||
PassphraseCredentialPHIDType::TYPECONST,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -192,4 +192,10 @@ final class PhabricatorPeopleApplication extends PhabricatorApplication {
|
|||
return $items;
|
||||
}
|
||||
|
||||
public function getApplicationSearchDocumentTypes() {
|
||||
return array(
|
||||
PhabricatorPeopleUserPHIDType::TYPECONST,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -90,4 +90,10 @@ final class PhabricatorPholioApplication extends PhabricatorApplication {
|
|||
);
|
||||
}
|
||||
|
||||
public function getApplicationSearchDocumentTypes() {
|
||||
return array(
|
||||
PholioMockPHIDType::TYPECONST,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -69,4 +69,10 @@ final class PhabricatorPhrictionApplication extends PhabricatorApplication {
|
|||
return 0.140;
|
||||
}
|
||||
|
||||
public function getApplicationSearchDocumentTypes() {
|
||||
return array(
|
||||
PhrictionDocumentPHIDType::TYPECONST,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -79,4 +79,10 @@ final class PhabricatorPonderApplication extends PhabricatorApplication {
|
|||
);
|
||||
}
|
||||
|
||||
public function getApplicationSearchDocumentTypes() {
|
||||
return array(
|
||||
PonderQuestionPHIDType::TYPECONST,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -133,4 +133,10 @@ final class PhabricatorProjectApplication extends PhabricatorApplication {
|
|||
);
|
||||
}
|
||||
|
||||
public function getApplicationSearchDocumentTypes() {
|
||||
return array(
|
||||
PhabricatorProjectProjectPHIDType::TYPECONST,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
final class PhabricatorSearchController
|
||||
extends PhabricatorSearchBaseController {
|
||||
|
||||
const SCOPE_CURRENT_APPLICATION = 'application';
|
||||
|
||||
private $queryKey;
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
|
@ -32,49 +34,65 @@ final class PhabricatorSearchController
|
|||
$engine = new PhabricatorSearchApplicationSearchEngine();
|
||||
$engine->setViewer($viewer);
|
||||
|
||||
// NOTE: This is a little weird. If we're coming from primary search, we
|
||||
// load the user's first search filter and overwrite the "query" part of
|
||||
// it, then send them to that result page. This is sort of odd, but lets
|
||||
// users choose a default query like "Open Tasks" in a reasonable way,
|
||||
// with only this piece of somewhat-sketchy code. See discussion in T4365.
|
||||
|
||||
// If we're coming from primary search, do some special handling to
|
||||
// interpret the scope selector and query.
|
||||
if ($request->getBool('search:primary')) {
|
||||
|
||||
// If there's no query, just take the user to advanced search.
|
||||
if (!strlen($request->getStr('query'))) {
|
||||
$advanced_uri = '/search/query/advanced/';
|
||||
return id(new AphrontRedirectResponse())->setURI($advanced_uri);
|
||||
}
|
||||
|
||||
$named_queries = $engine->loadEnabledNamedQueries();
|
||||
if ($named_queries) {
|
||||
$named = head($named_queries);
|
||||
// First, load or construct a template for the search by examining
|
||||
// the current search scope.
|
||||
$scope = $request->getStr('search:scope');
|
||||
$saved = null;
|
||||
|
||||
$query_key = $named->getQueryKey();
|
||||
$saved = null;
|
||||
if ($engine->isBuiltinQuery($query_key)) {
|
||||
$saved = $engine->buildSavedQueryFromBuiltin($query_key);
|
||||
} else {
|
||||
$saved = id(new PhabricatorSavedQueryQuery())
|
||||
->setViewer($viewer)
|
||||
->withQueryKeys(array($query_key))
|
||||
->executeOne();
|
||||
}
|
||||
|
||||
if ($saved) {
|
||||
$saved->setParameter('query', $request->getStr('query'));
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
try {
|
||||
$saved->setID(null)->save();
|
||||
} catch (AphrontDuplicateKeyQueryException $ex) {
|
||||
// Ignore, this is just a repeated search.
|
||||
}
|
||||
unset($unguarded);
|
||||
|
||||
$results_uri = $engine->getQueryResultsPageURI(
|
||||
$saved->getQueryKey()).'#R';
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($results_uri);
|
||||
if ($scope == self::SCOPE_CURRENT_APPLICATION) {
|
||||
$application = id(new PhabricatorApplicationQuery())
|
||||
->setViewer($viewer)
|
||||
->withClasses(array($request->getStr('search:application')))
|
||||
->executeOne();
|
||||
if ($application) {
|
||||
$types = $application->getApplicationSearchDocumentTypes();
|
||||
if ($types) {
|
||||
$saved = id(new PhabricatorSavedQuery())
|
||||
->setEngineClassName(get_class($engine))
|
||||
->setParameter('types', $types);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$saved && !$engine->isBuiltinQuery($scope)) {
|
||||
$saved = id(new PhabricatorSavedQueryQuery())
|
||||
->setViewer($viewer)
|
||||
->withQueryKeys(array($scope))
|
||||
->executeOne();
|
||||
}
|
||||
|
||||
if (!$saved) {
|
||||
if (!$engine->isBuiltinQuery($scope)) {
|
||||
$scope = 'all';
|
||||
}
|
||||
$saved = $engine->buildSavedQueryFromBuiltin($scope);
|
||||
}
|
||||
|
||||
// Add the user's query, then save this as a new saved query and send
|
||||
// the user to the results page.
|
||||
$saved->setParameter('query', $request->getStr('query'));
|
||||
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
try {
|
||||
$saved->setID(null)->save();
|
||||
} catch (AphrontDuplicateKeyQueryException $ex) {
|
||||
// Ignore, this is just a repeated search.
|
||||
}
|
||||
unset($unguarded);
|
||||
|
||||
$query_key = $saved->getQueryKey();
|
||||
$results_uri = $engine->getQueryResultsPageURI($query_key).'#R';
|
||||
return id(new AphrontRedirectResponse())->setURI($results_uri);
|
||||
}
|
||||
|
||||
$controller = id(new PhabricatorApplicationSearchController())
|
||||
|
|
|
@ -19,6 +19,7 @@ final class PhabricatorUserPreferences extends PhabricatorUserDAO {
|
|||
|
||||
const PREFERENCE_SEARCHBAR_JUMP = 'searchbar-jump';
|
||||
const PREFERENCE_SEARCH_SHORTCUT = 'search-shortcut';
|
||||
const PREFERENCE_SEARCH_SCOPE = 'search-scope';
|
||||
|
||||
const PREFERENCE_DIFFUSION_BLAME = 'diffusion-blame';
|
||||
const PREFERENCE_DIFFUSION_COLOR = 'diffusion-color';
|
||||
|
|
|
@ -3,6 +3,16 @@
|
|||
final class PhabricatorMainMenuSearchView extends AphrontView {
|
||||
|
||||
private $id;
|
||||
private $application;
|
||||
|
||||
public function setApplication(PhabricatorApplication $application) {
|
||||
$this->application = $application;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplication() {
|
||||
return $this->application;
|
||||
}
|
||||
|
||||
public function getID() {
|
||||
if (!$this->id) {
|
||||
|
@ -36,6 +46,7 @@ final class PhabricatorMainMenuSearchView extends AphrontView {
|
|||
'');
|
||||
|
||||
$search_datasource = new PhabricatorSearchDatasource();
|
||||
$scope_key = PhabricatorUserPreferences::PREFERENCE_SEARCH_SCOPE;
|
||||
|
||||
Javelin::initBehavior(
|
||||
'phabricator-search-typeahead',
|
||||
|
@ -46,6 +57,7 @@ final class PhabricatorMainMenuSearchView extends AphrontView {
|
|||
'src' => $search_datasource->getDatasourceURI(),
|
||||
'limit' => 10,
|
||||
'placeholder' => pht('Search'),
|
||||
'scopeUpdateURI' => '/settings/adjust/?key='.$scope_key,
|
||||
));
|
||||
|
||||
$primary_input = phutil_tag(
|
||||
|
@ -63,6 +75,8 @@ final class PhabricatorMainMenuSearchView extends AphrontView {
|
|||
),
|
||||
pht('Search'));
|
||||
|
||||
$selector = $this->buildModeSelector();
|
||||
|
||||
$form = phabricator_form(
|
||||
$user,
|
||||
array(
|
||||
|
@ -78,6 +92,7 @@ final class PhabricatorMainMenuSearchView extends AphrontView {
|
|||
'class' => 'phui-icon-view phui-font-fa fa-search',
|
||||
),
|
||||
$search_text),
|
||||
$selector,
|
||||
$primary_input,
|
||||
$target,
|
||||
)));
|
||||
|
@ -85,4 +100,124 @@ final class PhabricatorMainMenuSearchView extends AphrontView {
|
|||
return $form;
|
||||
}
|
||||
|
||||
private function buildModeSelector() {
|
||||
$viewer = $this->getUser();
|
||||
|
||||
$items = array();
|
||||
$items[] = array(
|
||||
'name' => pht('Search'),
|
||||
);
|
||||
|
||||
$items[] = array(
|
||||
'icon' => 'fa-globe',
|
||||
'name' => pht('Search All Documents'),
|
||||
'value' => 'all',
|
||||
);
|
||||
|
||||
$application_value = null;
|
||||
$application_icon = 'fa-file-o';
|
||||
$application = $this->getApplication();
|
||||
if ($application) {
|
||||
$application_value = get_class($application);
|
||||
if ($application->getApplicationSearchDocumentTypes()) {
|
||||
$application_icon = $application->getFontIcon();
|
||||
}
|
||||
}
|
||||
|
||||
$items[] = array(
|
||||
'icon' => $application_icon,
|
||||
'name' => pht('Search Current Application'),
|
||||
'value' => PhabricatorSearchController::SCOPE_CURRENT_APPLICATION,
|
||||
);
|
||||
|
||||
$items[] = array(
|
||||
'name' => pht('Saved Queries'),
|
||||
);
|
||||
|
||||
|
||||
$engine = id(new PhabricatorSearchApplicationSearchEngine())
|
||||
->setViewer($viewer);
|
||||
$engine_queries = $engine->loadEnabledNamedQueries();
|
||||
$query_map = mpull($engine_queries, 'getQueryName', 'getQueryKey');
|
||||
foreach ($query_map as $query_key => $query_name) {
|
||||
if ($query_key == 'all') {
|
||||
// Skip the builtin "All" query since it's redundant with the default
|
||||
// setting.
|
||||
continue;
|
||||
}
|
||||
|
||||
$items[] = array(
|
||||
'icon' => 'fa-search',
|
||||
'name' => $query_name,
|
||||
'value' => $query_key,
|
||||
);
|
||||
}
|
||||
|
||||
$items[] = array(
|
||||
'name' => pht('More Options'),
|
||||
);
|
||||
|
||||
$items[] = array(
|
||||
'icon' => 'fa-search-plus',
|
||||
'name' => pht('Advanced Search'),
|
||||
'href' => '/search/query/advanced/',
|
||||
);
|
||||
|
||||
/* TODO: Write this.
|
||||
$items[] = array(
|
||||
'icon' => 'fa-book',
|
||||
'name' => pht('User Guide: Search'),
|
||||
'href' => PhabricatorEnv::getDoclink('User Guide: Search'),
|
||||
);
|
||||
*/
|
||||
|
||||
$scope_key = PhabricatorUserPreferences::PREFERENCE_SEARCH_SCOPE;
|
||||
$current_value = $viewer->loadPreferences()->getPreference(
|
||||
$scope_key,
|
||||
'all');
|
||||
|
||||
$current_icon = 'fa-globe';
|
||||
foreach ($items as $item) {
|
||||
if (idx($item, 'value') == $current_value) {
|
||||
$current_icon = $item['icon'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$selector = id(new PHUIButtonView())
|
||||
->addClass('phabricator-main-menu-search-dropdown')
|
||||
->addSigil('global-search-dropdown')
|
||||
->setMetadata(
|
||||
array(
|
||||
'items' => $items,
|
||||
'icon' => $current_icon,
|
||||
'value' => $current_value,
|
||||
))
|
||||
->setIcon(
|
||||
id(new PHUIIconView())
|
||||
->addSigil('global-search-dropdown-icon')
|
||||
->setIconFont($current_icon))
|
||||
->setDropdown(true);
|
||||
|
||||
$input = javelin_tag(
|
||||
'input',
|
||||
array(
|
||||
'type' => 'hidden',
|
||||
'sigil' => 'global-search-dropdown-input',
|
||||
'name' => 'search:scope',
|
||||
'value' => $current_value,
|
||||
));
|
||||
|
||||
$application_input = javelin_tag(
|
||||
'input',
|
||||
array(
|
||||
'type' => 'hidden',
|
||||
'sigil' => 'global-search-dropdown-app',
|
||||
'name' => 'search:application',
|
||||
'value' => $application_value,
|
||||
));
|
||||
|
||||
return array($selector, $input, $application_input);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -114,6 +114,16 @@ final class PhabricatorMainMenuView extends AphrontView {
|
|||
if ($show_search) {
|
||||
$search = new PhabricatorMainMenuSearchView();
|
||||
$search->setUser($user);
|
||||
|
||||
$application = null;
|
||||
$controller = $this->getController();
|
||||
if ($controller) {
|
||||
$application = $controller->getCurrentApplication();
|
||||
}
|
||||
if ($application) {
|
||||
$search->setApplication($application);
|
||||
}
|
||||
|
||||
$result = $search;
|
||||
|
||||
$pref_shortcut = PhabricatorUserPreferences::PREFERENCE_SEARCH_SHORTCUT;
|
||||
|
|
|
@ -160,10 +160,9 @@
|
|||
height: 28px;
|
||||
line-height: 12px;
|
||||
box-shadow: 0px 1px 1px rgba(128, 128, 128, 0.25);
|
||||
padding: 6px 30px 6px 6px;
|
||||
padding: 6px 30px 6px 46px;
|
||||
float: left;
|
||||
width: 205px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.phabricator-main-menu.main-header-dark .phabricator-main-menu-search input {
|
||||
|
@ -207,6 +206,25 @@
|
|||
border-radius: 0;
|
||||
}
|
||||
|
||||
.phabricator-main-menu-search button.phabricator-main-menu-search-dropdown {
|
||||
position: absolute;
|
||||
right: auto;
|
||||
left: 0;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.phabricator-main-menu-search button.phabricator-main-menu-search-dropdown
|
||||
.phui-icon-view {
|
||||
color: rgba(255,255,255,.8);
|
||||
}
|
||||
|
||||
.phabricator-main-menu-search-dropdown .caret {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
|
||||
.phabricator-main-menu-search button:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
|
|
@ -143,4 +143,86 @@ JX.behavior('phabricator-search-typeahead', function(config) {
|
|||
typeahead.setPlaceholder('');
|
||||
typeahead.updatePlaceHolder();
|
||||
});
|
||||
|
||||
// TODO: Quicksand needs to update the application search input as we change
|
||||
// applications; we should register a listener.
|
||||
// TODO: Quicksand also needs to update the application search icon on the
|
||||
// button itself and in the menu.
|
||||
|
||||
// Implement the scope selector menu for the global search.
|
||||
JX.Stratcom.listen('click', 'global-search-dropdown', function(e) {
|
||||
var data = e.getNodeData('global-search-dropdown');
|
||||
var button = e.getNode('global-search-dropdown');
|
||||
if (data.menu) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.kill();
|
||||
|
||||
function updateValue(spec) {
|
||||
if (data.value == spec.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Swap out the icon.
|
||||
var icon = JX.DOM.find(button, 'span', 'global-search-dropdown-icon');
|
||||
JX.DOM.alterClass(icon, data.icon, false);
|
||||
data.icon = spec.icon;
|
||||
JX.DOM.alterClass(icon, data.icon, true);
|
||||
|
||||
// Update the value.
|
||||
data.value = spec.value;
|
||||
|
||||
// Update the form input.
|
||||
var frame = button.parentNode;
|
||||
var input = JX.DOM.find(frame, 'input', 'global-search-dropdown-input');
|
||||
input.value = data.value;
|
||||
|
||||
new JX.Request(config.scopeUpdateURI)
|
||||
.setData({value: data.value})
|
||||
.send();
|
||||
}
|
||||
|
||||
var menu = new JX.PHUIXDropdownMenu(button)
|
||||
.setAlign('left');
|
||||
data.menu = menu;
|
||||
|
||||
menu.listen('open', function() {
|
||||
var list = new JX.PHUIXActionListView();
|
||||
|
||||
for (var ii = 0; ii < data.items.length; ii++) {
|
||||
var spec = data.items[ii];
|
||||
var item = new JX.PHUIXActionView()
|
||||
.setName(spec.name)
|
||||
.setIcon(spec.icon);
|
||||
|
||||
if (spec.value) {
|
||||
if (spec.value == data.value) {
|
||||
item.setSelected(true);
|
||||
}
|
||||
|
||||
var handler = function(spec, e) {
|
||||
e.prevent();
|
||||
menu.close();
|
||||
updateValue(spec);
|
||||
};
|
||||
|
||||
item.setHandler(JX.bind(null, handler, spec));
|
||||
} else if (spec.href) {
|
||||
item.setHref(spec.href);
|
||||
item.setHandler(function() { menu.close(); });
|
||||
} else {
|
||||
item.setDisabled(true);
|
||||
}
|
||||
|
||||
list.addItem(item);
|
||||
}
|
||||
|
||||
menu.setContent(list.getNode());
|
||||
});
|
||||
|
||||
menu.open();
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue