commit: 3c6a54012c157667f1efcb23a9652b973b80cbce
parent: 603ecbeb6e75b28dff0427ac0cd3689348ef11ce
Author: Adam Tauber <asciimoo@gmail.com>
Date: Mon, 25 Apr 2016 13:22:32 +0200
Merge pull request #547 from ukwt/vim-hotkeys
Vim-inspired hotkeys plugin
Diffstat:
4 files changed, 375 insertions(+), 1 deletion(-)
diff --git a/searx/plugins/__init__.py b/searx/plugins/__init__.py
@@ -23,7 +23,8 @@ from searx.plugins import (https_rewrite,
open_results_on_new_tab,
self_info,
search_on_category_select,
- tracker_url_remover)
+ tracker_url_remover,
+ vim_hotkeys)
required_attrs = (('name', str),
('description', str),
@@ -77,3 +78,4 @@ plugins.register(open_results_on_new_tab)
plugins.register(self_info)
plugins.register(search_on_category_select)
plugins.register(tracker_url_remover)
+plugins.register(vim_hotkeys)
diff --git a/searx/plugins/vim_hotkeys.py b/searx/plugins/vim_hotkeys.py
@@ -0,0 +1,10 @@
+from flask.ext.babel import gettext
+
+name = gettext('Vim-like hotkeys')
+description = gettext('Navigate search results with Vim-like hotkeys '
+ '(JavaScript required). '
+ 'Press "h" key on main or result page to get help.')
+default_on = False
+
+js_dependencies = ('plugins/js/vim_hotkeys.js',)
+css_dependencies = ('plugins/css/vim_hotkeys.css',)
diff --git a/searx/static/plugins/css/vim_hotkeys.css b/searx/static/plugins/css/vim_hotkeys.css
@@ -0,0 +1,26 @@
+.vim-hotkeys-help {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ z-index: 9999999;
+ overflow-y: auto;
+ max-height: 80%;
+ box-shadow: 0 0 1em;
+}
+
+.dflex {
+ display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */
+ display: -moz-box; /* OLD - Firefox 19- (buggy but mostly works) */
+ display: -ms-flexbox; /* TWEENER - IE 10 */
+ display: -webkit-flex; /* NEW - Chrome */
+ display: flex; /* NEW, Spec - Opera 12.1, Firefox 20+ */
+}
+
+.iflex {
+ -webkit-box-flex: 1; /* OLD - iOS 6-, Safari 3.1-6 */
+ -moz-box-flex: 1; /* OLD - Firefox 19- */
+ -webkit-flex: 1; /* Chrome */
+ -ms-flex: 1; /* IE 10 */
+ flex: 1; /* NEW, Spec - Opera 12.1, Firefox 20+ */
+}
diff --git a/searx/static/plugins/js/vim_hotkeys.js b/searx/static/plugins/js/vim_hotkeys.js
@@ -0,0 +1,336 @@
+$(document).ready(function() {
+ highlightResult('top')();
+
+ $('.result').on('click', function() {
+ highlightResult($(this))();
+ });
+
+ var vimKeys = {
+ 27: {
+ key: 'Escape',
+ fun: removeFocus,
+ des: 'remove focus from the focused input',
+ cat: 'Control'
+ },
+ 73: {
+ key: 'i',
+ fun: searchInputFocus,
+ des: 'focus on the search input',
+ cat: 'Control'
+ },
+ 66: {
+ key: 'b',
+ fun: scrollPage(-window.innerHeight),
+ des: 'scroll one page up',
+ cat: 'Navigation'
+ },
+ 70: {
+ key: 'f',
+ fun: scrollPage(window.innerHeight),
+ des: 'scroll one page down',
+ cat: 'Navigation'
+ },
+ 85: {
+ key: 'u',
+ fun: scrollPage(-window.innerHeight / 2),
+ des: 'scroll half a page up',
+ cat: 'Navigation'
+ },
+ 68: {
+ key: 'd',
+ fun: scrollPage(window.innerHeight / 2),
+ des: 'scroll half a page down',
+ cat: 'Navigation'
+ },
+ 71: {
+ key: 'g',
+ fun: scrollPageTo(-document.body.scrollHeight, 'top'),
+ des: 'scroll to the top of the page',
+ cat: 'Navigation'
+ },
+ 86: {
+ key: 'v',
+ fun: scrollPageTo(document.body.scrollHeight, 'bottom'),
+ des: 'scroll to the bottom of the page',
+ cat: 'Navigation'
+ },
+ 75: {
+ key: 'k',
+ fun: highlightResult('up'),
+ des: 'select previous search result',
+ cat: 'Results'
+ },
+ 74: {
+ key: 'j',
+ fun: highlightResult('down'),
+ des: 'select next search result',
+ cat: 'Results'
+ },
+ 80: {
+ key: 'p',
+ fun: pageButtonClick(0),
+ des: 'go to previous page',
+ cat: 'Results'
+ },
+ 78: {
+ key: 'n',
+ fun: pageButtonClick(1),
+ des: 'go to next page',
+ cat: 'Results'
+ },
+ 79: {
+ key: 'o',
+ fun: openResult(false),
+ des: 'open search result',
+ cat: 'Results'
+ },
+ 84: {
+ key: 't',
+ fun: openResult(true),
+ des: 'open the result in a new tab',
+ cat: 'Results'
+ },
+ 82: {
+ key: 'r',
+ fun: reloadPage,
+ des: 'reload page from the server',
+ cat: 'Control'
+ },
+ 72: {
+ key: 'h',
+ fun: toggleHelp,
+ des: 'toggle help window',
+ cat: 'Other'
+ }
+ };
+
+ $(document).keyup(function(e) {
+ // check for modifiers so we don't break browser's hotkeys
+ if (vimKeys.hasOwnProperty(e.keyCode)
+ && !e.ctrlKey
+ && !e.altKey
+ && !e.shiftKey
+ && !e.metaKey)
+ {
+ if (e.keyCode === 27) {
+ if (e.target.tagName.toLowerCase() === 'input') {
+ vimKeys[e.keyCode].fun();
+ }
+ } else {
+ if (e.target === document.body) {
+ vimKeys[e.keyCode].fun();
+ }
+ }
+ }
+ });
+
+ function highlightResult(which) {
+ return function() {
+ var current = $('.result[data-vim-selected]');
+ if (current.length === 0) {
+ current = $('.result:first');
+ if (current.length === 0) {
+ return;
+ }
+ }
+
+ var next;
+
+ if (typeof which !== 'string') {
+ next = which;
+ } else {
+ switch (which) {
+ case 'visible':
+ var top = $(window).scrollTop();
+ var bot = top + $(window).height();
+ var results = $('.result');
+
+ for (var i = 0; i < results.length; i++) {
+ next = $(results[i]);
+ var etop = next.offset().top;
+ var ebot = etop + next.height();
+
+ if ((ebot <= bot) && (etop > top)) {
+ break;
+ }
+ }
+ break;
+ case 'down':
+ next = current.next('.result');
+ if (next.length === 0) {
+ next = $('.result:first');
+ }
+ break;
+ case 'up':
+ next = current.prev('.result');
+ if (next.length === 0) {
+ next = $('.result:last');
+ }
+ break;
+ case 'bottom':
+ next = $('.result:last');
+ break;
+ case 'top':
+ default:
+ next = $('.result:first');
+ }
+ }
+
+ if (next) {
+ current.removeAttr('data-vim-selected').removeClass('well well-sm');
+ next.attr('data-vim-selected', 'true').addClass('well well-sm');
+ scrollPageToSelected();
+ }
+ }
+ }
+
+ function reloadPage() {
+ document.location.reload(false);
+ }
+
+ function removeFocus() {
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ }
+
+ function pageButtonClick(num) {
+ return function() {
+ var buttons = $('div#pagination button[type="submit"]');
+ if (buttons.length !== 2) {
+ console.log('page navigation with this theme is not supported');
+ return;
+ }
+ if (num >= 0 && num < buttons.length) {
+ buttons[num].click();
+ } else {
+ console.log('pageButtonClick(): invalid argument');
+ }
+ }
+ }
+
+ function scrollPageToSelected() {
+ var sel = $('.result[data-vim-selected]');
+ if (sel.length !== 1) {
+ return;
+ }
+
+ var wnd = $(window);
+
+ var wtop = wnd.scrollTop();
+ var etop = sel.offset().top;
+
+ var offset = 30;
+
+ if (wtop > etop) {
+ wnd.scrollTop(etop - offset);
+ } else {
+ var ebot = etop + sel.height();
+ var wbot = wtop + wnd.height();
+
+ if (wbot < ebot) {
+ wnd.scrollTop(ebot - wnd.height() + offset);
+ }
+ }
+ }
+
+ function scrollPage(amount) {
+ return function() {
+ window.scrollBy(0, amount);
+ highlightResult('visible')();
+ }
+ }
+
+ function scrollPageTo(position, nav) {
+ return function() {
+ window.scrollTo(0, position);
+ highlightResult(nav)();
+ }
+ }
+
+ function searchInputFocus() {
+ $('input#q').focus();
+ }
+
+ function openResult(newTab) {
+ return function() {
+ var link = $('.result[data-vim-selected] .result_header a');
+ if (link.length) {
+ var url = link.attr('href');
+ if (newTab) {
+ window.open(url);
+ } else {
+ window.location.href = url;
+ }
+ }
+ };
+ }
+
+ function toggleHelp() {
+ var helpPanel = $('#vim-hotkeys-help');
+ if (helpPanel.length) {
+ helpPanel.toggleClass('hidden');
+ return;
+ }
+
+ var categories = {};
+
+ for (var k in vimKeys) {
+ var key = vimKeys[k];
+ categories[key.cat] = categories[key.cat] || [];
+ categories[key.cat].push(key);
+ }
+
+ var sorted = Object.keys(categories).sort(function(a, b) {
+ return categories[b].length - categories[a].length;
+ });
+
+ if (sorted.length === 0) {
+ return;
+ }
+
+ var html = '<div id="vim-hotkeys-help" class="well vim-hotkeys-help">';
+ html += '<div class="container-fluid">';
+
+ html += '<div class="row">';
+ html += '<div class="col-sm-12">';
+ html += '<h3>How to navigate searx with Vim-like hotkeys</h3>';
+ html += '</div>'; // col-sm-12
+ html += '</div>'; // row
+
+ for (var i = 0; i < sorted.length; i++) {
+ var cat = categories[sorted[i]];
+
+ var lastCategory = i === (sorted.length - 1);
+ var first = i % 2 === 0;
+
+ if (first) {
+ html += '<div class="row dflex">';
+ }
+ html += '<div class="col-sm-' + (first && lastCategory ? 12 : 6) + ' dflex">';
+
+ html += '<div class="panel panel-default iflex">';
+ html += '<div class="panel-heading">' + cat[0].cat + '</div>';
+ html += '<div class="panel-body">';
+ html += '<ul class="list-unstyled">';
+
+ for (var cj in cat) {
+ html += '<li><kbd>' + cat[cj].key + '</kbd> ' + cat[cj].des + '</li>';
+ }
+
+ html += '</ul>';
+ html += '</div>'; // panel-body
+ html += '</div>'; // panel
+ html += '</div>'; // col-sm-*
+
+ if (!first || lastCategory) {
+ html += '</div>'; // row
+ }
+ }
+
+ html += '</div>'; // container-fluid
+ html += '</div>'; // vim-hotkeys-help
+
+ $('body').append(html);
+ }
+});