keyhinter/keyhinter.js

161 lines
4.7 KiB
JavaScript

// Checks if the provided child is descendant of the parent (any level)
function is_descendant(parent, child) {
var node = child.parentNode;
while (node !== null) {
console.log(node)
if (node === parent) return true;
node = node.parentNode;
}
return false;
}
function isVisible(element) {
// Element is obviously not visible
if (element.offsetWidth === 0 || element.offsetHeight === 0) return false;
let rect = element.getClientRects()[0];
if (!rect) return false;
// Get the element clicked in the center of the element rect
var element_in_position = document.elementFromPoint((rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2);
// If there's no result, assume not visible
if (!element_in_position) return false;
// If the element clicked is the same as provided, the element is clearly visible
if (element_in_position === element) return true;
// If the element is not in the position we clicked, check if it's at least a child node
return is_descendant(element, element_in_position);
}
class KeyHinter {
constructor() {
this.number = 0;
this.nodes = {};
this.currentSelectionNumber = 0;
this.currentSelection = [];
this.mappings = {}
this.keyCodes = [
48, 49, 50, 51, 52, 53, 54, 55, 56, 57
]
}
// Highlight
highlight() {
if (this.number !== 0)
return false
this.listenKeybinds()
var _this = this;
document.querySelectorAll('a, form, input').forEach(function(nodeEl) {
if (isVisible(nodeEl)) {
_this.number++;
_this.nodes[_this.number] = nodeEl;
var el = _this.generateLabelFor(nodeEl);
el.textContent = _this.number;
el.setAttribute('data-keyhinter-number', _this.number);
document.body.appendChild(el);
}
})
}
// Label generation
generateLabelFor(node) {
var el = document.createElement('div');
var nodePosition = node.getBoundingClientRect();
el.classList.add('keyhinter-label');
el.style.left = nodePosition.left + 'px';
el.style.top = nodePosition.top + 'px';
if (node.nodeName === 'A') {
el.classList.add('keyhinter-label-anchor');
} else if (node.nodeName === 'FORM') {
el.classList.add('keyhinter-label-form');
} else if (node.nodeName === 'INPUT') {
el.classList.add('keyhinter-label-input');
}
return el;
}
doClick(obj) {
console.log(obj.nodeName)
switch(obj.nodeName) {
case 'INPUT':
obj.focus();
break;
case 'FORM':
obj.submit();
break;
default:
obj.click();
break;
}
}
// Keybinds
listenKeybinds() {
var _this = this;
document.onkeydown = function(event) {
if (event.keyCode === 27) { // ESC
event.preventDefault();
_this.destroy();
} else if (_this.keyCodes.indexOf(event.keyCode) !== -1) {
event.preventDefault();
var num = event.keyCode - 48;
_this.currentSelection.push(num);
document.querySelectorAll('[data-keyhinter-number]').forEach(function(item){
item.style.visibility = 'hidden';
})
var query = document.querySelectorAll('[data-keyhinter-number^="' + _this.currentSelection.join('') + '"]')
if (query.length === 1) {
var node = _this.nodes[_this.currentSelection.join('')];
_this.doClick(node);
_this.destroy();
} else {
query.forEach(function(item){
item.style.visibility = 'visible'
})
_this.currentSelectionNumber++;
}
} else {
console.log("Default:" + event.keyCode)
}
}
}
stopListeningKeybinds() {
document.onkeydown = null;
}
// Non-safe methods
destroy() {
document.querySelectorAll('.keyhinter-label').forEach(function(node){
document.body.removeChild(node);
})
this.stopListeningKeybinds();
keyHinter = new KeyHinter()
}
}
var keyHinter = new KeyHinter();
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
// If the received message has the expected format...
if (msg.action === 'start_keyhinter') {
// Call the specified callback, passing
// the web-page's DOM content as argument
keyHinter.highlight();
}
});