Index: resources/celerity/map.php =================================================================== --- resources/celerity/map.php +++ resources/celerity/map.php @@ -7,14 +7,14 @@ return array( 'names' => array( - 'core.pkg.css' => '044c2f0c', - 'core.pkg.js' => '0dc59a05', + 'core.pkg.css' => '1ccefdc6', + 'core.pkg.js' => 'ee746639', 'darkconsole.pkg.js' => 'ca8671ce', 'differential.pkg.css' => '6aef439e', 'differential.pkg.js' => '322ea941', 'diffusion.pkg.css' => '3783278d', 'diffusion.pkg.js' => '7b51e80a', - 'javelin.pkg.js' => '896bb02e', + 'javelin.pkg.js' => 'da52b0df', 'maniphest.pkg.css' => 'f1887d71', 'maniphest.pkg.js' => '1e8f11af', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', @@ -37,7 +37,7 @@ 'rsrc/css/aphront/tooltip.css' => '9c90229d', 'rsrc/css/aphront/transaction.css' => 'ce491938', 'rsrc/css/aphront/two-column.css' => '16ab3ad2', - 'rsrc/css/aphront/typeahead.css' => '104a6525', + 'rsrc/css/aphront/typeahead.css' => 'd24b4228', 'rsrc/css/application/auth/auth.css' => '1e655982', 'rsrc/css/application/base/main-menu-view.css' => 'd36e0c11', 'rsrc/css/application/base/notification-menu.css' => 'fc9a363c', @@ -207,13 +207,13 @@ 'rsrc/externals/javelin/lib/__tests__/URI.js' => 'ece3ddb3', 'rsrc/externals/javelin/lib/__tests__/behavior.js' => 'c1d75ee6', 'rsrc/externals/javelin/lib/behavior.js' => '8a3ed18b', - 'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => 'e7c21fb3', - 'rsrc/externals/javelin/lib/control/typeahead/Typeahead.js' => 'c22f4c01', + 'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => '1c1a6cdf', + 'rsrc/externals/javelin/lib/control/typeahead/Typeahead.js' => 'd99e27f7', 'rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js' => '5f850b5c', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => 'dbd9cd11', - 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js' => '1f595fb0', + 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js' => '7383383f', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js' => 'e9b95df3', - 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => 'f4412299', + 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => '74fe50ac', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js' => 'c2b8bf64', 'rsrc/externals/raphael/g.raphael.js' => '40dde778', 'rsrc/externals/raphael/g.raphael.line.js' => '40da039e', @@ -287,6 +287,7 @@ 'rsrc/image/loading/loading_48.gif' => '6a4994c7', 'rsrc/image/loading/loading_d48.gif' => 'cdcbe900', 'rsrc/image/loading/loading_w24.gif' => '7662fa2b', + 'rsrc/image/loading/tokenizer_loading.gif' => '93622511', 'rsrc/image/main_texture.png' => '29a2c5ad', 'rsrc/image/menu_texture.png' => '5a17580d', 'rsrc/image/people/harding.png' => '45aa614e', @@ -431,7 +432,7 @@ 'rsrc/js/core/KeyboardShortcutManager.js' => 'ad7a69ca', 'rsrc/js/core/MultirowRowManager.js' => 'e7076916', 'rsrc/js/core/Notification.js' => '95944043', - 'rsrc/js/core/Prefab.js' => '979f864d', + 'rsrc/js/core/Prefab.js' => '83ee580c', 'rsrc/js/core/ShapedRequest.js' => 'dfa181a4', 'rsrc/js/core/TextAreaUtils.js' => 'b3ec3cfc', 'rsrc/js/core/ToolTip.js' => '0a81ea29', @@ -492,7 +493,7 @@ 'aphront-tokenizer-control-css' => '08ea6326', 'aphront-tooltip-css' => '9c90229d', 'aphront-two-column-view-css' => '16ab3ad2', - 'aphront-typeahead-control-css' => '104a6525', + 'aphront-typeahead-control-css' => 'd24b4228', 'auth-css' => '1e655982', 'config-options-css' => '7fedf08b', 'conpherence-menu-css' => '561348ac', @@ -641,13 +642,13 @@ 'javelin-request' => '23f9bb8d', 'javelin-resource' => '356de121', 'javelin-stratcom' => 'c293f7b9', - 'javelin-tokenizer' => 'e7c21fb3', - 'javelin-typeahead' => 'c22f4c01', + 'javelin-tokenizer' => '1c1a6cdf', + 'javelin-typeahead' => 'd99e27f7', 'javelin-typeahead-composite-source' => 'dbd9cd11', 'javelin-typeahead-normalizer' => '5f850b5c', - 'javelin-typeahead-ondemand-source' => '1f595fb0', + 'javelin-typeahead-ondemand-source' => '7383383f', 'javelin-typeahead-preloaded-source' => 'e9b95df3', - 'javelin-typeahead-source' => 'f4412299', + 'javelin-typeahead-source' => '74fe50ac', 'javelin-typeahead-static-source' => 'c2b8bf64', 'javelin-uri' => 'd9a9b862', 'javelin-util' => '7501647b', @@ -701,7 +702,7 @@ 'phabricator-object-list-view-css' => '1a1ea560', 'phabricator-object-selector-css' => '029a133d', 'phabricator-phtize' => 'd254d646', - 'phabricator-prefab' => '979f864d', + 'phabricator-prefab' => '83ee580c', 'phabricator-profile-css' => '3a7e04ca', 'phabricator-project-tag-css' => '095c9404', 'phabricator-remarkup-css' => 'ca7f2265', @@ -909,6 +910,13 @@ 4 => 'javelin-workflow', 5 => 'phabricator-draggable-list', ), + '1c1a6cdf' => + array( + 0 => 'javelin-dom', + 1 => 'javelin-util', + 2 => 'javelin-stratcom', + 3 => 'javelin-install', + ), '1e1c8a59' => array( 0 => 'javelin-behavior', @@ -928,13 +936,6 @@ 5 => 'phabricator-drag-and-drop-file-upload', 6 => 'phabricator-draggable-list', ), - '1f595fb0' => - array( - 0 => 'javelin-install', - 1 => 'javelin-util', - 2 => 'javelin-request', - 3 => 'javelin-typeahead-source', - ), '2290aeef' => array( 0 => 'javelin-install', @@ -1226,6 +1227,20 @@ 0 => 'javelin-behavior', 1 => 'javelin-dom', ), + '7383383f' => + array( + 0 => 'javelin-install', + 1 => 'javelin-util', + 2 => 'javelin-request', + 3 => 'javelin-typeahead-source', + ), + '74fe50ac' => + array( + 0 => 'javelin-install', + 1 => 'javelin-util', + 2 => 'javelin-dom', + 3 => 'javelin-typeahead-normalizer', + ), '75e50c72' => array( 0 => 'javelin-behavior', @@ -1293,6 +1308,19 @@ 1 => 'javelin-dom', 2 => 'javelin-reactor-dom', ), + '83ee580c' => + array( + 0 => 'javelin-install', + 1 => 'javelin-util', + 2 => 'javelin-dom', + 3 => 'javelin-typeahead', + 4 => 'javelin-tokenizer', + 5 => 'javelin-typeahead-preloaded-source', + 6 => 'javelin-typeahead-ondemand-source', + 7 => 'javelin-dom', + 8 => 'javelin-stratcom', + 9 => 'javelin-util', + ), '8454ce4f' => array( 0 => 'javelin-behavior', @@ -1398,19 +1426,6 @@ 2 => 'javelin-view-visitor', 3 => 'javelin-util', ), - '979f864d' => - array( - 0 => 'javelin-install', - 1 => 'javelin-util', - 2 => 'javelin-dom', - 3 => 'javelin-typeahead', - 4 => 'javelin-tokenizer', - 5 => 'javelin-typeahead-preloaded-source', - 6 => 'javelin-typeahead-ondemand-source', - 7 => 'javelin-dom', - 8 => 'javelin-stratcom', - 9 => 'javelin-util', - ), '9b9197be' => array( 0 => 'javelin-behavior', @@ -1604,13 +1619,6 @@ 5 => 'javelin-workflow', 6 => 'javelin-vector', ), - 'c22f4c01' => - array( - 0 => 'javelin-install', - 1 => 'javelin-dom', - 2 => 'javelin-vector', - 3 => 'javelin-util', - ), 'c293f7b9' => array( 0 => 'javelin-install', @@ -1752,6 +1760,13 @@ 7 => 'phabricator-dropdown-menu', 8 => 'phabricator-menu-item', ), + 'd99e27f7' => + array( + 0 => 'javelin-install', + 1 => 'javelin-dom', + 2 => 'javelin-vector', + 3 => 'javelin-util', + ), 'd9a9b862' => array( 0 => 'javelin-install', @@ -1815,13 +1830,6 @@ 2 => 'javelin-dom', 3 => 'javelin-util', ), - 'e7c21fb3' => - array( - 0 => 'javelin-dom', - 1 => 'javelin-util', - 2 => 'javelin-stratcom', - 3 => 'javelin-install', - ), 'e9b95df3' => array( 0 => 'javelin-install', @@ -1874,13 +1882,6 @@ 2 => 'javelin-vector', 3 => 'javelin-dom', ), - 'f4412299' => - array( - 0 => 'javelin-install', - 1 => 'javelin-util', - 2 => 'javelin-dom', - 3 => 'javelin-typeahead-normalizer', - ), 'f6b56f7a' => array( 0 => 'javelin-behavior', Index: webroot/rsrc/css/aphront/typeahead.css =================================================================== --- webroot/rsrc/css/aphront/typeahead.css +++ webroot/rsrc/css/aphront/typeahead.css @@ -47,3 +47,21 @@ input.jx-typeahead-placeholder { color: {$lightgreytext}; } + +.jx-typeahead-waiting-indicator { + position: absolute; + right: 8px; + top: 2px; + width: 24px; + height: 24px; + background-image: url(/rsrc/image/loading/tokenizer_loading.gif); + display: none; +} + +.jx-typeahead-waiting .jx-typeahead-waiting-indicator { + display: inline-block; +} + +div.jx-tokenizer-container-focused.jx-typeahead-waiting { + border-color: {$lightblueborder}; +} Index: webroot/rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js =================================================================== --- webroot/rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js +++ webroot/rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js @@ -36,6 +36,7 @@ JX.install('Tokenizer', { construct : function(containerNode) { this._containerNode = containerNode; + this._uiNodes = []; }, events : [ @@ -63,6 +64,7 @@ _seq : 0, _lastvalue : null, _placeholder : null, + _uinodes : null, start : function() { if (__DEV__) { @@ -124,6 +126,7 @@ this._root = root; root.appendChild(focus); + JX.DOM.appendContent(root, this._uiNodes); var typeahead = this._typeahead; typeahead.setInputNode(this._focus); @@ -232,6 +235,12 @@ return this; }, + addUINode : function(node) { + this._uiNodes.push(node); + this._typeahead.addUINode(node); + return this; + }, + _redraw : function(force) { // If there are tokens in the tokenizer, never show a placeholder. Index: webroot/rsrc/externals/javelin/lib/control/typeahead/Typeahead.js =================================================================== --- webroot/rsrc/externals/javelin/lib/control/typeahead/Typeahead.js +++ webroot/rsrc/externals/javelin/lib/control/typeahead/Typeahead.js @@ -124,6 +124,7 @@ _datasource : null, _waitingListener : null, _readyListener : null, + _completeListener : null, /** * Activate your properly configured typeahead. It won't do anything until @@ -160,15 +161,20 @@ this._datasource.unbindFromTypeahead(); this._waitingListener.remove(); this._readyListener.remove(); + this._completeListener.remove(); } this._waitingListener = datasource.listen( 'waiting', - JX.bind(this, this.waitForResults) - ); + JX.bind(this, this.waitForResults)); + this._readyListener = datasource.listen( 'resultsready', - JX.bind(this, this.showResults) - ); + JX.bind(this, this.showResults)); + + this._completeListener = datasource.listen( + 'complete', + JX.bind(this, this.doneWaitingForResults)); + datasource.bindToTypeahead(this); this._datasource = datasource; }, @@ -190,6 +196,19 @@ return this; }, + /** + * Add an arbitrary node to the UI. Phabricator uses this to add a + * "waiting" graphic. + * + * @param node An arbitrary display node for the UI. + * @return this + * @task config + */ + addUINode : function(node) { + JX.DOM.appendContent(this._hardpoint, node); + return this; + }, + /** * Hide the typeahead's dropdown suggestion menu. @@ -247,18 +266,27 @@ this._value = this._control.value; this.invoke('change', this._value); }, + /** - * Show a "waiting for results" UI in place of the typeahead's dropdown - * suggestion menu. NOTE: currently there's no such UI, lolol. + * Show a "waiting for results" UI. We may be showing a partial result set + * at this time, if the user is extending a query we already have results + * for. * * @task control * @return void */ waitForResults : function() { - // TODO: Build some sort of fancy spinner or "..." type UI here to - // visually indicate that we're waiting on the server. - // Wait on the datasource 'complete' event for hiding the spinner. - this.hide(); + JX.DOM.alterClass(this._hardpoint, 'jx-typeahead-waiting', true); + }, + + /** + * Hide the "waiting for results" UI. + * + * @task control + * @return void + */ + doneWaitingForResults : function() { + JX.DOM.alterClass(this._hardpoint, 'jx-typeahead-waiting', false); }, /** Index: webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js =================================================================== --- webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js +++ webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js @@ -48,6 +48,19 @@ if (this.haveData[value]) { this.matchResults(value); } else { + // If we have data for any prefix of the query, send those results + // back immediately. This allows "alinc" -> "alinco" to show partial + // results without the UI flickering. We'll still show the loading + // state, and then can show better results once we get everything + // back. + for (var ii = value.length - 1; ii > 0; ii--) { + var substr = value.substring(0, ii); + if (this.haveData[substr]) { + this.matchResults(value, false); + break; + } + } + this.waitForResults(); setTimeout( JX.bind(this, this.sendRequest, this.lastChange, value, raw_value), Index: webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js =================================================================== --- webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js +++ webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js @@ -211,7 +211,7 @@ }, - matchResults : function(value) { + matchResults : function(value, partial) { // This table keeps track of the number of tokens each potential match // has actually matched. When we're done, the real matches are those @@ -279,7 +279,9 @@ var nodes = this.renderNodes(value, hits); this.invoke('resultsready', nodes); - this.invoke('complete'); + if (!partial) { + this.invoke('complete'); + } }, sortHits : function(value, hits) { Index: webroot/rsrc/js/core/Prefab.js =================================================================== --- webroot/rsrc/js/core/Prefab.js +++ webroot/rsrc/js/core/Prefab.js @@ -222,6 +222,12 @@ tokenizer.setInitialValue(config.value); } + tokenizer.addUINode( + JX.$N( + 'span', + {className: 'jx-typeahead-waiting-indicator'}, + null)); + JX.Stratcom.addData(root, {'tokenizer' : tokenizer}); return {