Tagger: Saka-key like keyboard navigation link tagging
This commit is contained in:
181
App/Resources/Tagger.js
Normal file
181
App/Resources/Tagger.js
Normal file
@@ -0,0 +1,181 @@
|
||||
|
||||
class ElementTagger {
|
||||
constructor() {
|
||||
this.isTagged = false;
|
||||
this.tagList = {};
|
||||
|
||||
const baseMnemonics = [
|
||||
'a', 's', 'd', /* 'f', */ 'g',
|
||||
'q', 'w', 'e', 'r', 't',
|
||||
'z', 'x', 'c', 'v', 'b',
|
||||
'y', 'u', 'i', 'o', 'p',
|
||||
'h', 'j', 'k', 'l',
|
||||
'n', 'm'
|
||||
];
|
||||
|
||||
let additionalMnemonics = [];
|
||||
baseMnemonics.forEach( (letter) => {
|
||||
additionalMnemonics.push("" + letter.toUpperCase() + letter);
|
||||
});
|
||||
|
||||
let mnemonics = baseMnemonics.concat(additionalMnemonics);
|
||||
|
||||
mnemonics.forEach( (letter) => {
|
||||
this.tagList[letter] = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
tagElement(elem) {
|
||||
if (!this.isBoundedByViewport(elem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let keys = Object.keys(this.tagList);
|
||||
for (let key_i in keys) {
|
||||
let key = keys[key_i];
|
||||
if (this.tagList[key] === undefined) {
|
||||
if ( this.addOverlay(elem, key) ) {
|
||||
this.tagList[key] = elem;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isBoundedByViewport(elem) {
|
||||
const viewport = window.visualViewport;
|
||||
const rect = elem.getClientRects()[0];
|
||||
|
||||
if (rect == undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
|
||||
const elemTop = (scrollTop + rect.top);
|
||||
if (elemTop > viewport.pageTop && elemTop < (viewport.pageTop + viewport.height)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
addOverlay(parentElem, mnemonic) {
|
||||
var rects = parentElem.getClientRects();
|
||||
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
|
||||
const scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
|
||||
|
||||
if (parentElem._overlayElem == undefined) {
|
||||
var rect = undefined;
|
||||
for (let i = 0; i < rects.length; i++) {
|
||||
if (rects[i] !== undefined) {
|
||||
rect = rects[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (rect === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let elem = document.createElement('div');
|
||||
elem.style.position = 'absolute';
|
||||
elem.style.border = '1px solid blue';
|
||||
elem.style.borderRadius = '5px';
|
||||
elem.style.background = 'rgba(0, 0, 255, 0.8)';
|
||||
elem.style.color = 'white';
|
||||
elem.style.font = '12px Helvetica bold';
|
||||
elem.style.padding = '2px';
|
||||
|
||||
|
||||
elem.innerText = "[" + mnemonic + "]";
|
||||
elem.style.top = (rect.top + scrollTop) + 'px';
|
||||
elem.style.left = (rect.left + scrollLeft) + 'px';
|
||||
document.body.appendChild(elem);
|
||||
|
||||
parentElem._overlayElem = elem;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
removeOverlay(elem) {
|
||||
let overlayElem = elem._overlayElem;
|
||||
if (overlayElem != undefined) {
|
||||
document.body.removeChild(overlayElem);
|
||||
elem._overlayElem = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
tagDocument() {
|
||||
var elt = document.getElementsByTagName("a");
|
||||
for (let i = 0; i < elt.length; i++) {
|
||||
this.tagElement(elt[i]);
|
||||
}
|
||||
|
||||
this.isTagged = true;
|
||||
}
|
||||
|
||||
untagDocument() {
|
||||
const keys = Object.keys(this.tagList);
|
||||
for (let key_i in keys) {
|
||||
let elem = this.tagList[keys[key_i]];
|
||||
if (elem !== undefined) {
|
||||
this.removeOverlay(elem);
|
||||
this.tagList[keys[key_i]] = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
this.isTagged = false;
|
||||
}
|
||||
|
||||
clickLinkWithTag(tag) {
|
||||
if (this.tagList[tag] !== undefined) {
|
||||
this.tagList[tag].click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(function() {
|
||||
let tagger = new ElementTagger();
|
||||
|
||||
var counter = 0;
|
||||
var tagAccum = [];
|
||||
document.addEventListener('keypress', (event) => {
|
||||
if (document.activeElement !== document.body) {
|
||||
return;
|
||||
}
|
||||
|
||||
const keyName = String.fromCharCode(event.charCode);
|
||||
if (keyName == undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyName == 'f') {
|
||||
if (++counter == 2) {
|
||||
counter = 0;
|
||||
if (tagger.isTagged) {
|
||||
tagger.untagDocument();
|
||||
} else {
|
||||
tagger.tagDocument();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Uppercase
|
||||
if (tagAccum.length > 0 || keyName.toUpperCase() == keyName) {
|
||||
tagAccum.push(keyName);
|
||||
}
|
||||
|
||||
if (tagAccum.length == 2) {
|
||||
tagger.clickLinkWithTag(tagAccum.join(''));
|
||||
tagger.untagDocument();
|
||||
tagAccum = [];
|
||||
} else if (tagAccum.length == 0) {
|
||||
tagger.clickLinkWithTag(keyName);
|
||||
tagger.untagDocument();
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user