Skip to content

Commit d8e8edd

Browse files
committed
Add up/down arrow navigation of search results
1 parent 894b388 commit d8e8edd

File tree

9 files changed

+218
-47
lines changed

9 files changed

+218
-47
lines changed

Manifest.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ lib/rdoc/generator/template/darkfish/js/search.js
7777
lib/rdoc/generator/template/darkfish/page.rhtml
7878
lib/rdoc/generator/template/darkfish/rdoc.css
7979
lib/rdoc/generator/template/darkfish/table_of_contents.rhtml
80+
lib/rdoc/generator/template/json_index/js/navigation.js
8081
lib/rdoc/generator/template/json_index/js/searcher.js
8182
lib/rdoc/ghost_method.rb
8283
lib/rdoc/include.rb

TODO.rdoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ Some file contains some things that might happen in RDoc, or might not
22

33
=== RDoc 3.10
44

5+
* Copy static files into doc dir for HTML
6+
57
=== RDoc 3.11
68

79
* Reload the RDoc tree from an RI store

lib/rdoc/generator/json_index.rb

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,6 @@ class RDoc::Generator::JsonIndex
7878

7979
SEARCH_INDEX_FILE = File.join 'js', 'search_index.js'
8080

81-
##
82-
# Where the search implementation lives in the generated output
83-
84-
SEARCHER_FILE = File.join 'js', 'searcher.js'
85-
8681
attr_reader :index # :nodoc:
8782

8883
##
@@ -95,7 +90,7 @@ def initialize parent_generator, options
9590
@parent_generator = parent_generator
9691
@options = options
9792

98-
@template_dir = Pathname.new options.template_dir
93+
@template_dir = File.expand_path '../template/json_index', __FILE__
9994
@base_dir = @parent_generator.base_dir
10095

10196
@classes = nil
@@ -140,13 +135,13 @@ def generate top_levels
140135
JSON.dump data, io, 0
141136
end
142137

143-
searcher_file = out_dir + SEARCHER_FILE
138+
Dir.chdir @template_dir do
139+
Dir['**/*.js'].each do |source|
140+
dest = File.join out_dir, source
144141

145-
searcher =
146-
File.expand_path "../template/json_index/#{SEARCHER_FILE}", __FILE__
147-
148-
FileUtils.install(searcher, searcher_file,
149-
:mode => 0644, :verbose => $DEBUG_RDOC)
142+
FileUtils.install source, dest, :mode => 0644, :verbose => $DEBUG_RDOC
143+
end
144+
end
150145
end
151146

152147
##

lib/rdoc/generator/template/darkfish/_head.rhtml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
</script>
1010

1111
<script type="text/javascript" charset="utf-8" src="<%= rel_prefix %>/js/jquery.js"></script>
12+
<script type="text/javascript" charset="utf-8" src="<%= rel_prefix %>/js/navigation.js"></script>
1213
<script type="text/javascript" charset="utf-8" src="<%= rel_prefix %>/js/search_index.js"></script>
1314
<script type="text/javascript" charset="utf-8" src="<%= rel_prefix %>/js/search.js"></script>
1415
<script type="text/javascript" charset="utf-8" src="<%= rel_prefix %>/js/searcher.js"></script>

lib/rdoc/generator/template/darkfish/js/darkfish.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,35 @@ function hookQuickSearch() {
7575
$(search_section).show();
7676

7777
var search = new Search(search_data, input, result);
78+
79+
search.renderItem = function(result) {
80+
var li = document.createElement('li');
81+
var html = '';
82+
83+
// TODO add relative path to <script> per-page
84+
html += '<p class="search-match"><a href="' + rdoc_rel_prefix + result.path + '">' + this.hlt(result.title);
85+
if (result.params)
86+
html += '<span class="params">' + result.params + '</span>';
87+
html += '</a>';
88+
89+
90+
if (result.namespace)
91+
html += '<p class="search-namespace">' + this.hlt(result.namespace);
92+
93+
if (result.snippet)
94+
html += '<div class="search-snippet">' + result.snippet + '</div>';
95+
96+
li.innerHTML = html;
97+
98+
return li;
99+
}
100+
101+
search.select = function(result) {
102+
var result_element = result.get(0);
103+
window.location.href = result_element.firstChild.firstChild.href;
104+
}
105+
106+
search.scrollIntoView = search.scrollInWindow;
78107
};
79108

80109
function highlightTarget( anchor ) {

lib/rdoc/generator/template/darkfish/js/search.js

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Search = function(data, input, result) {
99
this.init();
1010
}
1111

12-
Search.prototype = new function() {
12+
Search.prototype = $.extend({}, Navigation, new function() {
1313
var suid = 1;
1414

1515
this.init = function() {
@@ -23,13 +23,23 @@ Search.prototype = new function() {
2323
this.searcher.ready(function(results, isLast) {
2424
_this.addResults(results, isLast);
2525
})
26+
27+
this.initNavigation();
28+
this.setNavigationActive(false);
2629
}
2730

2831
this.search = function(value, selectFirstMatch) {
2932
value = jQuery.trim(value).toLowerCase();
33+
if (value) {
34+
this.setNavigationActive(true);
35+
} else {
36+
this.setNavigationActive(false);
37+
}
38+
3039
if (value == '') {
3140
this.lastQuery = value;
3241
this.$result.empty();
42+
this.setNavigationActive(false);
3343
} else if (value != this.lastQuery) {
3444
this.lastQuery = value;
3545
this.firstRun = true;
@@ -45,46 +55,40 @@ Search.prototype = new function() {
4555
}
4656

4757
for (var i=0, l = results.length; i < l; i++) {
48-
target.appendChild(renderItem.call(this, results[i]));
58+
target.appendChild(this.renderItem.call(this, results[i]));
4959
};
5060

5161
if (this.firstRun && results.length > 0) {
5262
this.firstRun = false;
5363
this.$current = $(target.firstChild);
5464
this.$current.addClass('current');
5565
}
66+
if (jQuery.browser.msie) this.$element[0].className += '';
5667
}
5768

58-
function renderItem(result) {
59-
var li = document.createElement('li');
60-
var html = '';
61-
62-
// TODO add relative path to <script> per-page
63-
html += '<p class="search-match"><a href="' + rdoc_rel_prefix + result.path + '">' + hlt(result.title);
64-
if (result.params)
65-
html += '<span class="params">' + result.params + '</span>';
66-
html += '</a>';
67-
68-
69-
if (result.namespace)
70-
html += '<p class="search-namespace">' + hlt(result.namespace);
71-
72-
if (result.snippet)
73-
html += '<div class="search-snippet">' + result.snippet + '</div>';
74-
75-
li.innerHTML = html;
76-
77-
return li;
69+
this.move = function(isDown) {
70+
if (!this.$current) return;
71+
var $next = this.$current[isDown ? 'next' : 'prev']();
72+
if ($next.length) {
73+
this.$current.removeClass('current');
74+
$next.addClass('current');
75+
this.scrollIntoView($next[0], this.$view[0]);
76+
this.$current = $next;
77+
}
78+
return true;
7879
}
7980

80-
function hlt(html) {
81-
return escapeHTML(html).replace(/\u0001/g, '<em>').replace(/\u0002/g, '</em>')
81+
this.hlt = function(html) {
82+
return this.escapeHTML(html).
83+
replace(/\u0001/g, '<em>').
84+
replace(/\u0002/g, '</em>');
8285
}
8386

84-
function escapeHTML(html) {
87+
this.escapeHTML = function(html) {
8588
return html.replace(/[&<>]/g, function(c) {
8689
return '&#' + c.charCodeAt(0) + ';';
8790
});
8891
}
8992

90-
}
93+
});
94+

lib/rdoc/generator/template/darkfish/rdoc.css

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
/* Base Green is: #6C8C22 */
1010

11-
*{ padding: 0; margin: 0; }
11+
* { padding: 0; margin: 0; }
1212

1313
body {
1414
background: #efefef;
@@ -238,14 +238,6 @@ ul.link-list .type {
238238
background: #ccc;
239239
}
240240

241-
#no-search-results {
242-
margin: 0 auto 1em;
243-
text-align: center;
244-
font-size: 14px;
245-
font-weight: bold;
246-
color: #aaa;
247-
}
248-
249241
/* @end */
250242

251243
/* @group Documentation Section */
@@ -569,6 +561,10 @@ pre {
569561
text-shadow: none;
570562
}
571563

564+
#search-results .current .search-match {
565+
font-weight: bold;
566+
}
567+
572568
#search-results li {
573569
list-style: none;
574570
border-bottom: 1px solid #AAA;
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
* Navigation allows movement using the arrow keys through the search results.
3+
*
4+
* When using this library you will need to set scrollIntoView to the
5+
* appropriate function for your layout. Use scrollInWindow if the container
6+
* is not scrollable and scrollInElement if the container is a separate
7+
* scrolling region.
8+
*/
9+
Navigation = new function() {
10+
this.initNavigation = function() {
11+
var _this = this;
12+
13+
$(document).keydown(function(e) {
14+
_this.onkeydown(e);
15+
}).keyup(function(e) {
16+
_this.onkeyup(e);
17+
});
18+
19+
this.navigationActive = true;
20+
}
21+
22+
this.setNavigationActive = function(state) {
23+
this.navigationActive = state;
24+
this.clearMoveTimeout();
25+
}
26+
27+
this.onkeyup = function(e) {
28+
if (!this.navigationActive) return;
29+
30+
switch(e.keyCode) {
31+
case 37: //Event.KEY_LEFT:
32+
case 38: //Event.KEY_UP:
33+
case 39: //Event.KEY_RIGHT:
34+
case 40: //Event.KEY_DOWN:
35+
this.clearMoveTimeout();
36+
break;
37+
}
38+
}
39+
40+
this.onkeydown = function(e) {
41+
if (!this.navigationActive) return;
42+
switch(e.keyCode) {
43+
case 37: //Event.KEY_LEFT:
44+
if (this.moveLeft()) e.preventDefault();
45+
break;
46+
case 38: //Event.KEY_UP:
47+
if (e.keyCode == 38 || e.ctrlKey) {
48+
if (this.moveUp()) e.preventDefault();
49+
this.startMoveTimeout(false);
50+
}
51+
break;
52+
case 39: //Event.KEY_RIGHT:
53+
if (this.moveRight()) e.preventDefault();
54+
break;
55+
case 40: //Event.KEY_DOWN:
56+
if (e.keyCode == 40 || e.ctrlKey) {
57+
if (this.moveDown()) e.preventDefault();
58+
this.startMoveTimeout(true);
59+
}
60+
break;
61+
case 13: //Event.KEY_RETURN:
62+
if (this.$current)
63+
e.preventDefault();
64+
this.select(this.$current);
65+
break;
66+
}
67+
if (e.ctrlKey && e.shiftKey) this.select(this.$current);
68+
}
69+
70+
this.clearMoveTimeout = function() {
71+
clearTimeout(this.moveTimeout);
72+
this.moveTimeout = null;
73+
}
74+
75+
this.startMoveTimeout = function(isDown) {
76+
if (!$.browser.mozilla && !$.browser.opera) return;
77+
if (this.moveTimeout) this.clearMoveTimeout();
78+
var _this = this;
79+
80+
var go = function() {
81+
if (!_this.moveTimeout) return;
82+
_this[isDown ? 'moveDown' : 'moveUp']();
83+
_this.moveTimout = setTimeout(go, 100);
84+
}
85+
this.moveTimeout = setTimeout(go, 200);
86+
}
87+
88+
this.moveRight = function() {
89+
}
90+
91+
this.moveLeft = function() {
92+
}
93+
94+
this.move = function(isDown) {
95+
}
96+
97+
this.moveUp = function() {
98+
return this.move(false);
99+
}
100+
101+
this.moveDown = function() {
102+
return this.move(true);
103+
}
104+
105+
/*
106+
* Scrolls to the given element in the scrollable element view.
107+
*/
108+
this.scrollInElement = function(element, view) {
109+
var offset, viewHeight, viewScroll, height;
110+
offset = element.offsetTop;
111+
height = element.offsetHeight;
112+
viewHeight = view.offsetHeight;
113+
viewScroll = view.scrollTop;
114+
115+
if (offset - viewScroll + height > viewHeight) {
116+
view.scrollTop = offset - viewHeight + height;
117+
}
118+
if (offset < viewScroll) {
119+
view.scrollTop = offset;
120+
}
121+
}
122+
123+
/*
124+
* Scrolls to the given element in the window. The second argument is
125+
* ignored
126+
*/
127+
this.scrollInWindow = function(element, ignored) {
128+
var offset, viewHeight, viewScroll, height;
129+
offset = element.offsetTop;
130+
height = element.offsetHeight;
131+
viewHeight = window.innerHeight;
132+
viewScroll = window.scrollY;
133+
134+
if (offset - viewScroll + height > viewHeight) {
135+
window.scrollTo(window.scrollX, offset - viewHeight + height);
136+
}
137+
if (offset < viewScroll) {
138+
window.scrollTo(window.scrollX, offset);
139+
}
140+
}
141+
}
142+

test/test_rdoc_generator_json_index.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ def test_generate
8282
@g.generate @top_levels
8383

8484
assert_file 'js/searcher.js'
85+
assert_file 'js/navigation.js'
8586
assert_file 'js/search_index.js'
8687

8788
json = File.read 'js/search_index.js'

0 commit comments

Comments
 (0)