Merge pull request #512 from keepassxreboot/fix/autosubmit

Fix auto-submit from keyboard shortcut
This commit is contained in:
Sami Vänttinen 2019-05-08 15:21:40 +03:00 committed by GitHub
commit 35e356e70c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 193 additions and 176 deletions

View file

@ -1,10 +1,16 @@
'use strict';
var kpxcAutocomplete = {};
kpxcAutocomplete.autoSubmit = false;
kpxcAutocomplete.elements = [];
kpxcAutocomplete.started = false;
kpxcAutocomplete.index = -1;
kpxcAutocomplete.input = undefined;
kpxcAutocomplete.create = function(input, showListInstantly = false, autoSubmit = false) {
let _index = -1;
kpxcAutocomplete.autoSubmit = autoSubmit;
kpxcAutocomplete.input = input;
kpxcAutocomplete.started = true
input.addEventListener('click', function(e) {
if (!e.isTrusted) {
@ -14,193 +20,193 @@ kpxcAutocomplete.create = function(input, showListInstantly = false, autoSubmit
if (input.value !== '') {
input.select();
}
showList(input);
kpxcAutocomplete.showList(input);
});
input.addEventListener('keydown', keyPress);
input.addEventListener('keydown', kpxcAutocomplete.keyPress);
input.setAttribute('autocomplete', 'off');
if (showListInstantly) {
showList(input);
kpxcAutocomplete.showList(input);
}
};
function showList(inputField) {
closeList();
const div = kpxcUI.createElement('div', 'kpxcAutocomplete-items', { 'id': 'kpxcAutocomplete-list' });
kpxcAutocomplete.showList = function(inputField) {
kpxcAutocomplete.closeList();
const div = kpxcUI.createElement('div', 'kpxcAutocomplete-items', { 'id': 'kpxcAutocomplete-list' });
// Element position
const rect = inputField.getBoundingClientRect();
div.style.top = String((rect.top + document.body.scrollTop) + input.offsetHeight) + 'px';
div.style.left = String((rect.left + document.body.scrollLeft)) + 'px';
div.style.minWidth = String(input.offsetWidth) + 'px';
div.style.zIndex = '2147483646';
document.body.append(div);
// Element position
const rect = inputField.getBoundingClientRect();
div.style.top = String((rect.top + document.body.scrollTop) + inputField.offsetHeight) + 'px';
div.style.left = String((rect.left + document.body.scrollLeft)) + 'px';
div.style.minWidth = String(inputField.offsetWidth) + 'px';
div.style.zIndex = '2147483646';
document.body.append(div);
for (const c of kpxcAutocomplete.elements) {
const item = document.createElement('div');
item.textContent += c.label;
const itemInput = kpxcUI.createElement('input', '', { 'type': 'hidden', 'value': c.value });
item.append(itemInput);
item.addEventListener('click', function(e) {
if (!e.isTrusted) {
return;
}
// Save index for combination.loginId
const index = Array.prototype.indexOf.call(e.currentTarget.parentElement.childNodes, e.currentTarget);
inputField.value = this.getElementsByTagName('input')[0].value;
fillPassword(inputField.value, index);
closeList();
inputField.focus();
});
// These events prevent the double hover effect if both keyboard and mouse are used
item.addEventListener('mouseover', function(e) {
removeItem(getAllItems());
item.classList.add('kpxcAutocomplete-active');
_index = Array.from(div.childNodes).indexOf(item);
});
item.addEventListener('mouseout', function(e) {
item.classList.remove('kpxcAutocomplete-active');
});
div.appendChild(item);
}
// Add a footer message for auto-submit
if (autoSubmit) {
const footer = kpxcUI.createElement('footer', '', {}, tr('autocompleteSubmitMessage'));
div.appendChild(footer);
}
// Activate the first item automatically
const items = getAllItems();
_index = 0;
activateItem(items);
}
function activateItem(item) {
if (!item || item.length === 0) {
return;
}
removeItem(item);
if (_index >= item.length) {
_index = 0;
}
if (_index < 0) {
_index = item.length - 1;
}
if (item[_index] !== undefined) {
item[_index].classList.add('kpxcAutocomplete-active');
}
}
function removeItem(items) {
for (const item of items) {
item.classList.remove('kpxcAutocomplete-active');
}
}
function closeList(elem) {
const items = document.getElementsByClassName('kpxcAutocomplete-items');
for (const item of items) {
if (elem !== item && input) {
item.parentNode.removeChild(item);
}
}
}
function getAllItems() {
const list = document.getElementById('kpxcAutocomplete-list');
if (!list) {
return [];
}
return list.getElementsByTagName('div');
}
/**
* Keyboard shortcuts for autocomplete menu:
* - ArrowDown shows the list or selects item below, or the first item (last is active)
* - ArrowUp selects item above, or the last item (first is active)
* - Enter or Tab selects the item
* - Backspace and Delete shows the list if input field is empty. First item is activated
*/
function keyPress(e) {
if (!e.isTrusted) {
return;
}
const items = getAllItems();
if (e.key === 'ArrowDown') {
// If the list is not visible, show it
if (items.length === 0) {
_index = -1;
showList(input);
} else {
// Activate next item
++_index;
activateItem(items);
}
} else if (e.key === 'ArrowUp') {
--_index;
activateItem(items);
} else if (e.key === 'Enter') {
if (input.value === '' && e.key === 'Enter') {
e.preventDefault();
}
if (_index >= 0 && items && items[_index] !== undefined) {
e.preventDefault();
input.value = e.currentTarget.value;
fillPassword(input.value, _index);
closeList();
}
} else if (e.key === 'Tab') {
// Return if value is not in the list
if (input.value !== '' && kpxcAutocomplete.elements.some(c => c.value !== input.value)) {
closeList();
for (const c of kpxcAutocomplete.elements) {
const item = document.createElement('div');
item.textContent += c.label;
const itemInput = kpxcUI.createElement('input', '', { 'type': 'hidden', 'value': c.value });
item.append(itemInput);
item.addEventListener('click', function(e) {
if (!e.isTrusted) {
return;
}
_index = kpxcAutocomplete.elements.findIndex(c => c.value === input.value);
fillPassword(input.value, _index);
closeList();
} else if (e.key === 'Escape') {
closeList();
} else if ((e.key === 'Backspace' || e.key === 'Delete') && input.value === '') {
// Show menu when input field has no value and backspace is pressed
_index = -1;
showList(input);
}
// Save index for combination.loginId
const index = Array.prototype.indexOf.call(e.currentTarget.parentElement.childNodes, e.currentTarget);
inputField.value = this.getElementsByTagName('input')[0].value;
kpxcAutocomplete.fillPassword(inputField.value, index);
kpxcAutocomplete.closeList();
inputField.focus();
});
// These events prevent the double hover effect if both keyboard and mouse are used
item.addEventListener('mouseover', function(e) {
kpxcAutocomplete.removeItem(kpxcAutocomplete.getAllItems());
item.classList.add('kpxcAutocomplete-active');
kpxcAutocomplete.index = Array.from(div.childNodes).indexOf(item);
});
item.addEventListener('mouseout', function(e) {
item.classList.remove('kpxcAutocomplete-active');
});
div.appendChild(item);
}
function fillPassword(value, index) {
const fieldId = input.getAttribute('data-kpxc-id');
kpxcFields.prepareId(fieldId);
const givenType = input.type === 'password' ? 'password' : 'username';
const combination = kpxcFields.getCombination(givenType, fieldId);
combination.loginId = index;
kpxc.fillInCredentials(combination, false, false);
input.setAttribute('fetched', true);
// Add a footer message for auto-submit
if (kpxcAutocomplete.autoSubmit) {
const footer = kpxcUI.createElement('footer', '', {}, tr('autocompleteSubmitMessage'));
div.appendChild(footer);
}
// Detect click outside autocomplete
document.addEventListener('click', function(e) {
if (!e.isTrusted) {
return;
}
const list = document.getElementById('kpxcAutocomplete-list');
if (!list) {
return;
}
if (e.target !== input && (e.target.nodeName !== input.nodeName)) {
closeList(e.target);
}
});
// Activate the first item automatically
const items = kpxcAutocomplete.getAllItems();
kpxcAutocomplete.index = 0;
kpxcAutocomplete.activateItem(items);
};
kpxcAutocomplete.activateItem = function(item) {
if (!item || item.length === 0) {
return;
}
kpxcAutocomplete.removeItem(item);
if (kpxcAutocomplete.index >= item.length) {
kpxcAutocomplete.index = 0;
}
if (kpxcAutocomplete.index < 0) {
kpxcAutocomplete.index = item.length - 1;
}
if (item[kpxcAutocomplete.index] !== undefined) {
item[kpxcAutocomplete.index].classList.add('kpxcAutocomplete-active');
}
};
kpxcAutocomplete.removeItem = function(items) {
for (const item of items) {
item.classList.remove('kpxcAutocomplete-active');
}
};
kpxcAutocomplete.closeList = function(elem) {
const items = document.getElementsByClassName('kpxcAutocomplete-items');
for (const item of items) {
if (elem !== item && kpxcAutocomplete.input) {
item.parentNode.removeChild(item);
}
}
};
kpxcAutocomplete.getAllItems = function() {
const list = document.getElementById('kpxcAutocomplete-list');
if (!list) {
return [];
}
return list.getElementsByTagName('div');
};
/**
* Keyboard shortcuts for autocomplete menu:
* - ArrowDown shows the list or selects item below, or the first item (last is active)
* - ArrowUp selects item above, or the last item (first is active)
* - Enter or Tab selects the item
* - Backspace and Delete shows the list if input field is empty. First item is activated
*/
kpxcAutocomplete.keyPress = function(e) {
if (!e.isTrusted) {
return;
}
const items = kpxcAutocomplete.getAllItems();
if (e.key === 'ArrowDown') {
// If the list is not visible, show it
if (items.length === 0) {
kpxcAutocomplete.index = -1;
kpxcAutocomplete.showList(kpxcAutocomplete.input);
} else {
// Activate next item
++kpxcAutocomplete.index;
kpxcAutocomplete.activateItem(items);
}
} else if (e.key === 'ArrowUp') {
--kpxcAutocomplete.index;
kpxcAutocomplete.activateItem(items);
} else if (e.key === 'Enter') {
if (kpxcAutocomplete.input.value === '') {
e.preventDefault();
}
if (kpxcAutocomplete.index >= 0 && items && items[kpxcAutocomplete.index] !== undefined) {
e.preventDefault();
kpxcAutocomplete.input.value = e.currentTarget.value;
kpxcAutocomplete.fillPassword(kpxcAutocomplete.input.value, kpxcAutocomplete.index);
kpxcAutocomplete.closeList();
}
} else if (e.key === 'Tab') {
// Return if value is not in the list
if (kpxcAutocomplete.input.value !== '' && kpxcAutocomplete.elements.some(c => c.value !== kpxcAutocomplete.input.value)) {
kpxcAutocomplete.closeList();
return;
}
kpxcAutocomplete.index = kpxcAutocomplete.elements.findIndex(c => c.value === kpxcAutocomplete.input.value);
kpxcAutocomplete.fillPassword(kpxcAutocomplete.input.value, kpxcAutocomplete.index);
kpxcAutocomplete.closeList();
} else if (e.key === 'Escape') {
kpxcAutocomplete.closeList();
} else if ((e.key === 'Backspace' || e.key === 'Delete') && kpxcAutocomplete.input.value === '') {
// Show menu when input field has no value and backspace is pressed
kpxcAutocomplete.index = -1;
kpxcAutocomplete.showList(kpxcAutocomplete.input);
}
};
kpxcAutocomplete.fillPassword = function(value, index) {
const fieldId = kpxcAutocomplete.input.getAttribute('data-kpxc-id');
kpxcFields.prepareId(fieldId);
const givenType = kpxcAutocomplete.input.type === 'password' ? 'password' : 'username';
const combination = kpxcFields.getCombination(givenType, fieldId);
combination.loginId = index;
kpxc.fillInCredentials(combination, false, false);
kpxcAutocomplete.input.setAttribute('fetched', true);
};
// Detect click outside autocomplete
document.addEventListener('click', function(e) {
if (!e.isTrusted) {
return;
}
const list = document.getElementById('kpxcAutocomplete-list');
if (!list) {
return;
}
if (e.target !== kpxcAutocomplete.input && (e.target.nodeName !== kpxcAutocomplete.input.nodeName)) {
kpxcAutocomplete.closeList(e.target);
}
});

View file

@ -1307,9 +1307,14 @@ kpxc.fillIn = function(combination, onlyPassword, suppressWarnings) {
if (countPasswords > 1) {
if (!suppressWarnings) {
const target = onlyPassword ? pField : uField;
kpxcAutocomplete.create(target, true, kpxc.settings.autoSubmit);
if (kpxcAutocomplete.started) {
kpxcAutocomplete.showList(target);
} else {
kpxcAutocomplete.create(target, true, kpxc.settings.autoSubmit);
}
target.focus();
}
return;
} else if (countPasswords < 1) {
if (!suppressWarnings) {
const message = tr('credentialsNoUsernameFound');
@ -1318,12 +1323,18 @@ kpxc.fillIn = function(combination, onlyPassword, suppressWarnings) {
args: [ message ]
});
}
return;
}
} else {
if (!suppressWarnings) {
const target = onlyPassword ? pField : uField;
kpxcAutocomplete.create(target, true, kpxc.settings.autoSubmit);
if (kpxcAutocomplete.started) {
kpxcAutocomplete.showList(target);
} else {
kpxcAutocomplete.create(target, true, kpxc.settings.autoSubmit);
}
target.focus();
return;
}
}
}