165 lines
4.9 KiB
JavaScript
165 lines
4.9 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;
|
|
}
|
|
|
|
Number.prototype.base26 = (function () {
|
|
return function base26() {
|
|
n = this/* w ww .j a v a 2s. co m*/
|
|
ret = "";
|
|
while (parseInt(n) > 0) {
|
|
--n;
|
|
ret += String.fromCharCode("A".charCodeAt(0) + (n % 26));
|
|
n /= 26;
|
|
}
|
|
return ret.split("").reverse().join("").toLowerCase();
|
|
};
|
|
}());
|
|
|
|
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.currentSelection = [];
|
|
}
|
|
|
|
// 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.base26()] = nodeEl;
|
|
|
|
var el = _this.generateLabelFor(nodeEl);
|
|
|
|
el.textContent = _this.number.base26();
|
|
el.setAttribute('data-keyhinter-label', _this.number.base26());
|
|
|
|
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) {
|
|
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.code === 'Escape') {
|
|
event.preventDefault();
|
|
_this.destroy();
|
|
} else if (event.code.startsWith("Key")) {
|
|
event.preventDefault();
|
|
var letter = event.key;
|
|
_this.currentSelection.push(letter);
|
|
|
|
document.querySelectorAll('[data-keyhinter-label]').forEach(function (item) {
|
|
item.style.visibility = 'hidden';
|
|
})
|
|
|
|
var query = document.querySelectorAll('[data-keyhinter-label^="' + _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'
|
|
})
|
|
}
|
|
} else {
|
|
console.log("Default:" + event.code + "," + event.key)
|
|
}
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
});
|