mirror of https://github.com/searx/searx
Merge pull request #547 from ukwt/vim-hotkeys
Vim-inspired hotkeys plugin
This commit is contained in:
commit
3c6a54012c
|
@ -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)
|
||||
|
|
|
@ -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',)
|
|
@ -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+ */
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
});
|
Loading…
Reference in New Issue