BUGFIX: Handle text input in keypress handler for I18n

While the `keydown` handler is the only way of reliably catching special keys,
for textual input `keypress` is preferable, as it can handle thing like parens
without having to hardcode a keyboard.

I moved that code and in the process converted some magic numbers to constants
- mostly so I can read stuff better.
This commit is contained in:
Martin Meyerhoff 2014-10-01 15:46:13 +02:00
parent 7a0b1c1dff
commit 3fd8fc97a2
1 changed files with 86 additions and 112 deletions

View File

@ -6,44 +6,26 @@
export var CANCELLED_STATUS = "__CANCELLED"; export var CANCELLED_STATUS = "__CANCELLED";
var shiftMap = []; var Keys = {
shiftMap[192] = "~"; BackSpace: 8,
shiftMap[49] = "!"; Tab: 9,
shiftMap[50] = "@"; Enter: 13,
shiftMap[51] = "#"; Shift: 16,
shiftMap[52] = "$"; Ctrl: 17,
shiftMap[53] = "%"; Alt: 18,
shiftMap[54] = "^"; Esc: 27,
shiftMap[55] = "&"; Space: 32,
shiftMap[56] = "*"; LeftWindows: 91,
shiftMap[57] = "("; RightWindows: 92,
shiftMap[48] = ")"; PageUp: 33,
shiftMap[109] = "_"; PageDown: 34,
shiftMap[107] = "+"; End: 35,
shiftMap[219] = "{"; Home: 36,
shiftMap[221] = "}"; LeftArrow: 37,
shiftMap[220] = "|"; UpArrow: 38,
shiftMap[59] = ":"; RightArrow: 39,
shiftMap[222] = "\""; DownArrow: 40,
shiftMap[188] = "<"; };
shiftMap[190] = ">";
shiftMap[191] = "?";
shiftMap[32] = " ";
function mapKeyPressToActualCharacter(isShiftKey, characterCode) {
if ( characterCode === 27 || characterCode === 8 || characterCode === 9 || characterCode === 20 || characterCode === 16 || characterCode === 17 || characterCode === 91 || characterCode === 13 || characterCode === 92 || characterCode === 18 ) { return false; }
// Lookup non-letter keypress while holding shift
if (isShiftKey && ( characterCode < 65 || characterCode > 90 )) {
return shiftMap[characterCode];
}
var stringValue = String.fromCharCode(characterCode);
if ( !isShiftKey ) {
stringValue = stringValue.toLowerCase();
}
return stringValue;
}
export default function(options) { export default function(options) {
var autocompletePlugin = this; var autocompletePlugin = this;
@ -78,9 +60,14 @@ export default function(options) {
} }
div = null; div = null;
completeStart = null; completeStart = null;
completeEnd = null;
autocompleteOptions = null; autocompleteOptions = null;
}; };
var autoCompleting = function () {
return completeStart !== null;
};
var addInputSelectedItem = function(item) { var addInputSelectedItem = function(item) {
var transformed, var transformed,
transformedItem = item; transformedItem = item;
@ -131,7 +118,7 @@ export default function(options) {
term = options.transformComplete(term); term = options.transformComplete(term);
} }
var text = me.val(); var text = me.val();
text = text.substring(0, completeStart) + (options.key || "") + term + ' ' + text.substring(completeEnd + 1, text.length); text = text.substring(0, completeStart) + term + ' ' + text.substring(completeEnd, text.length);
me.val(text); me.val(text);
Discourse.Utilities.setCaretPosition(me[0], completeStart + 1 + term.length); Discourse.Utilities.setCaretPosition(me[0], completeStart + 1 + term.length);
} }
@ -264,24 +251,37 @@ export default function(options) {
closeAutocomplete(); closeAutocomplete();
}); });
var getTerm = function() {
return me.val().slice(completeStart, completeEnd);
};
$(this).keypress(function(e) { $(this).keypress(function(e) {
var term, key = (e.char || String.fromCharCode(e.charCode));
if (!options.key) return; // if we just started with an options.key, set start and end.
if (key === options.key && !autoCompleting()) {
completeStart = completeEnd = Discourse.Utilities.caretPosition(me[0]) + 1;
}
// keep hunting backwards till you hit a if (!options.key) {
if (e.which === options.key.charCodeAt(0)) { completeStart = 0;
var caretPosition = Discourse.Utilities.caretPosition(me[0]); completeEnd = Discourse.Utilities.caretPosition(me[0]);
var prevChar = me.val().charAt(caretPosition - 1); }
if (!prevChar || /\s/.test(prevChar)) {
completeStart = completeEnd = caretPosition; if (autoCompleting()) {
if ((completeStart === completeEnd) && key === options.key) {
updateAutoComplete(options.dataSource("")); updateAutoComplete(options.dataSource(""));
} else {
term = getTerm() + key;
completeEnd += 1;
updateAutoComplete(options.dataSource(term));
} }
return true;
} }
}); });
$(this).keydown(function(e) { $(this).keydown(function(e) {
var c, caretPosition, i, initial, next, prev, prevIsGood, stopFound, term, total, userToComplete; var caretPosition, i, term, total, userToComplete;
if(options.allowAny){ if(options.allowAny){
// saves us wiring up a change event as well, keypress is while its pressed // saves us wiring up a change event as well, keypress is while its pressed
@ -301,57 +301,38 @@ export default function(options) {
},50); },50);
} }
if (!options.key) { // Handle Backspacing into stuff
completeStart = 0; if ((!autoCompleting()) && e.which === Keys.BackSpace && options.key) {
} var c = Discourse.Utilities.caretPosition(me[0]),
if (e.which === 16) return; last, first,
if ((completeStart === null) && e.which === 8 && options.key) { text = me[0].value;
c = Discourse.Utilities.caretPosition(me[0]); // search backwards until you find the last letter of the word
next = me[0].value[c]; while (/[\s]/.test(text[c]) && c >= 0) { c--; }
c -= 1; last = c;
initial = c; // search further until you find the first letter of the word
prevIsGood = true; while (/[\S]/.test(text[c]) && c >= 0) { c--; }
while (prevIsGood && c >= 0) { first = c + 1;
c -= 1;
prev = me[0].value[c]; if (text[first] === options.key) {
stopFound = prev === options.key; completeStart = first + 1;
if (stopFound) { completeEnd = (options.key === ":" ? last - 1 : last);
prev = me[0].value[c - 1];
if (!prev || /\s/.test(prev)) { if (completeEnd >= completeStart) {
completeStart = c; updateAutoComplete(options.dataSource(getTerm()));
caretPosition = completeEnd = initial;
term = me[0].value.substring(c + 1, initial);
updateAutoComplete(options.dataSource(term));
return true;
}
} }
prevIsGood = /[a-zA-Z\.]/.test(prev); return true;
} }
} }
// ESC if (autoCompleting()) {
if (e.which === 27) {
if (completeStart !== null) {
closeAutocomplete();
return false;
}
return true;
}
if (completeStart !== null) {
caretPosition = Discourse.Utilities.caretPosition(me[0]);
// If we've backspaced past the beginning, cancel unless no key
if (caretPosition <= completeStart && options.key) {
closeAutocomplete();
return false;
}
// Keyboard codes! So 80's. // Keyboard codes! So 80's.
switch (e.which) { switch (e.which) {
case 13: case Keys.Esc:
case 39: closeAutocomplete();
case 9: return false;
case Keys.Enter:
case Keys.RightArrow:
case Keys.Tab:
if (!autocompleteOptions) return true; if (!autocompleteOptions) return true;
if (selectedOption >= 0 && (userToComplete = autocompleteOptions[selectedOption])) { if (selectedOption >= 0 && (userToComplete = autocompleteOptions[selectedOption])) {
completeTerm(userToComplete); completeTerm(userToComplete);
@ -361,14 +342,14 @@ export default function(options) {
} }
e.stopImmediatePropagation(); e.stopImmediatePropagation();
return false; return false;
case 38: case Keys.UpArrow:
selectedOption = selectedOption - 1; selectedOption = selectedOption - 1;
if (selectedOption < 0) { if (selectedOption < 0) {
selectedOption = 0; selectedOption = 0;
} }
markSelected(); markSelected();
return false; return false;
case 40: case Keys.DownArrow:
total = autocompleteOptions.length; total = autocompleteOptions.length;
selectedOption = selectedOption + 1; selectedOption = selectedOption + 1;
if (selectedOption >= total) { if (selectedOption >= total) {
@ -379,12 +360,11 @@ export default function(options) {
} }
markSelected(); markSelected();
return false; return false;
default: case Keys.BackSpace:
// otherwise they're typing - let's search for it! caretPosition = Discourse.Utilities.caretPosition(me[0]) - 1;
completeEnd = caretPosition; completeEnd = caretPosition;
if (e.which === 8) {
caretPosition--;
}
if (caretPosition < 0) { if (caretPosition < 0) {
closeAutocomplete(); closeAutocomplete();
if (isInput) { if (isInput) {
@ -395,23 +375,17 @@ export default function(options) {
} }
return false; return false;
} }
term = me.val().substring(completeStart + (options.key ? 1 : 0), caretPosition);
if (e.which >= 48 && e.which <= 90) { if (completeEnd < completeStart) {
term += mapKeyPressToActualCharacter(e.shiftKey, e.which); closeAutocomplete();
} else if (e.which === 187) { return true;
term += "+";
} else if (e.which === 189) {
term += (e.shiftKey) ? "_" : "-";
} else if (e.which === 220) {
term += (e.shiftKey) ? "|" : "]";
} else if (e.which === 222) {
term += (e.shiftKey) ? "\"" : "'";
} else if (e.which !== 8) {
term += ",";
} }
term = getTerm();
updateAutoComplete(options.dataSource(term)); updateAutoComplete(options.dataSource(term));
return true; return true;
default:
return true;
} }
} }
}); });