瀏覽代碼

Merge pull request #547 from ukwt/vim-hotkeys

Vim-inspired hotkeys plugin
Adam Tauber 9 年之前
父節點
當前提交
3c6a54012c

+ 3
- 1
searx/plugins/__init__.py 查看文件

@@ -23,7 +23,8 @@ from searx.plugins import (https_rewrite,
23 23
                            open_results_on_new_tab,
24 24
                            self_info,
25 25
                            search_on_category_select,
26
-                           tracker_url_remover)
26
+                           tracker_url_remover,
27
+                           vim_hotkeys)
27 28
 
28 29
 required_attrs = (('name', str),
29 30
                   ('description', str),
@@ -77,3 +78,4 @@ plugins.register(open_results_on_new_tab)
77 78
 plugins.register(self_info)
78 79
 plugins.register(search_on_category_select)
79 80
 plugins.register(tracker_url_remover)
81
+plugins.register(vim_hotkeys)

+ 10
- 0
searx/plugins/vim_hotkeys.py 查看文件

@@ -0,0 +1,10 @@
1
+from flask.ext.babel import gettext
2
+
3
+name = gettext('Vim-like hotkeys')
4
+description = gettext('Navigate search results with Vim-like hotkeys '
5
+                      '(JavaScript required). '
6
+                      'Press "h" key on main or result page to get help.')
7
+default_on = False
8
+
9
+js_dependencies = ('plugins/js/vim_hotkeys.js',)
10
+css_dependencies = ('plugins/css/vim_hotkeys.css',)

+ 26
- 0
searx/static/plugins/css/vim_hotkeys.css 查看文件

@@ -0,0 +1,26 @@
1
+.vim-hotkeys-help {
2
+    position: fixed;
3
+    top: 50%;
4
+    left: 50%;
5
+    transform: translate(-50%, -50%);
6
+    z-index: 9999999;
7
+    overflow-y: auto;
8
+    max-height: 80%;
9
+    box-shadow: 0 0 1em;
10
+}
11
+
12
+.dflex {
13
+    display: -webkit-box;  /* OLD - iOS 6-, Safari 3.1-6 */
14
+    display: -moz-box;     /* OLD - Firefox 19- (buggy but mostly works) */
15
+    display: -ms-flexbox;  /* TWEENER - IE 10 */
16
+    display: -webkit-flex; /* NEW - Chrome */
17
+    display: flex;         /* NEW, Spec - Opera 12.1, Firefox 20+ */
18
+}
19
+
20
+.iflex {
21
+    -webkit-box-flex: 1; /* OLD - iOS 6-, Safari 3.1-6 */
22
+    -moz-box-flex: 1;    /* OLD - Firefox 19- */
23
+    -webkit-flex: 1;     /* Chrome */
24
+    -ms-flex: 1;         /* IE 10 */
25
+    flex: 1;             /* NEW, Spec - Opera 12.1, Firefox 20+ */
26
+}

+ 336
- 0
searx/static/plugins/js/vim_hotkeys.js 查看文件

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