mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-19 03:50:54 +01:00
Allow workboards to be filtered with ApplicationSearch
Summary: Ref T4673. IMPORTANT: I had to break one thing (see TODO) to get this working. Not sure how you want to deal with that. I might be able to put the element //inside// the workboard, or I could write some JS. But I figured I'd get feedback first. General areas for improvement: - It would be nice to give you some feedback that you have a filter applied. - It would be nice to let you save and quickly select common filters. - These would probably both be covered by a dropdown menu instead of a button, but that's more JS than I want to sign up for right now. - Managing custom filters is also a significant amount of extra UI to build. - Also, maybe these filters should be sticky per-board? Or across all boards? Or have a "make this my default view"? I tend to dislike implicit stickiness. Test Plan: Before: {F157543} Apply Filter: {F157544} Filtered: {F157545} Reviewers: chad, btrahan Reviewed By: btrahan Subscribers: qgil, swisspol, epriestley Maniphest Tasks: T4673 Differential Revision: https://secure.phabricator.com/D9211
This commit is contained in:
parent
8a429c51ac
commit
f0147fd8ad
10 changed files with 297 additions and 45 deletions
|
@ -407,6 +407,7 @@ return array(
|
||||||
'rsrc/js/application/policy/behavior-policy-control.js' => '71b4cbcc',
|
'rsrc/js/application/policy/behavior-policy-control.js' => '71b4cbcc',
|
||||||
'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '263aeb8c',
|
'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '263aeb8c',
|
||||||
'rsrc/js/application/ponder/behavior-votebox.js' => '327dbe61',
|
'rsrc/js/application/ponder/behavior-votebox.js' => '327dbe61',
|
||||||
|
'rsrc/js/application/projects/behavior-boards-filter.js' => '22f113af',
|
||||||
'rsrc/js/application/projects/behavior-project-boards.js' => 'd8e135db',
|
'rsrc/js/application/projects/behavior-project-boards.js' => 'd8e135db',
|
||||||
'rsrc/js/application/projects/behavior-project-create.js' => '065227cc',
|
'rsrc/js/application/projects/behavior-project-create.js' => '065227cc',
|
||||||
'rsrc/js/application/releeph/releeph-preview-branch.js' => '9eb2cedb',
|
'rsrc/js/application/releeph/releeph-preview-branch.js' => '9eb2cedb',
|
||||||
|
@ -544,6 +545,7 @@ return array(
|
||||||
'javelin-behavior-audio-source' => '59b251eb',
|
'javelin-behavior-audio-source' => '59b251eb',
|
||||||
'javelin-behavior-audit-preview' => 'be81801d',
|
'javelin-behavior-audit-preview' => 'be81801d',
|
||||||
'javelin-behavior-balanced-payment-form' => '3b3e1664',
|
'javelin-behavior-balanced-payment-form' => '3b3e1664',
|
||||||
|
'javelin-behavior-boards-filter' => '22f113af',
|
||||||
'javelin-behavior-config-reorder-fields' => '938aed89',
|
'javelin-behavior-config-reorder-fields' => '938aed89',
|
||||||
'javelin-behavior-conpherence-menu' => '7ee23816',
|
'javelin-behavior-conpherence-menu' => '7ee23816',
|
||||||
'javelin-behavior-conpherence-pontificate' => '53f6f2dd',
|
'javelin-behavior-conpherence-pontificate' => '53f6f2dd',
|
||||||
|
@ -1004,6 +1006,13 @@ return array(
|
||||||
5 => 'javelin-json',
|
5 => 'javelin-json',
|
||||||
6 => 'phabricator-prefab',
|
6 => 'phabricator-prefab',
|
||||||
),
|
),
|
||||||
|
'22f113af' =>
|
||||||
|
array(
|
||||||
|
0 => 'javelin-behavior',
|
||||||
|
1 => 'javelin-dom',
|
||||||
|
2 => 'javelin-stratcom',
|
||||||
|
3 => 'phuix-dropdown-menu',
|
||||||
|
),
|
||||||
'263aeb8c' =>
|
'263aeb8c' =>
|
||||||
array(
|
array(
|
||||||
0 => 'javelin-behavior',
|
0 => 'javelin-behavior',
|
||||||
|
|
|
@ -95,6 +95,22 @@ final class ManiphestTaskQuery
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an additional "all projects" constraint to existing filters.
|
||||||
|
*
|
||||||
|
* This is used by boards to supplement queries.
|
||||||
|
*
|
||||||
|
* @param list<phid> List of project PHIDs to add to any existing constriant.
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public function addWithAllProjects(array $projects) {
|
||||||
|
if ($this->projectPHIDs === null) {
|
||||||
|
$this->projectPHIDs = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->withAllProjects(array_merge($this->projectPHIDs, $projects));
|
||||||
|
}
|
||||||
|
|
||||||
public function withoutProjects(array $projects) {
|
public function withoutProjects(array $projects) {
|
||||||
$this->xprojectPHIDs = $projects;
|
$this->xprojectPHIDs = $projects;
|
||||||
return $this;
|
return $this;
|
||||||
|
|
|
@ -4,6 +4,26 @@ final class ManiphestTaskSearchEngine
|
||||||
extends PhabricatorApplicationSearchEngine {
|
extends PhabricatorApplicationSearchEngine {
|
||||||
|
|
||||||
private $showBatchControls;
|
private $showBatchControls;
|
||||||
|
private $baseURI;
|
||||||
|
private $isBoardView;
|
||||||
|
|
||||||
|
public function setIsBoardView($is_board_view) {
|
||||||
|
$this->isBoardView = $is_board_view;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIsBoardView() {
|
||||||
|
return $this->isBoardView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBaseURI($base_uri) {
|
||||||
|
$this->baseURI = $base_uri;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBaseURI() {
|
||||||
|
return $this->baseURI;
|
||||||
|
}
|
||||||
|
|
||||||
public function setShowBatchControls($show_batch_controls) {
|
public function setShowBatchControls($show_batch_controls) {
|
||||||
$this->showBatchControls = $show_batch_controls;
|
$this->showBatchControls = $show_batch_controls;
|
||||||
|
@ -301,14 +321,20 @@ final class ManiphestTaskSearchEngine
|
||||||
->setDatasource('/typeahead/common/projects/')
|
->setDatasource('/typeahead/common/projects/')
|
||||||
->setName('allProjects')
|
->setName('allProjects')
|
||||||
->setLabel(pht('In All Projects'))
|
->setLabel(pht('In All Projects'))
|
||||||
->setValue($all_project_handles))
|
->setValue($all_project_handles));
|
||||||
|
|
||||||
|
if (!$this->getIsBoardView()) {
|
||||||
|
$form
|
||||||
->appendChild(
|
->appendChild(
|
||||||
id(new AphrontFormCheckboxControl())
|
id(new AphrontFormCheckboxControl())
|
||||||
->addCheckbox(
|
->addCheckbox(
|
||||||
'withNoProject',
|
'withNoProject',
|
||||||
1,
|
1,
|
||||||
pht('Show only tasks with no projects.'),
|
pht('Show only tasks with no projects.'),
|
||||||
$with_no_projects))
|
$with_no_projects));
|
||||||
|
}
|
||||||
|
|
||||||
|
$form
|
||||||
->appendChild(
|
->appendChild(
|
||||||
id(new AphrontFormTokenizerControl())
|
id(new AphrontFormTokenizerControl())
|
||||||
->setDatasource('/typeahead/common/projects/')
|
->setDatasource('/typeahead/common/projects/')
|
||||||
|
@ -340,7 +366,10 @@ final class ManiphestTaskSearchEngine
|
||||||
->setLabel(pht('Subscribers'))
|
->setLabel(pht('Subscribers'))
|
||||||
->setValue($subscriber_handles))
|
->setValue($subscriber_handles))
|
||||||
->appendChild($status_control)
|
->appendChild($status_control)
|
||||||
->appendChild($priority_control)
|
->appendChild($priority_control);
|
||||||
|
|
||||||
|
if (!$this->getIsBoardView()) {
|
||||||
|
$form
|
||||||
->appendChild(
|
->appendChild(
|
||||||
id(new AphrontFormSelectControl())
|
id(new AphrontFormSelectControl())
|
||||||
->setName('group')
|
->setName('group')
|
||||||
|
@ -352,7 +381,10 @@ final class ManiphestTaskSearchEngine
|
||||||
->setName('order')
|
->setName('order')
|
||||||
->setLabel(pht('Order By'))
|
->setLabel(pht('Order By'))
|
||||||
->setValue($saved->getParameter('order'))
|
->setValue($saved->getParameter('order'))
|
||||||
->setOptions($this->getOrderOptions()))
|
->setOptions($this->getOrderOptions()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$form
|
||||||
->appendChild(
|
->appendChild(
|
||||||
id(new AphrontFormTextControl())
|
id(new AphrontFormTextControl())
|
||||||
->setName('fulltext')
|
->setName('fulltext')
|
||||||
|
@ -382,6 +414,7 @@ final class ManiphestTaskSearchEngine
|
||||||
'modifiedEnd',
|
'modifiedEnd',
|
||||||
pht('Updated Before'));
|
pht('Updated Before'));
|
||||||
|
|
||||||
|
if (!$this->getIsBoardView()) {
|
||||||
$form
|
$form
|
||||||
->appendChild(
|
->appendChild(
|
||||||
id(new AphrontFormTextControl())
|
id(new AphrontFormTextControl())
|
||||||
|
@ -389,8 +422,12 @@ final class ManiphestTaskSearchEngine
|
||||||
->setLabel(pht('Page Size'))
|
->setLabel(pht('Page Size'))
|
||||||
->setValue($saved->getParameter('limit', 100)));
|
->setValue($saved->getParameter('limit', 100)));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected function getURI($path) {
|
protected function getURI($path) {
|
||||||
|
if ($this->baseURI) {
|
||||||
|
return $this->baseURI.$path;
|
||||||
|
}
|
||||||
return '/maniphest/'.$path;
|
return '/maniphest/'.$path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,10 @@ final class PhabricatorApplicationProject extends PhabricatorApplication {
|
||||||
'picture/(?P<id>[1-9]\d*)/' =>
|
'picture/(?P<id>[1-9]\d*)/' =>
|
||||||
'PhabricatorProjectEditPictureController',
|
'PhabricatorProjectEditPictureController',
|
||||||
'create/' => 'PhabricatorProjectCreateController',
|
'create/' => 'PhabricatorProjectCreateController',
|
||||||
'board/(?P<id>[1-9]\d*)/' => 'PhabricatorProjectBoardViewController',
|
'board/(?P<id>[1-9]\d*)/'.
|
||||||
|
'(?P<filter>filter/)?'.
|
||||||
|
'(?:query/(?P<queryKey>[^/]+)/)?' =>
|
||||||
|
'PhabricatorProjectBoardViewController',
|
||||||
'move/(?P<id>[1-9]\d*)/' => 'PhabricatorProjectMoveController',
|
'move/(?P<id>[1-9]\d*)/' => 'PhabricatorProjectMoveController',
|
||||||
'board/(?P<projectID>[1-9]\d*)/edit/(?:(?P<id>\d+)/)?'
|
'board/(?P<projectID>[1-9]\d*)/edit/(?:(?P<id>\d+)/)?'
|
||||||
=> 'PhabricatorProjectBoardEditController',
|
=> 'PhabricatorProjectBoardEditController',
|
||||||
|
|
|
@ -5,6 +5,8 @@ final class PhabricatorProjectBoardViewController
|
||||||
|
|
||||||
private $id;
|
private $id;
|
||||||
private $handles;
|
private $handles;
|
||||||
|
private $queryKey;
|
||||||
|
private $filter;
|
||||||
|
|
||||||
public function shouldAllowPublic() {
|
public function shouldAllowPublic() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -12,6 +14,8 @@ final class PhabricatorProjectBoardViewController
|
||||||
|
|
||||||
public function willProcessRequest(array $data) {
|
public function willProcessRequest(array $data) {
|
||||||
$this->id = $data['id'];
|
$this->id = $data['id'];
|
||||||
|
$this->queryKey = idx($data, 'queryKey');
|
||||||
|
$this->filter = (bool)idx($data, 'filter');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function processRequest() {
|
public function processRequest() {
|
||||||
|
@ -50,12 +54,63 @@ final class PhabricatorProjectBoardViewController
|
||||||
|
|
||||||
ksort($columns);
|
ksort($columns);
|
||||||
|
|
||||||
$tasks = id(new ManiphestTaskQuery())
|
$board_uri = $this->getApplicationURI('board/'.$project->getID().'/');
|
||||||
|
|
||||||
|
$engine = id(new ManiphestTaskSearchEngine())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withAllProjects(array($project->getPHID()))
|
->setBaseURI($board_uri)
|
||||||
->withStatuses(ManiphestTaskStatus::getOpenStatusConstants())
|
->setIsBoardView(true);
|
||||||
|
|
||||||
|
if ($request->isFormPost()) {
|
||||||
|
$saved = $engine->buildSavedQueryFromRequest($request);
|
||||||
|
$engine->saveQuery($saved);
|
||||||
|
return id(new AphrontRedirectResponse())->setURI(
|
||||||
|
$engine->getQueryResultsPageURI($saved->getQueryKey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$query_key = $this->queryKey;
|
||||||
|
if (!$query_key) {
|
||||||
|
$query_key = 'open';
|
||||||
|
}
|
||||||
|
|
||||||
|
$custom_query = 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) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$custom_query = $saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->filter) {
|
||||||
|
$filter_form = id(new AphrontFormView())
|
||||||
|
->setUser($viewer);
|
||||||
|
$engine->buildSearchForm($filter_form, $saved);
|
||||||
|
|
||||||
|
return $this->newDialog()
|
||||||
|
->setWidth(AphrontDialogView::WIDTH_FULL)
|
||||||
|
->setTitle(pht('Advanced Filter'))
|
||||||
|
->appendChild($filter_form->buildLayoutView())
|
||||||
|
->setSubmitURI($board_uri)
|
||||||
|
->addSubmitButton(pht('Apply Filter'))
|
||||||
|
->addCancelButton($board_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
$task_query = $engine->buildQueryFromSavedQuery($saved);
|
||||||
|
|
||||||
|
$tasks = $task_query
|
||||||
|
->addWithAllProjects(array($project->getPHID()))
|
||||||
->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY)
|
->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY)
|
||||||
|
->setViewer($viewer)
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
$tasks = mpull($tasks, null, 'getPHID');
|
$tasks = mpull($tasks, null, 'getPHID');
|
||||||
$task_phids = array_keys($tasks);
|
$task_phids = array_keys($tasks);
|
||||||
|
|
||||||
|
@ -166,6 +221,87 @@ final class PhabricatorProjectBoardViewController
|
||||||
->setDisabled(!$can_edit)
|
->setDisabled(!$can_edit)
|
||||||
->setWorkflow(!$can_edit);
|
->setWorkflow(!$can_edit);
|
||||||
|
|
||||||
|
Javelin::initBehavior(
|
||||||
|
'boards-filter',
|
||||||
|
array(
|
||||||
|
));
|
||||||
|
|
||||||
|
$filter_icon = id(new PHUIIconView())
|
||||||
|
->setIconFont('fa-search-plus bluegrey');
|
||||||
|
|
||||||
|
$named = array(
|
||||||
|
'open' => pht('Open Tasks'),
|
||||||
|
'all' => pht('All Tasks'),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($viewer->isLoggedIn()) {
|
||||||
|
$named['assigned'] = pht('Assigned to Me');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($custom_query) {
|
||||||
|
$named[$custom_query->getQueryKey()] = pht('Custom Filter');
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = array();
|
||||||
|
foreach ($named as $key => $name) {
|
||||||
|
$is_selected = ($key == $query_key);
|
||||||
|
if ($is_selected) {
|
||||||
|
$active_filter = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
$is_custom = false;
|
||||||
|
if ($custom_query) {
|
||||||
|
$is_custom = ($key == $custom_query->getQueryKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
$item = id(new PhabricatorActionView())
|
||||||
|
->setIcon('fa-search')
|
||||||
|
->setSelected($is_selected)
|
||||||
|
->setName($name);
|
||||||
|
|
||||||
|
if ($is_custom) {
|
||||||
|
$item->setHref(
|
||||||
|
$this->getApplicationURI(
|
||||||
|
'board/'.$this->id.'/filter/query/'.$key.'/'));
|
||||||
|
$item->setWorkflow(true);
|
||||||
|
} else {
|
||||||
|
$item->setHref($engine->getQueryResultsPageURI($key));
|
||||||
|
}
|
||||||
|
|
||||||
|
$items[] = $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
$items[] = id(new PhabricatorActionView())
|
||||||
|
->setIcon('fa-cog')
|
||||||
|
->setHref($this->getApplicationURI('board/'.$this->id.'/filter/'))
|
||||||
|
->setWorkflow(true)
|
||||||
|
->setName(pht('Advanced Filter...'));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$filter_menu = id(new PhabricatorActionListView())
|
||||||
|
->setUser($viewer);
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$filter_menu->addAction($item);
|
||||||
|
}
|
||||||
|
|
||||||
|
$filter_button = id(new PHUIButtonView())
|
||||||
|
->setText(pht('Filter: %s', $active_filter))
|
||||||
|
->setIcon($filter_icon)
|
||||||
|
->setTag('a')
|
||||||
|
->setHref('#')
|
||||||
|
->addSigil('boards-filter-menu')
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO: @chad, this looks really gnarly right now, at least in Safari.
|
||||||
|
->setDropdown(true)
|
||||||
|
*/
|
||||||
|
|
||||||
|
->setMetadata(
|
||||||
|
array(
|
||||||
|
'items' => hsprintf('%s', $filter_menu),
|
||||||
|
));
|
||||||
|
|
||||||
$header_link = phutil_tag(
|
$header_link = phutil_tag(
|
||||||
'a',
|
'a',
|
||||||
array(
|
array(
|
||||||
|
@ -179,6 +315,7 @@ final class PhabricatorProjectBoardViewController
|
||||||
->setNoBackground(true)
|
->setNoBackground(true)
|
||||||
->setImage($project->getProfileImageURI())
|
->setImage($project->getProfileImageURI())
|
||||||
->setImageURL($this->getApplicationURI('view/'.$project->getID().'/'))
|
->setImageURL($this->getApplicationURI('view/'.$project->getID().'/'))
|
||||||
|
->addActionLink($filter_button)
|
||||||
->addActionLink($add_button)
|
->addActionLink($add_button)
|
||||||
->setPolicyObject($project);
|
->setPolicyObject($project);
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,7 @@ final class ProjectBoardTaskCard {
|
||||||
->setGrippable($can_edit)
|
->setGrippable($can_edit)
|
||||||
->setHref('/T'.$task->getID())
|
->setHref('/T'.$task->getID())
|
||||||
->addSigil('project-card')
|
->addSigil('project-card')
|
||||||
|
->setDisabled($task->isClosed())
|
||||||
->setMetadata(
|
->setMetadata(
|
||||||
array(
|
array(
|
||||||
'objectPHID' => $task->getPHID(),
|
'objectPHID' => $task->getPHID(),
|
||||||
|
|
|
@ -90,7 +90,7 @@ final class PhabricatorApplicationSearchController
|
||||||
|
|
||||||
if ($request->isFormPost()) {
|
if ($request->isFormPost()) {
|
||||||
$saved_query = $engine->buildSavedQueryFromRequest($request);
|
$saved_query = $engine->buildSavedQueryFromRequest($request);
|
||||||
$this->saveQuery($saved_query);
|
$engine->saveQuery($saved_query);
|
||||||
return id(new AphrontRedirectResponse())->setURI(
|
return id(new AphrontRedirectResponse())->setURI(
|
||||||
$engine->getQueryResultsPageURI($saved_query->getQueryKey()).'#R');
|
$engine->getQueryResultsPageURI($saved_query->getQueryKey()).'#R');
|
||||||
}
|
}
|
||||||
|
@ -145,7 +145,7 @@ final class PhabricatorApplicationSearchController
|
||||||
|
|
||||||
// Save the query to generate a query key, so "Save Custom Query..." and
|
// Save the query to generate a query key, so "Save Custom Query..." and
|
||||||
// other features like Maniphest's "Export..." work correctly.
|
// other features like Maniphest's "Export..." work correctly.
|
||||||
$this->saveQuery($saved_query);
|
$engine->saveQuery($saved_query);
|
||||||
}
|
}
|
||||||
|
|
||||||
$nav->selectFilter(
|
$nav->selectFilter(
|
||||||
|
@ -353,18 +353,6 @@ final class PhabricatorApplicationSearchController
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function saveQuery(PhabricatorSavedQuery $query) {
|
|
||||||
$query->setEngineClassName(get_class($this->getSearchEngine()));
|
|
||||||
|
|
||||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
|
||||||
try {
|
|
||||||
$query->save();
|
|
||||||
} catch (AphrontQueryDuplicateKeyException $ex) {
|
|
||||||
// Ignore, this is just a repeated search.
|
|
||||||
}
|
|
||||||
unset($unguarded);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function buildApplicationMenu() {
|
protected function buildApplicationMenu() {
|
||||||
return $this->getDelegatingController()->buildApplicationMenu();
|
return $this->getDelegatingController()->buildApplicationMenu();
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,18 @@ abstract class PhabricatorApplicationSearchEngine {
|
||||||
return $this->viewer;
|
return $this->viewer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function saveQuery(PhabricatorSavedQuery $query) {
|
||||||
|
$query->setEngineClassName(get_class($this));
|
||||||
|
|
||||||
|
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||||
|
try {
|
||||||
|
$query->save();
|
||||||
|
} catch (AphrontQueryDuplicateKeyException $ex) {
|
||||||
|
// Ignore, this is just a repeated search.
|
||||||
|
}
|
||||||
|
unset($unguarded);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a saved query object from the request.
|
* Create a saved query object from the request.
|
||||||
*
|
*
|
||||||
|
|
|
@ -12,6 +12,16 @@ final class PhabricatorActionView extends AphrontView {
|
||||||
private $objectURI;
|
private $objectURI;
|
||||||
private $sigils = array();
|
private $sigils = array();
|
||||||
private $metadata;
|
private $metadata;
|
||||||
|
private $selected;
|
||||||
|
|
||||||
|
public function setSelected($selected) {
|
||||||
|
$this->selected = $selected;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSelected() {
|
||||||
|
return $this->selected;
|
||||||
|
}
|
||||||
|
|
||||||
public function setMetadata($metadata) {
|
public function setMetadata($metadata) {
|
||||||
$this->metadata = $metadata;
|
$this->metadata = $metadata;
|
||||||
|
@ -167,6 +177,10 @@ final class PhabricatorActionView extends AphrontView {
|
||||||
$classes[] = 'phabricator-action-view-disabled';
|
$classes[] = 'phabricator-action-view-disabled';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->selected) {
|
||||||
|
$classes[] = 'phabricator-action-view-selected';
|
||||||
|
}
|
||||||
|
|
||||||
return phutil_tag(
|
return phutil_tag(
|
||||||
'li',
|
'li',
|
||||||
array(
|
array(
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
/**
|
||||||
|
* @provides javelin-behavior-boards-filter
|
||||||
|
* @requires javelin-behavior
|
||||||
|
* javelin-dom
|
||||||
|
* javelin-stratcom
|
||||||
|
* phuix-dropdown-menu
|
||||||
|
*/
|
||||||
|
|
||||||
|
JX.behavior('boards-filter', function(config) {
|
||||||
|
|
||||||
|
JX.Stratcom.listen('click', 'boards-filter-menu', function(e) {
|
||||||
|
var data = e.getNodeData('boards-filter-menu');
|
||||||
|
if (data.menu) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.kill();
|
||||||
|
|
||||||
|
var list = JX.$H(data.items).getFragment().firstChild;
|
||||||
|
|
||||||
|
var button = e.getNode('boards-filter-menu');
|
||||||
|
data.menu = new JX.PHUIXDropdownMenu(button);
|
||||||
|
data.menu.setContent(list);
|
||||||
|
data.menu.open();
|
||||||
|
|
||||||
|
JX.DOM.listen(list, 'click', 'tag:a', function(e) {
|
||||||
|
if (!e.isNormalClick()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.menu.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
Loading…
Reference in a new issue