Skip to content

Commit 0248b18

Browse files
committed
Rebuild Aliki's searching mechanism
1 parent aaaf97f commit 0248b18

File tree

14 files changed

+1372
-18
lines changed

14 files changed

+1372
-18
lines changed

lib/rdoc/code_object/class_module.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,9 @@ def remove_things(my_things, other_files) # :nodoc:
689689

690690
##
691691
# Search record used by RDoc::Generator::JsonIndex
692+
#
693+
# TODO: Remove this method after dropping the darkfish theme and JsonIndex generator.
694+
# Use #search_snippet instead for getting documentation snippets.
692695

693696
def search_record
694697
[
@@ -702,6 +705,16 @@ def search_record
702705
]
703706
end
704707

708+
##
709+
# Returns an HTML snippet of the first comment for search results.
710+
711+
def search_snippet
712+
first_comment = @comment_location.first&.first
713+
return '' unless first_comment && !first_comment.empty?
714+
715+
snippet(first_comment)
716+
end
717+
705718
##
706719
# Sets the store for this class or module and its contained code objects.
707720

lib/rdoc/code_object/constant.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,15 @@ def path
154154
"#{@parent.path}##{@name}"
155155
end
156156

157+
##
158+
# Returns an HTML snippet of the comment for search results.
159+
160+
def search_snippet
161+
return '' if comment.empty?
162+
163+
snippet(comment)
164+
end
165+
157166
def pretty_print(q) # :nodoc:
158167
q.group 2, "[#{self.class.name} #{full_name}", "]" do
159168
unless comment.empty? then

lib/rdoc/code_object/method_attr.rb

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,9 @@ def pretty_print(q) # :nodoc:
375375
##
376376
# Used by RDoc::Generator::JsonIndex to create a record for the search
377377
# engine.
378+
#
379+
# TODO: Remove this method after dropping the darkfish theme and JsonIndex generator.
380+
# Use #search_snippet instead for getting documentation snippets.
378381

379382
def search_record
380383
[
@@ -384,10 +387,19 @@ def search_record
384387
@parent.full_name,
385388
path,
386389
params,
387-
snippet(@comment),
390+
search_snippet,
388391
]
389392
end
390393

394+
##
395+
# Returns an HTML snippet of the comment for search results.
396+
397+
def search_snippet
398+
return '' if @comment.empty?
399+
400+
snippet(@comment)
401+
end
402+
391403
def to_s # :nodoc:
392404
if @is_alias_for
393405
"#{self.class.name}: #{full_name} -> #{is_alias_for}"

lib/rdoc/code_object/top_level.rb

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,9 @@ def pretty_print(q) # :nodoc:
237237

238238
##
239239
# Search record used by RDoc::Generator::JsonIndex
240+
#
241+
# TODO: Remove this method after dropping the darkfish theme and JsonIndex generator.
242+
# Use #search_snippet instead for getting documentation snippets.
240243

241244
def search_record
242245
return unless @parser < RDoc::Parser::Text
@@ -248,10 +251,19 @@ def search_record
248251
'',
249252
path,
250253
'',
251-
snippet(@comment),
254+
search_snippet,
252255
]
253256
end
254257

258+
##
259+
# Returns an HTML snippet of the comment for search results.
260+
261+
def search_snippet
262+
return '' if @comment.empty?
263+
264+
snippet(@comment)
265+
end
266+
255267
##
256268
# Is this TopLevel from a text file instead of a source code file?
257269

lib/rdoc/generator/aliki.rb

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,30 @@ def initialize(store, options)
1515
@template_dir = Pathname.new(aliki_template_dir)
1616
end
1717

18+
##
19+
# Generate documentation. Overrides Darkfish to use Aliki's own search index
20+
# instead of the JsonIndex generator.
21+
22+
def generate
23+
setup
24+
25+
write_style_sheet
26+
generate_index
27+
generate_class_files
28+
generate_file_files
29+
generate_table_of_contents
30+
write_search_index
31+
32+
copy_static
33+
34+
rescue => e
35+
debug_msg "%s: %s\n %s" % [
36+
e.class.name, e.message, e.backtrace.join("\n ")
37+
]
38+
39+
raise
40+
end
41+
1842
##
1943
# Copy only the static assets required by the Aliki theme. Unlike Darkfish we
2044
# don't ship embedded fonts or image sprites, so limit the asset list to keep
@@ -39,4 +63,104 @@ def write_style_sheet
3963
install_rdoc_static_file @template_dir + path, dst, options
4064
end
4165
end
66+
67+
##
68+
# Build a search index array for Aliki's searcher.
69+
70+
def build_search_index
71+
setup
72+
73+
index = []
74+
75+
@classes.each do |klass|
76+
next unless klass.display?
77+
78+
index << build_class_module_entry(klass)
79+
80+
klass.constants.each do |const|
81+
next unless const.display?
82+
83+
index << build_constant_entry(const, klass)
84+
end
85+
end
86+
87+
@methods.each do |method|
88+
next unless method.display?
89+
90+
index << build_method_entry(method)
91+
end
92+
93+
index
94+
end
95+
96+
##
97+
# Write the search index as a JavaScript file
98+
# Format: var search_data = { index: [...] }
99+
#
100+
# We still write to a .js instead of a .json because loading a JSON file triggers CORS check in browsers.
101+
# And if we simply inspect the generated pages using file://, which is often the case due to lack of the server mode,
102+
# the JSON file will be blocked by the browser.
103+
104+
def write_search_index
105+
debug_msg "Writing Aliki search index"
106+
107+
index = build_search_index
108+
109+
FileUtils.mkdir_p 'js' unless @dry_run
110+
111+
search_index_path = 'js/search_data.js'
112+
return if @dry_run
113+
114+
data = { index: index }
115+
File.write search_index_path, "var search_data = #{JSON.generate(data)};"
116+
end
117+
118+
private
119+
120+
def build_class_module_entry(klass)
121+
type = case klass
122+
when RDoc::NormalClass then 'class'
123+
when RDoc::NormalModule then 'module'
124+
else 'class'
125+
end
126+
127+
entry = {
128+
name: klass.name,
129+
full_name: klass.full_name,
130+
type: type,
131+
path: klass.path
132+
}
133+
134+
snippet = klass.search_snippet
135+
entry[:snippet] = snippet unless snippet.empty?
136+
entry
137+
end
138+
139+
def build_method_entry(method)
140+
type = method.singleton ? 'class_method' : 'instance_method'
141+
142+
entry = {
143+
name: method.name,
144+
full_name: method.full_name,
145+
type: type,
146+
path: method.path
147+
}
148+
149+
snippet = method.search_snippet
150+
entry[:snippet] = snippet unless snippet.empty?
151+
entry
152+
end
153+
154+
def build_constant_entry(const, parent)
155+
entry = {
156+
name: const.name,
157+
full_name: "#{parent.full_name}::#{const.name}",
158+
type: 'constant',
159+
path: parent.path
160+
}
161+
162+
snippet = const.search_snippet
163+
entry[:snippet] = snippet unless snippet.empty?
164+
entry
165+
end
42166
end

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,22 +116,22 @@
116116
></script>
117117

118118
<script
119-
src="<%= h asset_rel_prefix %>/js/navigation.js?v=<%= h RDoc::VERSION %>"
119+
src="<%= h asset_rel_prefix %>/js/search_navigation.js?v=<%= h RDoc::VERSION %>"
120120
defer
121121
></script>
122122

123123
<script
124-
src="<%= h asset_rel_prefix %>/js/search.js?v=<%= h RDoc::VERSION %>"
124+
src="<%= h asset_rel_prefix %>/js/search_data.js?v=<%= h RDoc::VERSION %>"
125125
defer
126126
></script>
127127

128128
<script
129-
src="<%= h asset_rel_prefix %>/js/search_index.js?v=<%= h RDoc::VERSION %>"
129+
src="<%= h asset_rel_prefix %>/js/search_ranker.js?v=<%= h RDoc::VERSION %>"
130130
defer
131131
></script>
132132

133133
<script
134-
src="<%= h asset_rel_prefix %>/js/searcher.js?v=<%= h RDoc::VERSION %>"
134+
src="<%= h asset_rel_prefix %>/js/search_controller.js?v=<%= h RDoc::VERSION %>"
135135
defer
136136
></script>
137137

lib/rdoc/generator/template/aliki/css/rdoc.css

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,16 @@
8585
--color-th-background: var(--color-neutral-100);
8686
--color-td-background: var(--color-neutral-50);
8787

88+
/* Search Type Badge Colors */
89+
--color-search-type-class-bg: #e6f0ff;
90+
--color-search-type-class-text: #0050a0;
91+
--color-search-type-module-bg: #e6ffe6;
92+
--color-search-type-module-text: #060;
93+
--color-search-type-constant-bg: #fff0e6;
94+
--color-search-type-constant-text: #995200;
95+
--color-search-type-method-bg: #f0e6ff;
96+
--color-search-type-method-text: #5200a0;
97+
8898
/* RGBA Colors (theme-agnostic) */
8999
--color-overlay: rgb(0 0 0 / 50%);
90100
--color-emphasis-bg: rgb(255 111 97 / 10%);
@@ -218,6 +228,16 @@
218228
--color-th-background: var(--color-background-tertiary);
219229
--color-td-background: var(--color-background-secondary);
220230

231+
/* Search Type Badge Colors - Dark Theme */
232+
--color-search-type-class-bg: #1e3a5f;
233+
--color-search-type-class-text: #93c5fd;
234+
--color-search-type-module-bg: #14532d;
235+
--color-search-type-module-text: #86efac;
236+
--color-search-type-constant-bg: #451a03;
237+
--color-search-type-constant-text: #fcd34d;
238+
--color-search-type-method-bg: #3b0764;
239+
--color-search-type-method-text: #d8b4fe;
240+
221241
/* Dark theme shadows (slightly more subtle) */
222242
--shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 40%), 0 1px 2px -1px rgb(0 0 0 / 40%);
223243
--shadow-md: 0 2px 8px rgb(0 0 0 / 40%);
@@ -1834,6 +1854,39 @@ footer.site-footer .footer-bottom:first-child {
18341854
font-weight: bold;
18351855
}
18361856

1857+
#search-results .search-type {
1858+
display: inline-block;
1859+
margin-left: var(--space-2);
1860+
padding: 0 var(--space-2);
1861+
font-size: var(--font-size-xs);
1862+
font-weight: 500;
1863+
border-radius: var(--radius-sm);
1864+
vertical-align: middle;
1865+
background: var(--color-background-tertiary);
1866+
color: var(--color-text-secondary);
1867+
}
1868+
1869+
#search-results .search-type-class {
1870+
background: var(--color-search-type-class-bg);
1871+
color: var(--color-search-type-class-text);
1872+
}
1873+
1874+
#search-results .search-type-module {
1875+
background: var(--color-search-type-module-bg);
1876+
color: var(--color-search-type-module-text);
1877+
}
1878+
1879+
#search-results .search-type-constant {
1880+
background: var(--color-search-type-constant-bg);
1881+
color: var(--color-search-type-constant-text);
1882+
}
1883+
1884+
#search-results .search-type-instance-method,
1885+
#search-results .search-type-class-method {
1886+
background: var(--color-search-type-method-bg);
1887+
color: var(--color-search-type-method-text);
1888+
}
1889+
18371890
#search-results li em {
18381891
background-color: var(--color-search-highlight-bg);
18391892
font-style: normal;

lib/rdoc/generator/template/aliki/js/aliki.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ function createSearchInstance(input, result) {
2828

2929
result.classList.remove("initially-hidden");
3030

31-
const search = new Search(search_data, input, result);
31+
const search = new SearchController(search_data, input, result);
3232

3333
search.renderItem = function(result) {
3434
const li = document.createElement('li');
@@ -40,8 +40,12 @@ function createSearchInstance(input, result) {
4040
html += `<span class="params">${result.params}</span>`;
4141
html += '</a>';
4242

43-
if (result.namespace)
44-
html += `<p class="search-namespace">${this.hlt(result.namespace)}`;
43+
// Add type indicator
44+
if (result.type) {
45+
const typeLabel = this.formatType(result.type);
46+
const typeClass = result.type.replace(/_/g, '-');
47+
html += `<span class="search-type search-type-${this.escapeHTML(typeClass)}">${typeLabel}</span>`;
48+
}
4549

4650
if (result.snippet)
4751
html += `<div class="search-snippet">${result.snippet}</div>`;
@@ -51,6 +55,17 @@ function createSearchInstance(input, result) {
5155
return li;
5256
}
5357

58+
search.formatType = function(type) {
59+
const typeLabels = {
60+
'class': 'class',
61+
'module': 'module',
62+
'constant': 'const',
63+
'instance_method': 'method',
64+
'class_method': 'method'
65+
};
66+
return typeLabels[type] || type;
67+
}
68+
5469
search.select = function(result) {
5570
let href = result.firstChild.firstChild.href;
5671
const query = this.input.value;

0 commit comments

Comments
 (0)