logo

searx

My custom branche(s) on searx, a meta-search engine git clone https://hacktivis.me/git/searx.git

searx_keyboard.js (9300B)


  1. searx.ready(function() {
  2. searx.on('.result', 'click', function() {
  3. highlightResult(this)(true);
  4. });
  5. searx.on('.result a', 'focus', function(e) {
  6. var el = e.target;
  7. while (el !== undefined) {
  8. if (el.classList.contains('result')) {
  9. if (el.getAttribute("data-vim-selected") === null) {
  10. highlightResult(el)(true);
  11. }
  12. break;
  13. }
  14. el = el.parentNode;
  15. }
  16. }, true);
  17. var vimKeys = {
  18. 27: {
  19. key: 'Escape',
  20. fun: removeFocus,
  21. des: 'remove focus from the focused input',
  22. cat: 'Control'
  23. },
  24. 73: {
  25. key: 'i',
  26. fun: searchInputFocus,
  27. des: 'focus on the search input',
  28. cat: 'Control'
  29. },
  30. 66: {
  31. key: 'b',
  32. fun: scrollPage(-window.innerHeight),
  33. des: 'scroll one page up',
  34. cat: 'Navigation'
  35. },
  36. 70: {
  37. key: 'f',
  38. fun: scrollPage(window.innerHeight),
  39. des: 'scroll one page down',
  40. cat: 'Navigation'
  41. },
  42. 85: {
  43. key: 'u',
  44. fun: scrollPage(-window.innerHeight / 2),
  45. des: 'scroll half a page up',
  46. cat: 'Navigation'
  47. },
  48. 68: {
  49. key: 'd',
  50. fun: scrollPage(window.innerHeight / 2),
  51. des: 'scroll half a page down',
  52. cat: 'Navigation'
  53. },
  54. 71: {
  55. key: 'g',
  56. fun: scrollPageTo(-document.body.scrollHeight, 'top'),
  57. des: 'scroll to the top of the page',
  58. cat: 'Navigation'
  59. },
  60. 86: {
  61. key: 'v',
  62. fun: scrollPageTo(document.body.scrollHeight, 'bottom'),
  63. des: 'scroll to the bottom of the page',
  64. cat: 'Navigation'
  65. },
  66. 75: {
  67. key: 'k',
  68. fun: highlightResult('up'),
  69. des: 'select previous search result',
  70. cat: 'Results'
  71. },
  72. 74: {
  73. key: 'j',
  74. fun: highlightResult('down'),
  75. des: 'select next search result',
  76. cat: 'Results'
  77. },
  78. 80: {
  79. key: 'p',
  80. fun: pageButtonClick(0),
  81. des: 'go to previous page',
  82. cat: 'Results'
  83. },
  84. 78: {
  85. key: 'n',
  86. fun: pageButtonClick(1),
  87. des: 'go to next page',
  88. cat: 'Results'
  89. },
  90. 79: {
  91. key: 'o',
  92. fun: openResult(false),
  93. des: 'open search result',
  94. cat: 'Results'
  95. },
  96. 84: {
  97. key: 't',
  98. fun: openResult(true),
  99. des: 'open the result in a new tab',
  100. cat: 'Results'
  101. },
  102. 82: {
  103. key: 'r',
  104. fun: reloadPage,
  105. des: 'reload page from the server',
  106. cat: 'Control'
  107. },
  108. 72: {
  109. key: 'h',
  110. fun: toggleHelp,
  111. des: 'toggle help window',
  112. cat: 'Other'
  113. }
  114. };
  115. searx.on(document, "keydown", function(e) {
  116. // check for modifiers so we don't break browser's hotkeys
  117. if (vimKeys.hasOwnProperty(e.keyCode) && !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey) {
  118. var tagName = e.target.tagName.toLowerCase();
  119. if (e.keyCode === 27) {
  120. if (tagName === 'input' || tagName === 'select' || tagName === 'textarea') {
  121. vimKeys[e.keyCode].fun();
  122. }
  123. } else {
  124. if (e.target === document.body || tagName === 'a' || tagName === 'button') {
  125. e.preventDefault();
  126. vimKeys[e.keyCode].fun();
  127. }
  128. }
  129. }
  130. });
  131. function highlightResult(which) {
  132. return function(noScroll) {
  133. var current = document.querySelector('.result[data-vim-selected]'),
  134. effectiveWhich = which;
  135. if (current === null) {
  136. // no selection : choose the first one
  137. current = document.querySelector('.result');
  138. if (current === null) {
  139. // no first one : there are no results
  140. return;
  141. }
  142. // replace up/down actions by selecting first one
  143. if (which === "down" || which === "up") {
  144. effectiveWhich = current;
  145. }
  146. }
  147. var next, results = document.querySelectorAll('.result');
  148. if (typeof effectiveWhich !== 'string') {
  149. next = effectiveWhich;
  150. } else {
  151. switch (effectiveWhich) {
  152. case 'visible':
  153. var top = document.documentElement.scrollTop || document.body.scrollTop;
  154. var bot = top + document.documentElement.clientHeight;
  155. for (var i = 0; i < results.length; i++) {
  156. next = results[i];
  157. var etop = next.offsetTop;
  158. var ebot = etop + next.clientHeight;
  159. if ((ebot <= bot) && (etop > top)) {
  160. break;
  161. }
  162. }
  163. break;
  164. case 'down':
  165. next = current.nextElementSibling;
  166. if (next === null) {
  167. next = results[0];
  168. }
  169. break;
  170. case 'up':
  171. next = current.previousElementSibling;
  172. if (next === null) {
  173. next = results[results.length - 1];
  174. }
  175. break;
  176. case 'bottom':
  177. next = results[results.length - 1];
  178. break;
  179. case 'top':
  180. /* falls through */
  181. default:
  182. next = results[0];
  183. }
  184. }
  185. if (next) {
  186. current.removeAttribute('data-vim-selected');
  187. next.setAttribute('data-vim-selected', 'true');
  188. var link = next.querySelector('h3 a') || next.querySelector('a');
  189. if (link !== null) {
  190. link.focus();
  191. }
  192. if (!noScroll) {
  193. scrollPageToSelected();
  194. }
  195. }
  196. };
  197. }
  198. function reloadPage() {
  199. document.location.reload(true);
  200. }
  201. function removeFocus() {
  202. if (document.activeElement) {
  203. document.activeElement.blur();
  204. }
  205. }
  206. function pageButtonClick(num) {
  207. return function() {
  208. var buttons = $('div#pagination button[type="submit"]');
  209. if (buttons.length !== 2) {
  210. console.log('page navigation with this theme is not supported');
  211. return;
  212. }
  213. if (num >= 0 && num < buttons.length) {
  214. buttons[num].click();
  215. } else {
  216. console.log('pageButtonClick(): invalid argument');
  217. }
  218. };
  219. }
  220. function scrollPageToSelected() {
  221. var sel = document.querySelector('.result[data-vim-selected]');
  222. if (sel === null) {
  223. return;
  224. }
  225. var wtop = document.documentElement.scrollTop || document.body.scrollTop,
  226. wheight = document.documentElement.clientHeight,
  227. etop = sel.offsetTop,
  228. ebot = etop + sel.clientHeight,
  229. offset = 120;
  230. // first element ?
  231. if ((sel.previousElementSibling === null) && (ebot < wheight)) {
  232. // set to the top of page if the first element
  233. // is fully included in the viewport
  234. window.scroll(window.scrollX, 0);
  235. return;
  236. }
  237. if (wtop > (etop - offset)) {
  238. window.scroll(window.scrollX, etop - offset);
  239. } else {
  240. var wbot = wtop + wheight;
  241. if (wbot < (ebot + offset)) {
  242. window.scroll(window.scrollX, ebot - wheight + offset);
  243. }
  244. }
  245. }
  246. function scrollPage(amount) {
  247. return function() {
  248. window.scrollBy(0, amount);
  249. highlightResult('visible')();
  250. };
  251. }
  252. function scrollPageTo(position, nav) {
  253. return function() {
  254. window.scrollTo(0, position);
  255. highlightResult(nav)();
  256. };
  257. }
  258. function searchInputFocus() {
  259. window.scrollTo(0, 0);
  260. document.querySelector('#q').focus();
  261. }
  262. function openResult(newTab) {
  263. return function() {
  264. var link = document.querySelector('.result[data-vim-selected] h3 a');
  265. if (link !== null) {
  266. var url = link.getAttribute('href');
  267. if (newTab) {
  268. window.open(url);
  269. } else {
  270. window.location.href = url;
  271. }
  272. }
  273. };
  274. }
  275. function initHelpContent(divElement) {
  276. var categories = {};
  277. for (var k in vimKeys) {
  278. var key = vimKeys[k];
  279. categories[key.cat] = categories[key.cat] || [];
  280. categories[key.cat].push(key);
  281. }
  282. var sorted = Object.keys(categories).sort(function(a, b) {
  283. return categories[b].length - categories[a].length;
  284. });
  285. if (sorted.length === 0) {
  286. return;
  287. }
  288. var html = '<a href="#" class="close" aria-label="close" title="close">×</a>';
  289. html += '<h3>How to navigate searx with Vim-like hotkeys</h3>';
  290. html += '<table>';
  291. for (var i = 0; i < sorted.length; i++) {
  292. var cat = categories[sorted[i]];
  293. var lastCategory = i === (sorted.length - 1);
  294. var first = i % 2 === 0;
  295. if (first) {
  296. html += '<tr>';
  297. }
  298. html += '<td>';
  299. html += '<h4>' + cat[0].cat + '</h4>';
  300. html += '<ul class="list-unstyled">';
  301. for (var cj in cat) {
  302. html += '<li><kbd>' + cat[cj].key + '</kbd> ' + cat[cj].des + '</li>';
  303. }
  304. html += '</ul>';
  305. html += '</td>'; // col-sm-*
  306. if (!first || lastCategory) {
  307. html += '</tr>'; // row
  308. }
  309. }
  310. html += '</table>';
  311. divElement.innerHTML = html;
  312. }
  313. function toggleHelp() {
  314. var helpPanel = document.querySelector('#vim-hotkeys-help');
  315. console.log(helpPanel);
  316. if (helpPanel === undefined || helpPanel === null) {
  317. // first call
  318. helpPanel = document.createElement('div');
  319. helpPanel.id = 'vim-hotkeys-help';
  320. helpPanel.className='dialog-modal';
  321. helpPanel.style='width: 40%';
  322. initHelpContent(helpPanel);
  323. var body = document.getElementsByTagName('body')[0];
  324. body.appendChild(helpPanel);
  325. } else {
  326. // togggle hidden
  327. helpPanel.classList.toggle('invisible');
  328. return;
  329. }
  330. }
  331. });