Page MenuHomePhabricator

D12509.id30031.diff
No OneTemporary

D12509.id30031.diff

diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -7,8 +7,8 @@
*/
return array(
'names' => array(
- 'core.pkg.css' => 'a2a90172',
- 'core.pkg.js' => 'd57952b0',
+ 'core.pkg.css' => 'f7d01efc',
+ 'core.pkg.js' => '9af42daa',
'darkconsole.pkg.js' => '8ab24e01',
'differential.pkg.css' => '3500921f',
'differential.pkg.js' => 'c0506961',
@@ -34,7 +34,7 @@
'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',
@@ -486,7 +486,7 @@
'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' => '02a26f64',
'rsrc/js/core/behavior-select-on-click.js' => '4e3e79a6',
'rsrc/js/core/behavior-toggle-class.js' => 'e566f52c',
'rsrc/js/core/behavior-tokenizer.js' => 'b3a4b884',
@@ -628,7 +628,7 @@
'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' => '02a26f64',
'javelin-behavior-phabricator-show-older-transactions' => 'dbbf48b6',
'javelin-behavior-phabricator-tooltips' => '3ee3408b',
'javelin-behavior-phabricator-transaction-comment-form' => '9f7309fb',
@@ -737,7 +737,7 @@
'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',
@@ -845,6 +845,16 @@
'029a133d' => array(
'aphront-dialog-view-css',
),
+ '02a26f64' => array(
+ 'javelin-behavior',
+ 'javelin-typeahead-ondemand-source',
+ 'javelin-typeahead',
+ 'javelin-dom',
+ 'javelin-uri',
+ 'javelin-util',
+ 'javelin-stratcom',
+ 'phabricator-prefab',
+ ),
'05270951' => array(
'javelin-util',
'javelin-magical-init',
@@ -1367,16 +1377,6 @@
'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',
diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php
--- a/src/applications/base/PhabricatorApplication.php
+++ b/src/applications/base/PhabricatorApplication.php
@@ -582,4 +582,8 @@
}
}
+ public function getApplicationSearchDocumentTypes() {
+ return array();
+ }
+
}
diff --git a/src/applications/maniphest/application/PhabricatorManiphestApplication.php b/src/applications/maniphest/application/PhabricatorManiphestApplication.php
--- a/src/applications/maniphest/application/PhabricatorManiphestApplication.php
+++ b/src/applications/maniphest/application/PhabricatorManiphestApplication.php
@@ -158,4 +158,10 @@
);
}
+ public function getApplicationSearchDocumentTypes() {
+ return array(
+ ManiphestTaskPHIDType::TYPECONST,
+ );
+ }
+
}
diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php
--- a/src/applications/search/controller/PhabricatorApplicationSearchController.php
+++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php
@@ -233,6 +233,15 @@
$saved_query);
}
+ $messages = $engine->getMessages();
+ if ($messages) {
+ $nav->appendChild(
+ id(new PHUIInfoView())
+ ->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
+ ->setErrors($messages));
+ }
+
+
$nav->appendChild($list);
// TODO: This is a bit hacky.
diff --git a/src/applications/search/controller/PhabricatorSearchController.php b/src/applications/search/controller/PhabricatorSearchController.php
--- a/src/applications/search/controller/PhabricatorSearchController.php
+++ b/src/applications/search/controller/PhabricatorSearchController.php
@@ -3,6 +3,8 @@
final class PhabricatorSearchController
extends PhabricatorSearchBaseController {
+ const SCOPE_CURRENT_APPLICATION = 'application';
+
private $queryKey;
public function shouldAllowPublic() {
@@ -32,49 +34,69 @@
$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);
-
- $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();
+ // First, load or construct a template for the search by examining
+ // the current search scope.
+ $scope = $request->getStr('search:scope');
+ $saved = null;
+
+ $app_scope_error = false;
+ if ($scope == self::SCOPE_CURRENT_APPLICATION) {
+ $app_scope_error = true;
+ $application = id(new PhabricatorApplicationQuery())
+ ->setViewer($viewer)
+ ->withClasses(array($request->getStr('search:application')))
+ ->executeOne();
+ if ($application) {
+ $types = $application->getApplicationSearchDocumentTypes();
+ if ($types) {
+ $app_scope_error = false;
+ $saved = id(new PhabricatorSavedQuery())
+ ->setEngineClassName(get_class($engine))
+ ->setParameter('types', $types);
+ }
}
+ }
- 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';
+ if (!$saved && !$engine->isBuiltinQuery($scope)) {
+ $saved = id(new PhabricatorSavedQueryQuery())
+ ->setViewer($viewer)
+ ->withQueryKeys(array($scope))
+ ->executeOne();
+ }
- return id(new AphrontRedirectResponse())->setURI($results_uri);
+ 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'));
+ $saved->setParameter('app.scope.error', $app_scope_error);
+
+ $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())
diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php
--- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php
+++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php
@@ -19,6 +19,7 @@
private $application;
private $viewer;
private $errors = array();
+ private $messages = array();
private $customFields = false;
private $request;
private $context;
@@ -101,6 +102,15 @@
return $this;
}
+ public function getMessages() {
+ return $this->messages;
+ }
+
+ public function addMessage($message) {
+ $this->messages[] = $message;
+ return $this;
+ }
+
/**
* Return an application URI corresponding to the results page of a query.
* Normally, this is something like `/application/query/QUERYKEY/`.
diff --git a/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php b/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php
--- a/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php
+++ b/src/applications/search/query/PhabricatorSearchApplicationSearchEngine.php
@@ -55,6 +55,14 @@
AphrontFormView $form,
PhabricatorSavedQuery $saved) {
+ if ($saved->getParameter('app.scope.error')) {
+ $this->addMessage(
+ pht(
+ 'You ran a "Current Application" search, but the current '.
+ 'application does not have any searchable document types. '.
+ 'Showing results for all documents.'));
+ }
+
$options = array();
$author_value = null;
$owner_value = null;
diff --git a/src/applications/settings/storage/PhabricatorUserPreferences.php b/src/applications/settings/storage/PhabricatorUserPreferences.php
--- a/src/applications/settings/storage/PhabricatorUserPreferences.php
+++ b/src/applications/settings/storage/PhabricatorUserPreferences.php
@@ -19,6 +19,7 @@
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';
diff --git a/src/view/page/menu/PhabricatorMainMenuSearchView.php b/src/view/page/menu/PhabricatorMainMenuSearchView.php
--- a/src/view/page/menu/PhabricatorMainMenuSearchView.php
+++ b/src/view/page/menu/PhabricatorMainMenuSearchView.php
@@ -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 @@
'');
$search_datasource = new PhabricatorSearchDatasource();
+ $scope_key = PhabricatorUserPreferences::PREFERENCE_SEARCH_SCOPE;
Javelin::initBehavior(
'phabricator-search-typeahead',
@@ -46,6 +57,7 @@
'src' => $search_datasource->getDatasourceURI(),
'limit' => 10,
'placeholder' => pht('Search'),
+ 'scopeUpdateURI' => '/settings/adjust/?key='.$scope_key,
));
$primary_input = phutil_tag(
@@ -63,6 +75,8 @@
),
pht('Search'));
+ $selector = $this->buildModeSelector();
+
$form = phabricator_form(
$user,
array(
@@ -78,6 +92,7 @@
'class' => 'phui-icon-view phui-font-fa fa-search',
),
$search_text),
+ $selector,
$primary_input,
$target,
)));
@@ -85,4 +100,119 @@
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',
+ );
+
+ $items[] = array(
+ 'icon' => 'fa-puzzle-piece',
+ '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_value = null;
+ if ($this->getApplication()) {
+ $application_value = get_class($this->getApplication());
+ }
+
+ $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);
+ }
+
}
diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php
--- a/src/view/page/menu/PhabricatorMainMenuView.php
+++ b/src/view/page/menu/PhabricatorMainMenuView.php
@@ -114,6 +114,16 @@
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;
diff --git a/webroot/rsrc/css/application/base/main-menu-view.css b/webroot/rsrc/css/application/base/main-menu-view.css
--- a/webroot/rsrc/css/application/base/main-menu-view.css
+++ b/webroot/rsrc/css/application/base/main-menu-view.css
@@ -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;
}
diff --git a/webroot/rsrc/js/core/behavior-search-typeahead.js b/webroot/rsrc/js/core/behavior-search-typeahead.js
--- a/webroot/rsrc/js/core/behavior-search-typeahead.js
+++ b/webroot/rsrc/js/core/behavior-search-typeahead.js
@@ -143,4 +143,84 @@
typeahead.setPlaceholder('');
typeahead.updatePlaceHolder();
});
+
+ // TODO: Quicksand needs to update the application search input as we change
+ // applications; we should register a listener.
+
+ // 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();
+ });
+
+
});

File Metadata

Mime Type
text/plain
Expires
Mon, May 20, 2:34 PM (3 w, 5 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6296238
Default Alt Text
D12509.id30031.diff (20 KB)

Event Timeline