1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-18 11:30:55 +01:00

Hide the autocompleter intelligently when you ignore it and keep typing

Summary:
Ref T10163. When we think the user has finished typing a word (because they typed a space, period, or other similar characters) and nothing else they might type could possibly change the outcome (usually because the words they have typed already match nothing), just deactivate the autocomplete.

As a special case, if the word they have typed already select exactly one result, //and// they have already typed exactly that result,  assume they just typed it from memory and deactivate.

Test Plan:
  - Typed `@dog qwer zxcv` and saw autocomplete deactivate on the space before `z` (on my local install, `@dog` is ambiguous but `@dog qwer` matches nothing).
  - Typed `@epriestley ` and saw autocomplete deactivate on space.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10163

Differential Revision: https://secure.phabricator.com/D15039
This commit is contained in:
epriestley 2016-01-16 14:33:03 -08:00
parent 849b4c765a
commit 75b8d3312b
4 changed files with 152 additions and 33 deletions

View file

@ -8,7 +8,7 @@
return array(
'names' => array(
'core.pkg.css' => '3a97c8b9',
'core.pkg.js' => '5813273d',
'core.pkg.js' => '573e6664',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '2de124c9',
'differential.pkg.js' => 'f83532f8',
@ -249,9 +249,9 @@ return array(
'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',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js' => '8b3fd187',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js' => '013ffff9',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js' => '54f314a0',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => '2818f5ce',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => '1bc11c4a',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js' => '6c0e62fa',
'rsrc/externals/raphael/g.raphael.js' => '40dde778',
'rsrc/externals/raphael/g.raphael.line.js' => '40da039e',
@ -507,7 +507,7 @@ return array(
'rsrc/js/phui/behavior-phui-object-box-tabs.js' => '2bfa2836',
'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8',
'rsrc/js/phuix/PHUIXActionView.js' => '8cf6d262',
'rsrc/js/phuix/PHUIXAutocomplete.js' => '5582787f',
'rsrc/js/phuix/PHUIXAutocomplete.js' => '0abdd4a8',
'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'bd4c8dca',
'rsrc/js/phuix/PHUIXFormControl.js' => '8fba1997',
'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b',
@ -709,9 +709,9 @@ return array(
'javelin-typeahead' => '70baed2f',
'javelin-typeahead-composite-source' => '503e17fd',
'javelin-typeahead-normalizer' => 'e6e25838',
'javelin-typeahead-ondemand-source' => '8b3fd187',
'javelin-typeahead-ondemand-source' => '013ffff9',
'javelin-typeahead-preloaded-source' => '54f314a0',
'javelin-typeahead-source' => '2818f5ce',
'javelin-typeahead-source' => '1bc11c4a',
'javelin-typeahead-static-source' => '6c0e62fa',
'javelin-uri' => 'c989ade3',
'javelin-util' => '93cc50d6',
@ -836,7 +836,7 @@ return array(
'phui-workpanel-view-css' => 'adec7699',
'phuix-action-list-view' => 'b5c256b8',
'phuix-action-view' => '8cf6d262',
'phuix-autocomplete' => '5582787f',
'phuix-autocomplete' => '0abdd4a8',
'phuix-dropdown-menu' => 'bd4c8dca',
'phuix-form-control-view' => '8fba1997',
'phuix-icon-view' => 'bff6884b',
@ -863,6 +863,12 @@ return array(
'unhandled-exception-css' => '4c96257a',
),
'requires' => array(
'013ffff9' => array(
'javelin-install',
'javelin-util',
'javelin-request',
'javelin-typeahead-source',
),
'01774ab2' => array(
'javelin-dom',
'javelin-util',
@ -911,6 +917,12 @@ return array(
'javelin-dom',
'javelin-router',
),
'0abdd4a8' => array(
'javelin-install',
'javelin-dom',
'phuix-icon-view',
'phabricator-prefab',
),
'0b7a4f6e' => array(
'javelin-behavior',
'javelin-typeahead-ondemand-source',
@ -950,6 +962,12 @@ return array(
'javelin-util',
'phabricator-keyboard-shortcut-manager',
),
'1bc11c4a' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-typeahead-normalizer',
),
'1d298e3a' => array(
'javelin-install',
'javelin-util',
@ -1009,12 +1027,6 @@ return array(
'phabricator-drag-and-drop-file-upload',
'phabricator-draggable-list',
),
'2818f5ce' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-typeahead-normalizer',
),
'2926fff2' => array(
'javelin-behavior',
'javelin-dom',
@ -1198,12 +1210,6 @@ return array(
'javelin-request',
'javelin-typeahead-source',
),
'5582787f' => array(
'javelin-install',
'javelin-dom',
'phuix-icon-view',
'phabricator-prefab',
),
'558829c2' => array(
'javelin-stratcom',
'javelin-behavior',
@ -1489,12 +1495,6 @@ return array(
'javelin-stratcom',
'javelin-vector',
),
'8b3fd187' => array(
'javelin-install',
'javelin-util',
'javelin-request',
'javelin-typeahead-source',
),
'8bdb2835' => array(
'phui-fontkit-css',
),

View file

@ -38,7 +38,7 @@ JX.install('TypeaheadOnDemandSource', {
lastChange : null,
haveData : null,
didChange : function(raw_value) {
didChange : function(raw_value, force) {
this.lastChange = JX.now();
var value = this.normalize(raw_value);
@ -59,10 +59,19 @@ JX.install('TypeaheadOnDemandSource', {
}
this.waitForResults();
setTimeout(
JX.bind(this, this.sendRequest, this.lastChange, value, raw_value),
this.getQueryDelay()
);
var send_request = JX.bind(
this,
this.sendRequest,
this.lastChange,
value,
raw_value);
if (force) {
send_request();
} else {
setTimeout(send_request, this.getQueryDelay());
}
}
},

View file

@ -289,7 +289,7 @@ JX.install('TypeaheadSource', {
this.filterAndSortHits(value, hits);
var nodes = this.renderNodes(value, hits);
this.invoke('resultsready', nodes, value);
this.invoke('resultsready', nodes, value, partial);
if (!partial) {
this.invoke('complete');
}

View file

@ -12,6 +12,7 @@ JX.install('PHUIXAutocomplete', {
this._map = {};
this._datasources = {};
this._listNodes = [];
this._resultMap = {};
},
members: {
@ -35,6 +36,7 @@ JX.install('PHUIXAutocomplete', {
_x: null,
_y: null,
_visible: false,
_resultMap: null,
setArea: function(area) {
this._area = area;
@ -205,7 +207,30 @@ JX.install('PHUIXAutocomplete', {
}
},
_onresults: function(code, nodes, value) {
_onresults: function(code, nodes, value, partial) {
// Even if these results are out of date, we still want to fill in the
// result map so we can terminate things later.
if (!partial) {
if (!this._resultMap[code]) {
this._resultMap[code] = {};
}
var hits = [];
for (var ii = 0; ii < nodes.length; ii++) {
var result = this._datasources[code].getResult(nodes[ii].rel);
if (!result) {
hits = null;
break;
}
hits.push(result.autocomplete);
}
if (hits !== null) {
this._resultMap[code][value] = hits;
}
}
if (code !== this._active) {
return;
}
@ -214,6 +239,13 @@ JX.install('PHUIXAutocomplete', {
return;
}
if (this._isTerminatedString(value)) {
if (this._hasUnrefinableResults(value)) {
this._deactivate();
return;
}
}
var list = this._getListNode();
JX.DOM.setContent(list, nodes);
@ -291,6 +323,59 @@ JX.install('PHUIXAutocomplete', {
return ['#', '@', ',', '!', '?'];
},
_getTerminators: function() {
return [' ', ':', ',', '.', '!', '?'];
},
_isTerminatedString: function(string) {
var terminators = this._getTerminators();
for (var ii = 0; ii < terminators.length; ii++) {
var term = terminators[ii];
if (string.substring(string.length - term.length) == term) {
return true;
}
}
return false;
},
_hasUnrefinableResults: function(query) {
if (!this._resultMap[this._active]) {
return false;
}
var map = this._resultMap[this._active];
for (var ii = 1; ii < query.length; ii++) {
var prefix = query.substring(0, ii);
if (map.hasOwnProperty(prefix)) {
var results = map[prefix];
// If any prefix of the query has no results, the full query also
// has no results so we can not refine them.
if (!results.length) {
return true;
}
// If there is exactly one match and the it is a prefix of the query,
// we can safely assume the user just typed out the right result
// from memory and doesn't need to refine it.
if (results.length == 1) {
// Strip the first character off, like a "#" or "@".
var result = results[0].substring(1);
if (query.length >= result.length) {
if (query.substring(0, result.length) === result) {
return true;
}
}
}
}
}
return false;
},
_trim: function(str) {
var suffixes = this._getSuffixes();
for (var ii = 0; ii < suffixes.length; ii++) {
@ -416,7 +501,32 @@ JX.install('PHUIXAutocomplete', {
}
}
this._datasource.didChange(trim);
// If the input is terminated by a space or another word-terminating
// punctuation mark, we're going to deactivate if the results can not
// be refined by addding more words.
// The idea is that if you type "@alan ab", you're allowed to keep
// editing "ab" until you type a space, period, or other terminator,
// since you might not be sure how to spell someone's last name or the
// second word of a project.
// Once you do terminate a word, if the words you have have entered match
// nothing or match only one exact match, we can safely deactivate and
// assume you're just typing text because further words could never
// refine the result set.
var force;
if (this._isTerminatedString(text)) {
if (this._hasUnrefinableResults(text)) {
this._deactivate();
return;
}
force = true;
} else {
force = false;
}
this._datasource.didChange(trim, force);
this._x = x;
this._y = y;