mirror of
				https://github.com/searxng/searxng.git
				synced 2025-10-31 02:27:06 -04:00 
			
		
		
		
	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) | ||||
|  | ||||
							
								
								
									
										10
									
								
								searx/plugins/vim_hotkeys.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								searx/plugins/vim_hotkeys.py
									
									
									
									
									
										Normal file
									
								
							| @ -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',) | ||||
							
								
								
									
										26
									
								
								searx/static/plugins/css/vim_hotkeys.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								searx/static/plugins/css/vim_hotkeys.css
									
									
									
									
									
										Normal file
									
								
							| @ -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+ */ | ||||
| } | ||||
							
								
								
									
										336
									
								
								searx/static/plugins/js/vim_hotkeys.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										336
									
								
								searx/static/plugins/js/vim_hotkeys.js
									
									
									
									
									
										Normal file
									
								
							| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user