vim_hotkeys.js 9.7 KB

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