mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-18 12:52:42 +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:
parent
849b4c765a
commit
75b8d3312b
4 changed files with 152 additions and 33 deletions
|
@ -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',
|
||||
),
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue