For a mouseless future.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

onload.js 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. var conf = {
  2. scroll_speed: 0.4,
  3. scroll_speed_fast: 1.1,
  4. scroll_friction: 0.8,
  5. chars: "SANOTEHUCP",
  6. input_whitelist: ["checkbox", "radio", "hidden", "submit", "reset", "button", "file", "image"],
  7. location_change_check_timeout: 2000
  8. }
  9. var keys = {
  10. scroll_up: {code: "T"},
  11. scroll_down: {code: "N"},
  12. scroll_up_fast: {code: 219, shiftKey: true},
  13. scroll_down_fast: {code: 221, shiftKey: true},
  14. blobs_show: {code: "D"},
  15. blobs_show_reload: {code: "D", ctrlKey: true},
  16. blobs_hide: {code: 27},
  17. blobs_click: {code: 13},
  18. blobs_click_new_tab: {code: 13, shiftKey: true},
  19. elem_deselect: {code: 27},
  20. change_tab_left: {code: "H"},
  21. change_tab_right: {code: "S"},
  22. move_tab_left: {code: "H", shiftKey: true},
  23. move_tab_right: {code: "S", shiftKey: true},
  24. history_back: {code: "H", ctrlKey: true},
  25. history_forward: {code: "S", ctrlKey: true}
  26. }
  27. for (var i in keys) {
  28. if (typeof keys[i].code === "string") {
  29. console.log(keys[i].code.charCodeAt(0));
  30. keys[i].code = keys[i].code.charCodeAt(0);
  31. }
  32. }
  33. console.log(keys);
  34. function isMatch(k, evt) {
  35. if ((k.code === evt.keyCode)
  36. && (!!k.ctrlKey == evt.ctrlKey)
  37. && (!!k.shiftKey == evt.shiftKey)
  38. && (!!k.altKey == evt.altKey)
  39. && (!!k.metaKey == evt.metaKey)) {
  40. return true;
  41. }
  42. return false;
  43. }
  44. //There's a lot we don't want to do if we're not on an actual webpage, but on
  45. //the "speed dial"-ish pages.
  46. var onWebPage = (document.body !== undefined);
  47. function randomChar() {
  48. var index = Math.floor(Math.random() * conf.chars.length);
  49. return conf.chars[index];
  50. }
  51. function getElemPos(elem) {
  52. var curtop = 0;
  53. var curleft = 0;
  54. do {
  55. curtop += elem.offsetTop;
  56. curleft += elem.offsetLeft;
  57. } while (elem = elem.offsetParent);
  58. return {top: curtop, left: curleft};
  59. }
  60. var blobList = {
  61. blobs: {},
  62. container: null,
  63. visible: false,
  64. needLoadBlobs: true,
  65. currentKey: "",
  66. createContainer: function() {
  67. var container = document.createElement("div");
  68. container.style =
  69. "pointer-events: none;"+
  70. "display: none;"+
  71. "position: absolute;"+
  72. "top: 0px;"+
  73. "left: 0px;"+
  74. "z-index: 2147483647"; //Max z-index value in most browsers
  75. document.body.appendChild(container);
  76. blobList.container = container;
  77. },
  78. init: function() {
  79. if (!onWebPage)
  80. return;
  81. blobList.createContainer();
  82. window.addEventListener("scroll", function() {
  83. blobList.needLoadBlobs = true;
  84. });
  85. },
  86. currentIndex: 0,
  87. loadBlobs: function() {
  88. if (!onWebPage)
  89. return;
  90. var linkElems = document.querySelectorAll("a, button, input, textarea");
  91. //Remove old container contents
  92. blobList.container.innerHTML = ""
  93. //Remove old blobs
  94. blobList.blobs = {};
  95. var i = 0;
  96. function addBlob() {
  97. var linkElem = linkElems[i];
  98. i += 1;
  99. if (i > linkElems.length)
  100. return false;
  101. if (linkElem === undefined)
  102. return true;
  103. //We don't want hidden elements
  104. if ((linkElem === undefined)
  105. || (linkElem.style.display == "none")
  106. || (linkElem.style.visibility == "hidden")) {
  107. return true;
  108. }
  109. //Get element's absolute position
  110. var pos = getElemPos(linkElem);
  111. //Lots of things which don't really exist have an X and Y value of 0
  112. if (pos.top == 0 && pos.left == 0)
  113. return true;
  114. //We don't need to get things far above our current scroll position
  115. if (pos.top < (window.scrollY - 100))
  116. return true;
  117. //We don't need things below our scroll position either
  118. if (pos.top - 100 > (window.scrollY + window.innerHeight))
  119. return true;
  120. var key = randomChar();
  121. while (blobList.blobs[key])
  122. key += randomChar();
  123. var blobElem = document.createElement("div");
  124. blobElem.innerHTML = key;
  125. blobElem.style = [
  126. "position: absolute",
  127. "background-color: yellow",
  128. "border: 1px solid black",
  129. "border-radius: 10px",
  130. "padding-left: 3px",
  131. "padding-right: 3px",
  132. "color: black",
  133. "top: "+pos.top+"px",
  134. "left: "+pos.left+"px",
  135. "line-height: 12px",
  136. "font-size: 12px"
  137. ].join(" !important;");
  138. blobList.container.appendChild(blobElem);
  139. blobList.blobs[key] = {
  140. blobElem: blobElem,
  141. linkElem: linkElem
  142. }
  143. return true;
  144. }
  145. while (addBlob()) {};
  146. },
  147. showBlobs: function() {
  148. blobList.visible = true;
  149. blobList.container.style.display = "block";
  150. },
  151. hideBlobs: function() {
  152. blobList.currentKey = "";
  153. blobList.visible = false;
  154. blobList.container.style.display = "none";
  155. },
  156. click: function() {
  157. if (!blobList.visible)
  158. return;
  159. var blob = blobList.blobs[blobList.currentKey];
  160. if (!blob)
  161. return;
  162. blob.linkElem.click();
  163. blob.linkElem.focus();
  164. blobList.hideBlobs();
  165. },
  166. clickNewTab: function() {
  167. if (!blobList.visible)
  168. return;
  169. var blob = blobList.blobs[blobList.currentKey];
  170. if (!blob)
  171. return;
  172. if (blob.linkElem.tagName == "A" && blob.linkElem.href) {
  173. self.port.emit("tab_open", blob.linkElem.href);
  174. } else {
  175. blob.linkElem.click();
  176. blob.linkElem.focus();
  177. }
  178. blobList.hideBlobs();
  179. },
  180. appendKey: function(c) {
  181. blobList.currentKey += c;
  182. }
  183. }
  184. blobList.init();
  185. //Reload blobs whenever the URL changes
  186. var currentUrl = location.href;
  187. setInterval(function() {
  188. if (currentUrl !== location.href) {
  189. blobList.loadBlobs();
  190. }
  191. currentUrl = location.href;
  192. }, conf.location_change_check_timeout);
  193. var pressedKeys = [];
  194. function inArray(arr, val) {
  195. return (arr.indexOf(val) !== -1);
  196. }
  197. function isValidElem(elem) {
  198. var tag = elem.tagName.toLowerCase();
  199. if (tag === "textarea")
  200. return false;
  201. if (tag === "select")
  202. return false;
  203. if (elem.contentEditable.toLowerCase() === "true")
  204. return false;
  205. if ((tag === "input")
  206. && (!inArray(conf.input_whitelist, elem.type.toLowerCase()))) {
  207. return false;
  208. }
  209. return true;
  210. }
  211. window.addEventListener("keydown", function(evt) {
  212. if (/about:.+/.test(location.href))
  213. return;
  214. var active = document.activeElement;
  215. //We don't want to do anything if the user is tpying in an input field,
  216. //unless the key is to deselect an input field
  217. if (!isValidElem(active)) {
  218. if (isMatch(keys.elem_deselect, evt)) {
  219. active.blur();
  220. blobList.hideBlobs();
  221. return;
  222. } else {
  223. return;
  224. }
  225. }
  226. //User is typing a key to a blob
  227. var c = String.fromCharCode(evt.keyCode);
  228. if (blobList.visible && conf.chars.indexOf(c) !== -1) {
  229. //Hide blobs if appropriate
  230. if (isMatch(keys.blobs_hide, evt)) {
  231. blobList.hideBlobs();
  232. return;
  233. }
  234. blobList.appendKey(c);
  235. evt.preventDefault();
  236. evt.stopPropagation();
  237. return false;
  238. }
  239. //Handle other key presses
  240. //Deselect element
  241. if (onWebPage && isMatch(keys.elem_deselect, evt)) {
  242. blobList.hideBlobs();
  243. active.blur();
  244. //Show/hide/reload blobs
  245. } else if (onWebPage && !blobList.visible && isMatch(keys.blobs_show, evt)) {
  246. if (blobList.needLoadBlobs)
  247. blobList.loadBlobs();
  248. blobList.needLoadBlobs = false;
  249. blobList.showBlobs();
  250. } else if (onWebPage && !blobList.visible && isMatch(keys.blobs_show_reload, evt)) {
  251. blobList.loadBlobs();
  252. blobList.needLoadBlobs = false;
  253. blobList.showBlobs();
  254. } else if (onWebPage && blobList.visible && isMatch(keys.blobs_hide, evt)) {
  255. blobList.hideBlobs();
  256. //Simulate clicks
  257. } else if (onWebPage && blobList.visible && isMatch(keys.blobs_click, evt)) {
  258. blobList.click();
  259. } else if (onWebPage && blobList.visible && isMatch(keys.blobs_click_new_tab, evt)) {
  260. blobList.clickNewTab();
  261. //Scrolling
  262. } else if (onWebPage && isMatch(keys.scroll_up, evt)) {
  263. scroll.start(-conf.scroll_speed);
  264. } else if (onWebPage && isMatch(keys.scroll_down, evt)) {
  265. scroll.start(conf.scroll_speed);
  266. } else if (onWebPage && isMatch(keys.scroll_up_fast, evt)) {
  267. scroll.start(-conf.scroll_speed_fast);
  268. } else if (onWebPage && isMatch(keys.scroll_down_fast, evt)) {
  269. scroll.start(conf.scroll_speed_fast);
  270. //Back and forwards
  271. } else if (isMatch(keys.history_back, evt)) {
  272. history.back();
  273. } else if (isMatch(keys.history_forward, evt)) {
  274. history.forward();
  275. //Change tab
  276. } else if (isMatch(keys.change_tab_left, evt)) {
  277. self.port.emit("change_tab_left");
  278. } else if (isMatch(keys.change_tab_right, evt)) {
  279. self.port.emit("change_tab_right");
  280. //Move tab
  281. } else if (isMatch(keys.move_tab_left, evt)) {
  282. self.port.emit("move_tab_left");
  283. } else if (isMatch(keys.move_tab_right, evt)) {
  284. self.port.emit("move_tab_right");
  285. //We don't want to stop the event from propagating
  286. //if it hasn't matched anything yet
  287. } else {
  288. return true;
  289. }
  290. evt.preventDefault();
  291. evt.stopPropagation();
  292. return false;
  293. }, true);
  294. window.addEventListener("keyup", function(evt) {
  295. if ((isMatch(keys.scroll_up, evt))
  296. || (isMatch(keys.scroll_down, evt))
  297. || (isMatch(keys.scroll_up_fast, evt))
  298. || (isMatch(keys.scroll_down_fast, evt))) {
  299. scroll.stop();
  300. }
  301. }, true);
  302. var scroll = {
  303. start: function(acceleration) {
  304. scroll.acceleration = acceleration;
  305. if (scroll.raf == null)
  306. scroll.update();
  307. },
  308. stop: function() {
  309. scroll.acceleration = 0;
  310. },
  311. update: function() {
  312. var tdiff = scroll.endTime - scroll.startTime;
  313. if (tdiff < 100) {
  314. scroll.velocity += scroll.acceleration;
  315. window.scrollBy(0, scroll.velocity * tdiff);
  316. scroll.velocity *= conf.scroll_friction;
  317. }
  318. if (tdiff < 100 && scroll.velocity < -0.1 && scroll.velocity > 0.1) {
  319. scroll.velocity = 0;
  320. cancelAnimationFrame(scroll.raf);
  321. scroll.raf = null;
  322. } else {
  323. scroll.startTime = scroll.endTime;
  324. scroll.endTime = new Date().getTime();
  325. scroll.raf = requestAnimationFrame(scroll.update);
  326. }
  327. },
  328. raf: null,
  329. acceleration: 0,
  330. velocity: 0,
  331. startDate: 0,
  332. endDate: 0
  333. }