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' => '743edc44', - 'core.pkg.js' => '42315bf4', + 'core.pkg.css' => '7870a8c5', + 'core.pkg.js' => 'b00140fe', 'darkconsole.pkg.js' => '8ab24e01', 'differential.pkg.css' => '3500921f', 'differential.pkg.js' => 'c0506961', @@ -27,7 +27,7 @@ 'rsrc/css/aphront/panel-view.css' => '8427b78d', 'rsrc/css/aphront/phabricator-nav-view.css' => '7aeaf435', 'rsrc/css/aphront/table-view.css' => '59e2c0f8', - 'rsrc/css/aphront/tokenizer.css' => '95e931ab', + 'rsrc/css/aphront/tokenizer.css' => '6fd738ea', 'rsrc/css/aphront/tooltip.css' => '7672b60f', 'rsrc/css/aphront/two-column.css' => '16ab3ad2', 'rsrc/css/aphront/typeahead-browse.css' => '343ab59f', @@ -222,7 +222,7 @@ 'rsrc/externals/javelin/lib/__tests__/URI.js' => '1e45fda9', 'rsrc/externals/javelin/lib/__tests__/behavior.js' => '1ea62783', 'rsrc/externals/javelin/lib/behavior.js' => '61cbc29a', - 'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => '0fd17937', + 'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => 'dc708b7e', 'rsrc/externals/javelin/lib/control/typeahead/Typeahead.js' => '70baed2f', 'rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js' => 'e6e25838', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => '503e17fd', @@ -450,7 +450,7 @@ 'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f', 'rsrc/js/core/MultirowRowManager.js' => 'b5d57730', 'rsrc/js/core/Notification.js' => '0c6946e7', - 'rsrc/js/core/Prefab.js' => 'd339753f', + 'rsrc/js/core/Prefab.js' => '4c292cc5', 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 'rsrc/js/core/TextAreaUtils.js' => '5c93c52c', 'rsrc/js/core/Title.js' => 'df5e11d2', @@ -510,7 +510,7 @@ 'aphront-pager-view-css' => '2e3539af', 'aphront-panel-view-css' => '8427b78d', 'aphront-table-view-css' => '59e2c0f8', - 'aphront-tokenizer-control-css' => '95e931ab', + 'aphront-tokenizer-control-css' => '6fd738ea', 'aphront-tooltip-css' => '7672b60f', 'aphront-two-column-view-css' => '16ab3ad2', 'aphront-typeahead-control-css' => '0e403212', @@ -689,7 +689,7 @@ 'javelin-scrollbar' => 'eaa5b321', 'javelin-sound' => '949c0fe5', 'javelin-stratcom' => '6c53634d', - 'javelin-tokenizer' => '0fd17937', + 'javelin-tokenizer' => 'dc708b7e', 'javelin-typeahead' => '70baed2f', 'javelin-typeahead-composite-source' => '503e17fd', 'javelin-typeahead-normalizer' => 'e6e25838', @@ -744,7 +744,7 @@ 'phabricator-notification-menu-css' => '3c9d8aa1', 'phabricator-object-selector-css' => '029a133d', 'phabricator-phtize' => 'd254d646', - 'phabricator-prefab' => 'd339753f', + 'phabricator-prefab' => '4c292cc5', 'phabricator-profile-css' => '1a20dcbf', 'phabricator-remarkup-css' => 'bc65f3cc', 'phabricator-search-results-css' => '15c71110', @@ -921,12 +921,6 @@ 'javelin-install', 'javelin-util', ), - '0fd17937' => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - ), '13c739ea' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1158,6 +1152,18 @@ '4a2430d7' => array( 'phui-fontkit-css', ), + '4c292cc5' => array( + 'javelin-install', + 'javelin-util', + 'javelin-dom', + 'javelin-typeahead', + 'javelin-tokenizer', + 'javelin-typeahead-preloaded-source', + 'javelin-typeahead-ondemand-source', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-util', + ), '4d94d9c3' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1339,6 +1345,9 @@ 'javelin-vector', 'javelin-stratcom', ), + '6fd738ea' => array( + 'aphront-typeahead-control-css', + ), '70baed2f' => array( 'javelin-install', 'javelin-dom', @@ -1587,9 +1596,6 @@ 'javelin-resource', 'javelin-routable', ), - '95e931ab' => array( - 'aphront-typeahead-control-css', - ), '988040b4' => array( 'javelin-install', 'javelin-dom', @@ -1793,18 +1799,6 @@ 'd254d646' => array( 'javelin-util', ), - 'd339753f' => array( - 'javelin-install', - 'javelin-util', - 'javelin-dom', - 'javelin-typeahead', - 'javelin-tokenizer', - 'javelin-typeahead-preloaded-source', - 'javelin-typeahead-ondemand-source', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-util', - ), 'd4505101' => array( 'javelin-stratcom', 'javelin-install', @@ -1840,6 +1834,12 @@ 'javelin-dom', 'phabricator-busy', ), + 'dc708b7e' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + ), 'de2e896f' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1206,6 +1206,7 @@ 'PHUITimelineEventView' => 'view/phui/PHUITimelineEventView.php', 'PHUITimelineExample' => 'applications/uiexample/examples/PHUITimelineExample.php', 'PHUITimelineView' => 'view/phui/PHUITimelineView.php', + 'PHUITypeaheadExample' => 'applications/uiexample/examples/PHUITypeaheadExample.php', 'PHUIWorkboardView' => 'view/phui/PHUIWorkboardView.php', 'PHUIWorkpanelView' => 'view/phui/PHUIWorkpanelView.php', 'PackageCreateMail' => 'applications/owners/mail/PackageCreateMail.php', @@ -4489,6 +4490,7 @@ 'PHUITimelineEventView' => 'AphrontView', 'PHUITimelineExample' => 'PhabricatorUIExample', 'PHUITimelineView' => 'AphrontView', + 'PHUITypeaheadExample' => 'PhabricatorUIExample', 'PHUIWorkboardView' => 'AphrontTagView', 'PHUIWorkpanelView' => 'AphrontTagView', 'PackageCreateMail' => 'PackageMail', diff --git a/src/applications/people/typeahead/PhabricatorViewerDatasource.php b/src/applications/people/typeahead/PhabricatorViewerDatasource.php --- a/src/applications/people/typeahead/PhabricatorViewerDatasource.php +++ b/src/applications/people/typeahead/PhabricatorViewerDatasource.php @@ -50,6 +50,7 @@ return $this->newFunctionResult() ->setName(pht('Current Viewer')) ->setPHID('viewer()') + ->setIcon('fa-user') ->setUnique(true); } diff --git a/src/applications/phid/PhabricatorObjectHandle.php b/src/applications/phid/PhabricatorObjectHandle.php --- a/src/applications/phid/PhabricatorObjectHandle.php +++ b/src/applications/phid/PhabricatorObjectHandle.php @@ -56,9 +56,17 @@ if ($this->tagColor) { return $this->tagColor; } + return 'blue'; } + public function getIconColor() { + if ($this->tagColor) { + return $this->tagColor; + } + return null; + } + public function getTypeIcon() { if ($this->getPHIDType()) { return $this->getPHIDType()->getTypeIcon(); diff --git a/src/applications/project/typeahead/PhabricatorProjectDatasource.php b/src/applications/project/typeahead/PhabricatorProjectDatasource.php --- a/src/applications/project/typeahead/PhabricatorProjectDatasource.php +++ b/src/applications/project/typeahead/PhabricatorProjectDatasource.php @@ -63,7 +63,7 @@ ->setDisplayType('Project') ->setURI('/tag/'.$proj->getPrimarySlug().'/') ->setPHID($proj->getPHID()) - ->setIcon($proj->getIcon().' bluegrey') + ->setIcon($proj->getIcon().' '.$proj->getColor()) ->setPriorityType('proj') ->setClosed($closed); diff --git a/src/applications/project/typeahead/PhabricatorProjectMembersDatasource.php b/src/applications/project/typeahead/PhabricatorProjectMembersDatasource.php --- a/src/applications/project/typeahead/PhabricatorProjectMembersDatasource.php +++ b/src/applications/project/typeahead/PhabricatorProjectMembersDatasource.php @@ -116,6 +116,7 @@ ->setDisplayName(pht('Members: %s', $project->getName())) ->setURI('/tag/'.$project->getPrimarySlug().'/') ->setPHID('members('.$project->getPHID().')') + ->setIcon('fa-users') ->setClosed($closed); } diff --git a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php --- a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php +++ b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php @@ -187,16 +187,16 @@ } protected function newFunctionResult() { - // TODO: Find a more consistent design. return id(new PhabricatorTypeaheadResult()) - ->setIcon('fa-magic indigo'); + ->setTokenType(PhabricatorTypeaheadTokenView::TYPE_FUNCTION) + ->setIcon('fa-asterisk'); } public function newInvalidToken($name) { return id(new PhabricatorTypeaheadTokenView()) - ->setKey(PhabricatorTypeaheadTokenView::KEY_INVALID) ->setValue($name) - ->setIcon('fa-exclamation-circle red'); + ->setIcon('fa-exclamation-circle') + ->setTokenType(PhabricatorTypeaheadTokenView::TYPE_INVALID); } /* -( Token Functions )---------------------------------------------------- */ diff --git a/src/applications/typeahead/storage/PhabricatorTypeaheadResult.php b/src/applications/typeahead/storage/PhabricatorTypeaheadResult.php --- a/src/applications/typeahead/storage/PhabricatorTypeaheadResult.php +++ b/src/applications/typeahead/storage/PhabricatorTypeaheadResult.php @@ -13,6 +13,7 @@ private $imageSprite; private $icon; private $closed; + private $tokenType; private $unique; public function setIcon($icon) { @@ -91,6 +92,18 @@ return $this; } + public function setTokenType($type) { + $this->tokenType = $type; + return $this; + } + + public function getTokenType() { + if ($this->closed && !$this->tokenType) { + return PhabricatorTypeaheadTokenView::TYPE_DISABLED; + } + return $this->tokenType; + } + public function getSortKey() { // Put unique results (special parameter functions) ahead of other // results. @@ -116,6 +129,7 @@ $this->getIcon(), $this->closed, $this->imageSprite ? (string)$this->imageSprite : null, + $this->tokenType, $this->unique ? 1 : null, ); while (end($data) === null) { diff --git a/src/applications/typeahead/view/PhabricatorTypeaheadTokenView.php b/src/applications/typeahead/view/PhabricatorTypeaheadTokenView.php --- a/src/applications/typeahead/view/PhabricatorTypeaheadTokenView.php +++ b/src/applications/typeahead/view/PhabricatorTypeaheadTokenView.php @@ -3,12 +3,16 @@ final class PhabricatorTypeaheadTokenView extends AphrontTagView { + const TYPE_OBJECT = 'object'; + const TYPE_DISABLED = 'disabled'; + const TYPE_FUNCTION = 'function'; + const TYPE_INVALID = 'invalid'; + private $key; private $icon; private $inputName; private $value; - - const KEY_INVALID = ''; + private $tokenType = self::TYPE_OBJECT; public static function newFromTypeaheadResult( PhabricatorTypeaheadResult $result) { @@ -16,16 +20,24 @@ return id(new PhabricatorTypeaheadTokenView()) ->setKey($result->getPHID()) ->setIcon($result->getIcon()) - ->setValue($result->getDisplayName()); + ->setValue($result->getDisplayName()) + ->setTokenType($result->getTokenType()); } public static function newFromHandle( PhabricatorObjectHandle $handle) { - return id(new PhabricatorTypeaheadTokenView()) + $token = id(new PhabricatorTypeaheadTokenView()) ->setKey($handle->getPHID()) ->setValue($handle->getFullName()) - ->setIcon($handle->getIcon()); + ->setIcon(rtrim($handle->getIcon().' '.$handle->getIconColor())); + + if ($handle->isDisabled() || + $handle->getStatus() == PhabricatorObjectHandleStatus::STATUS_CLOSED) { + $token->setTokenType(self::TYPE_DISABLED); + } + + return $token; } public function setKey($key) { @@ -37,6 +49,15 @@ return $this->key; } + public function setTokenType($token_type) { + $this->tokenType = $token_type; + return $this; + } + + public function getTokenType() { + return $this->tokenType; + } + public function setInputName($input_name) { $this->inputName = $input_name; return $this; @@ -69,8 +90,25 @@ } protected function getTagAttributes() { + $classes = array(); + $classes[] = 'jx-tokenizer-token'; + switch ($this->getTokenType()) { + case self::TYPE_FUNCTION: + $classes[] = 'jx-tokenizer-token-function'; + break; + case self::TYPE_INVALID: + $classes[] = 'jx-tokenizer-token-invalid'; + break; + case self::TYPE_DISABLED: + $classes[] = 'jx-tokenizer-token-disabled'; + break; + case self::TYPE_OBJECT: + default: + break; + } + return array( - 'class' => 'jx-tokenizer-token', + 'class' => $classes, ); } diff --git a/src/applications/uiexample/examples/PHUITypeaheadExample.php b/src/applications/uiexample/examples/PHUITypeaheadExample.php new file mode 100644 --- /dev/null +++ b/src/applications/uiexample/examples/PHUITypeaheadExample.php @@ -0,0 +1,57 @@ +setValue(pht('Normal Object')) + ->setIcon('fa-user'); + + $token_list[] = id(new PhabricatorTypeaheadTokenView()) + ->setValue(pht('Disabled Object')) + ->setTokenType(PhabricatorTypeaheadTokenView::TYPE_DISABLED) + ->setIcon('fa-user'); + + $token_list[] = id(new PhabricatorTypeaheadTokenView()) + ->setValue(pht('Custom Object')) + ->setIcon('fa-tag green'); + + $token_list[] = id(new PhabricatorTypeaheadTokenView()) + ->setValue(pht('Function Token')) + ->setTokenType(PhabricatorTypeaheadTokenView::TYPE_FUNCTION) + ->setIcon('fa-users'); + + $token_list[] = id(new PhabricatorTypeaheadTokenView()) + ->setValue(pht('Invalid Token')) + ->setTokenType(PhabricatorTypeaheadTokenView::TYPE_INVALID) + ->setIcon('fa-exclamation-circle'); + + + $token_list = phutil_tag( + 'div', + array( + 'class' => 'grouped', + 'style' => 'padding: 8px', + ), + $token_list); + + $output = array(); + + $output[] = id(new PHUIObjectBoxView()) + ->setHeaderText('Tokens') + ->appendChild($token_list); + + return $output; + } +} diff --git a/src/view/form/control/AphrontFormTokenizerControl.php b/src/view/form/control/AphrontFormTokenizerControl.php --- a/src/view/form/control/AphrontFormTokenizerControl.php +++ b/src/view/form/control/AphrontFormTokenizerControl.php @@ -51,7 +51,9 @@ } $datasource = $this->datasource; - $datasource->setViewer($this->getUser()); + if ($datasource) { + $datasource->setViewer($this->getUser()); + } $placeholder = null; if (!strlen($this->placeholder)) { @@ -84,7 +86,8 @@ $token = $datasource->newInvalidToken($name); } - if ($token->getKey() == PhabricatorTypeaheadTokenView::KEY_INVALID) { + $type = $token->getTokenType(); + if ($type == PhabricatorTypeaheadTokenView::TYPE_INVALID) { $token->setKey($value); } } @@ -117,12 +120,13 @@ if (!$this->disableBehavior) { Javelin::initBehavior('aphront-basic-tokenizer', array( - 'id' => $id, - 'src' => $datasource_uri, - 'value' => mpull($tokens, 'getValue', 'getKey'), - 'icons' => mpull($tokens, 'getIcon', 'getKey'), - 'limit' => $this->limit, - 'username' => $username, + 'id' => $id, + 'src' => $datasource_uri, + 'value' => mpull($tokens, 'getValue', 'getKey'), + 'icons' => mpull($tokens, 'getIcon', 'getKey'), + 'types' => mpull($tokens, 'getTokenType', 'getKey'), + 'limit' => $this->limit, + 'username' => $username, 'placeholder' => $placeholder, 'browseURI' => $browse_uri, )); diff --git a/webroot/rsrc/css/aphront/tokenizer.css b/webroot/rsrc/css/aphront/tokenizer.css --- a/webroot/rsrc/css/aphront/tokenizer.css +++ b/webroot/rsrc/css/aphront/tokenizer.css @@ -83,6 +83,51 @@ color: {$bluetext}; } +a.jx-tokenizer-token-function { + border-color: {$sh-lightyellowborder}; + background: {$sh-yellowbackground}; + color: {$sh-yellowtext}; +} + +a.jx-tokenizer-token-function:hover { + border-color: {$sh-yellowborder}; + background: {$lightyellow}; +} + +.jx-tokenizer-token-function .phui-icon-view { + color: {$sh-yellowicon}; +} + +a.jx-tokenizer-token-disabled { + border-color: {$sh-lightgreyborder}; + background: {$sh-greybackground}; + color: {$sh-greytext}; +} + +a.jx-tokenizer-token-disabled:hover { + border-color: {$sh-greyborder}; + background: {$greybackground}; +} + +.jx-tokenizer-token-disabled .phui-icon-view { + color: {$sh-greyicon}; +} + +a.jx-tokenizer-token-invalid { + border-color: {$sh-lightredborder}; + background: {$sh-redbackground}; + color: {$sh-redtext}; +} + +a.jx-tokenizer-token-invalid:hover { + border-color: {$sh-redborder}; + background: {$lightred}; +} + +.jx-tokenizer-token-invalid .phui-icon-view { + color: {$sh-redicon}; +} + .tokenizer-result { position: relative; padding: 5px 8px 5px 28px; diff --git a/webroot/rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js b/webroot/rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js --- a/webroot/rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js +++ b/webroot/rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js @@ -348,16 +348,22 @@ }, '\u00d7'); // U+00D7 multiplication sign var display_token = value; - var render_callback = this.getRenderTokenCallback(); - if (render_callback) { - display_token = render_callback(value, key); - } - return JX.$N('a', { + var attrs = { className: 'jx-tokenizer-token', sigil: 'token', meta: {key: key} - }, [display_token, input, remove]); + }; + var container = JX.$N('a', attrs); + + var render_callback = this.getRenderTokenCallback(); + if (render_callback) { + display_token = render_callback(value, key, container); + } + + JX.DOM.setContent(container, [display_token, input, remove]); + + return container; }, getTokens : function() { diff --git a/webroot/rsrc/js/core/Prefab.js b/webroot/rsrc/js/core/Prefab.js --- a/webroot/rsrc/js/core/Prefab.js +++ b/webroot/rsrc/js/core/Prefab.js @@ -172,23 +172,27 @@ var tokenizer = new JX.Tokenizer(root); tokenizer.setTypeahead(typeahead); - tokenizer.setRenderTokenCallback(function(value, key) { + tokenizer.setRenderTokenCallback(function(value, key, container) { var result = datasource.getResult(key); var icon; + var type; if (result) { icon = result.icon; value = result.displayName; + type = result.tokenType; } else { icon = config.icons[key]; + type = config.types[key]; } if (icon) { icon = JX.Prefab._renderIcon(icon); } - // TODO: Maybe we should render these closed tags in grey? Figure out - // how we're going to use color. + if (type) { + JX.DOM.alterClass(container, 'jx-tokenizer-token-' + type, true); + } return [icon, value]; }); @@ -288,7 +292,8 @@ closed: closed, type: fields[5], sprite: fields[10], - unique: fields[11] || false + tokenType: fields[11], + unique: fields[12] || false }; },