rdoc 6.16.1 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +196 -0
- data/LEGAL.rdoc +6 -0
- data/README.md +90 -7
- data/doc/markup_reference/markdown.md +558 -0
- data/doc/markup_reference/rdoc.rdoc +1169 -0
- data/lib/rdoc/code_object/any_method.rb +15 -7
- data/lib/rdoc/code_object/class_module.rb +62 -11
- data/lib/rdoc/code_object/constant.rb +9 -0
- data/lib/rdoc/code_object/context/section.rb +20 -1
- data/lib/rdoc/code_object/method_attr.rb +13 -1
- data/lib/rdoc/code_object/top_level.rb +13 -1
- data/lib/rdoc/cross_reference.rb +30 -21
- data/lib/rdoc/generator/aliki.rb +141 -0
- data/lib/rdoc/generator/darkfish.rb +8 -2
- data/lib/rdoc/generator/template/aliki/_footer.rhtml +1 -1
- data/lib/rdoc/generator/template/aliki/_head.rhtml +4 -4
- data/lib/rdoc/generator/template/aliki/_header.rhtml +4 -4
- data/lib/rdoc/generator/template/aliki/_icons.rhtml +208 -0
- data/lib/rdoc/generator/template/aliki/_sidebar_ancestors.rhtml +12 -2
- data/lib/rdoc/generator/template/aliki/_sidebar_classes.rhtml +12 -2
- data/lib/rdoc/generator/template/aliki/_sidebar_extends.rhtml +20 -10
- data/lib/rdoc/generator/template/aliki/_sidebar_includes.rhtml +20 -10
- data/lib/rdoc/generator/template/aliki/_sidebar_methods.rhtml +32 -12
- data/lib/rdoc/generator/template/aliki/_sidebar_pages.rhtml +58 -28
- data/lib/rdoc/generator/template/aliki/_sidebar_search.rhtml +3 -3
- data/lib/rdoc/generator/template/aliki/_sidebar_sections.rhtml +16 -6
- data/lib/rdoc/generator/template/aliki/class.rhtml +13 -12
- data/lib/rdoc/generator/template/aliki/css/rdoc.css +401 -50
- data/lib/rdoc/generator/template/aliki/index.rhtml +2 -1
- data/lib/rdoc/generator/template/aliki/js/aliki.js +43 -21
- data/lib/rdoc/generator/template/aliki/js/c_highlighter.js +1 -1
- data/lib/rdoc/generator/template/aliki/js/{search.js → search_controller.js} +15 -6
- data/lib/rdoc/generator/template/aliki/js/search_navigation.js +105 -0
- data/lib/rdoc/generator/template/aliki/js/search_ranker.js +239 -0
- data/lib/rdoc/generator/template/aliki/page.rhtml +2 -1
- data/lib/rdoc/generator/template/aliki/servlet_not_found.rhtml +1 -1
- data/lib/rdoc/generator/template/aliki/servlet_root.rhtml +1 -1
- data/lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml +1 -1
- data/lib/rdoc/generator/template/darkfish/class.rhtml +11 -11
- data/lib/rdoc/generator/template/darkfish/css/rdoc.css +19 -0
- data/lib/rdoc/markdown.kpeg +1 -5
- data/lib/rdoc/markdown.rb +1 -5
- data/lib/rdoc/markup/attribute_manager.rb +28 -1
- data/lib/rdoc/markup/blank_line.rb +25 -23
- data/lib/rdoc/markup/element.rb +21 -0
- data/lib/rdoc/markup/hard_break.rb +30 -27
- data/lib/rdoc/markup/heading.rb +166 -77
- data/lib/rdoc/markup/raw.rb +52 -55
- data/lib/rdoc/markup/table.rb +48 -40
- data/lib/rdoc/markup/to_ansi.rb +4 -0
- data/lib/rdoc/markup/to_bs.rb +4 -0
- data/lib/rdoc/markup/to_html.rb +31 -11
- data/lib/rdoc/markup/to_html_crossref.rb +24 -5
- data/lib/rdoc/markup/to_label.rb +11 -1
- data/lib/rdoc/markup/to_rdoc.rb +11 -3
- data/lib/rdoc/markup/verbatim.rb +1 -1
- data/lib/rdoc/markup.rb +3 -2
- data/lib/rdoc/options.rb +1 -1
- data/lib/rdoc/parser/changelog.rb +8 -0
- data/lib/rdoc/rubygems_hook.rb +3 -3
- data/lib/rdoc/text.rb +15 -0
- data/lib/rdoc/token_stream.rb +4 -8
- data/lib/rdoc/version.rb +1 -1
- data/rdoc.gemspec +3 -3
- metadata +12 -10
- data/CONTRIBUTING.rdoc +0 -219
- data/ExampleMarkdown.md +0 -39
- data/ExampleRDoc.rdoc +0 -210
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<body role="document" class="file has-toc">
|
|
2
|
+
<%= render '_icons.rhtml' %>
|
|
2
3
|
<%= render '_header.rhtml' %>
|
|
3
4
|
<%= render '_sidebar_toggle.rhtml' %>
|
|
4
5
|
|
|
5
|
-
<nav id="navigation" role="navigation">
|
|
6
|
+
<nav id="navigation" role="navigation" hidden>
|
|
6
7
|
<%= render '_sidebar_pages.rhtml' %>
|
|
7
8
|
<%= render '_sidebar_classes.rhtml' %>
|
|
8
9
|
</nav>
|
|
@@ -28,7 +28,7 @@ function createSearchInstance(input, result) {
|
|
|
28
28
|
|
|
29
29
|
result.classList.remove("initially-hidden");
|
|
30
30
|
|
|
31
|
-
const search = new
|
|
31
|
+
const search = new SearchController(search_data, input, result);
|
|
32
32
|
|
|
33
33
|
search.renderItem = function(result) {
|
|
34
34
|
const li = document.createElement('li');
|
|
@@ -40,8 +40,12 @@ function createSearchInstance(input, result) {
|
|
|
40
40
|
html += `<span class="params">${result.params}</span>`;
|
|
41
41
|
html += '</a>';
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
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
|
+
}
|
|
45
49
|
|
|
46
50
|
if (result.snippet)
|
|
47
51
|
html += `<div class="search-snippet">${result.snippet}</div>`;
|
|
@@ -51,16 +55,19 @@ function createSearchInstance(input, result) {
|
|
|
51
55
|
return li;
|
|
52
56
|
}
|
|
53
57
|
|
|
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
|
+
|
|
54
69
|
search.select = function(result) {
|
|
55
|
-
|
|
56
|
-
const query = this.input.value;
|
|
57
|
-
if (query) {
|
|
58
|
-
const url = new URL(href, window.location.origin);
|
|
59
|
-
url.searchParams.set('q', query);
|
|
60
|
-
url.searchParams.set('nav', '0');
|
|
61
|
-
href = url.toString();
|
|
62
|
-
}
|
|
63
|
-
window.location.href = href;
|
|
70
|
+
window.location.href = result.firstChild.firstChild.href;
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
search.scrollIntoView = search.scrollInWindow;
|
|
@@ -70,7 +77,7 @@ function createSearchInstance(input, result) {
|
|
|
70
77
|
|
|
71
78
|
function hookSearch() {
|
|
72
79
|
const input = document.querySelector('#search-field');
|
|
73
|
-
const result = document.querySelector('#search-results');
|
|
80
|
+
const result = document.querySelector('#search-results-desktop');
|
|
74
81
|
|
|
75
82
|
if (!input || !result) return; // Exit if search elements not found
|
|
76
83
|
|
|
@@ -82,15 +89,27 @@ function hookSearch() {
|
|
|
82
89
|
const search = createSearchInstance(input, result);
|
|
83
90
|
if (!search) return;
|
|
84
91
|
|
|
92
|
+
// Hide search results when clicking outside the search area
|
|
93
|
+
document.addEventListener('click', (e) => {
|
|
94
|
+
if (!e.target.closest('.navbar-search-desktop')) {
|
|
95
|
+
search.hide();
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Show search results when focusing on input (if there's a query)
|
|
100
|
+
input.addEventListener('focus', () => {
|
|
101
|
+
if (input.value.trim()) {
|
|
102
|
+
search.show();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
85
106
|
// Check for ?q= URL parameter and trigger search automatically
|
|
86
107
|
if (typeof URLSearchParams !== 'undefined') {
|
|
87
108
|
const urlParams = new URLSearchParams(window.location.search);
|
|
88
109
|
const queryParam = urlParams.get('q');
|
|
89
110
|
if (queryParam) {
|
|
90
|
-
const navParam = urlParams.get('nav');
|
|
91
|
-
const autoSelect = navParam !== '0';
|
|
92
111
|
input.value = queryParam;
|
|
93
|
-
search.search(queryParam,
|
|
112
|
+
search.search(queryParam, false);
|
|
94
113
|
}
|
|
95
114
|
}
|
|
96
115
|
}
|
|
@@ -143,9 +162,12 @@ function hookSidebar() {
|
|
|
143
162
|
});
|
|
144
163
|
|
|
145
164
|
const isSmallViewport = window.matchMedia("(max-width: 1023px)").matches;
|
|
146
|
-
if (isSmallViewport) {
|
|
147
|
-
closeNav();
|
|
148
165
|
|
|
166
|
+
// The sidebar is hidden by default with the `hidden` attribute
|
|
167
|
+
// On large viewports, we display the sidebar with JavaScript
|
|
168
|
+
// This is better than the opposite approach of hiding it with JavaScript
|
|
169
|
+
// because it avoids flickering the sidebar when the page is loaded, especially on mobile devices
|
|
170
|
+
if (isSmallViewport) {
|
|
149
171
|
// Close nav when clicking links inside it
|
|
150
172
|
document.addEventListener('click', (e) => {
|
|
151
173
|
if (e.target.closest('#navigation a')) {
|
|
@@ -161,6 +183,8 @@ function hookSidebar() {
|
|
|
161
183
|
closeNav();
|
|
162
184
|
}
|
|
163
185
|
});
|
|
186
|
+
} else {
|
|
187
|
+
openNav();
|
|
164
188
|
}
|
|
165
189
|
}
|
|
166
190
|
|
|
@@ -363,9 +387,7 @@ function hookSearchModal() {
|
|
|
363
387
|
if (queryParam && isSmallViewport) {
|
|
364
388
|
openSearchModal();
|
|
365
389
|
searchInput.value = queryParam;
|
|
366
|
-
|
|
367
|
-
const autoSelect = navParam !== '0';
|
|
368
|
-
mobileSearch.search(queryParam, autoSelect);
|
|
390
|
+
mobileSearch.search(queryParam, false);
|
|
369
391
|
}
|
|
370
392
|
}
|
|
371
393
|
}
|
|
@@ -276,7 +276,7 @@
|
|
|
276
276
|
* Initialize C syntax highlighting on page load
|
|
277
277
|
*/
|
|
278
278
|
function initHighlighting() {
|
|
279
|
-
const codeBlocks = document.querySelectorAll('pre
|
|
279
|
+
const codeBlocks = document.querySelectorAll('pre.c');
|
|
280
280
|
|
|
281
281
|
codeBlocks.forEach(block => {
|
|
282
282
|
if (block.getAttribute('data-highlighted') === 'true') {
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
SearchController = function(data, input, result) {
|
|
2
2
|
this.data = data;
|
|
3
3
|
this.input = input;
|
|
4
4
|
this.result = result;
|
|
5
5
|
|
|
6
6
|
this.current = null;
|
|
7
7
|
this.view = this.result.parentNode;
|
|
8
|
-
this.
|
|
8
|
+
this.ranker = new SearchRanker(data.index);
|
|
9
9
|
this.init();
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
SearchController.prototype = Object.assign({}, SearchNavigation, new function() {
|
|
13
13
|
var suid = 1;
|
|
14
14
|
|
|
15
15
|
this.init = function() {
|
|
@@ -25,7 +25,7 @@ Search.prototype = Object.assign({}, Navigation, new function() {
|
|
|
25
25
|
this.input.addEventListener('keyup', observer);
|
|
26
26
|
this.input.addEventListener('click', observer); // mac's clear field
|
|
27
27
|
|
|
28
|
-
this.
|
|
28
|
+
this.ranker.ready(function(results, isLast) {
|
|
29
29
|
_this.addResults(results, isLast);
|
|
30
30
|
})
|
|
31
31
|
|
|
@@ -36,7 +36,7 @@ Search.prototype = Object.assign({}, Navigation, new function() {
|
|
|
36
36
|
this.search = function(value, selectFirstMatch) {
|
|
37
37
|
this.selectFirstMatch = selectFirstMatch;
|
|
38
38
|
|
|
39
|
-
value = value.trim()
|
|
39
|
+
value = value.trim();
|
|
40
40
|
if (value) {
|
|
41
41
|
this.setNavigationActive(true);
|
|
42
42
|
} else {
|
|
@@ -53,7 +53,7 @@ Search.prototype = Object.assign({}, Navigation, new function() {
|
|
|
53
53
|
this.result.setAttribute('aria-busy', 'true');
|
|
54
54
|
this.result.setAttribute('aria-expanded', 'true');
|
|
55
55
|
this.firstRun = true;
|
|
56
|
-
this.
|
|
56
|
+
this.ranker.find(value);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -116,5 +116,14 @@ Search.prototype = Object.assign({}, Navigation, new function() {
|
|
|
116
116
|
});
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
this.hide = function() {
|
|
120
|
+
this.result.setAttribute('aria-expanded', 'false');
|
|
121
|
+
this.setNavigationActive(false);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
this.show = function() {
|
|
125
|
+
this.result.setAttribute('aria-expanded', 'true');
|
|
126
|
+
this.setNavigationActive(true);
|
|
127
|
+
}
|
|
119
128
|
});
|
|
120
129
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SearchNavigation 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
|
+
SearchNavigation = new function() {
|
|
10
|
+
this.initNavigation = function() {
|
|
11
|
+
var _this = this;
|
|
12
|
+
|
|
13
|
+
document.addEventListener('keydown', function(e) {
|
|
14
|
+
_this.onkeydown(e);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
this.navigationActive = true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
this.setNavigationActive = function(state) {
|
|
21
|
+
this.navigationActive = state;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
this.onkeydown = function(e) {
|
|
25
|
+
if (!this.navigationActive) return;
|
|
26
|
+
switch(e.key) {
|
|
27
|
+
case 'ArrowLeft':
|
|
28
|
+
if (this.moveLeft()) e.preventDefault();
|
|
29
|
+
break;
|
|
30
|
+
case 'ArrowUp':
|
|
31
|
+
if (e.key == 'ArrowUp' || e.ctrlKey) {
|
|
32
|
+
if (this.moveUp()) e.preventDefault();
|
|
33
|
+
}
|
|
34
|
+
break;
|
|
35
|
+
case 'ArrowRight':
|
|
36
|
+
if (this.moveRight()) e.preventDefault();
|
|
37
|
+
break;
|
|
38
|
+
case 'ArrowDown':
|
|
39
|
+
if (e.key == 'ArrowDown' || e.ctrlKey) {
|
|
40
|
+
if (this.moveDown()) e.preventDefault();
|
|
41
|
+
}
|
|
42
|
+
break;
|
|
43
|
+
case 'Enter':
|
|
44
|
+
if (this.current) e.preventDefault();
|
|
45
|
+
this.select(this.current);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
if (e.ctrlKey && e.shiftKey) this.select(this.current);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.moveRight = function() {
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.moveLeft = function() {
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.move = function(isDown) {
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.moveUp = function() {
|
|
61
|
+
return this.move(false);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.moveDown = function() {
|
|
65
|
+
return this.move(true);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/*
|
|
69
|
+
* Scrolls to the given element in the scrollable element view.
|
|
70
|
+
*/
|
|
71
|
+
this.scrollInElement = function(element, view) {
|
|
72
|
+
var offset, viewHeight, viewScroll, height;
|
|
73
|
+
offset = element.offsetTop;
|
|
74
|
+
height = element.offsetHeight;
|
|
75
|
+
viewHeight = view.offsetHeight;
|
|
76
|
+
viewScroll = view.scrollTop;
|
|
77
|
+
|
|
78
|
+
if (offset - viewScroll + height > viewHeight) {
|
|
79
|
+
view.scrollTop = offset - viewHeight + height;
|
|
80
|
+
}
|
|
81
|
+
if (offset < viewScroll) {
|
|
82
|
+
view.scrollTop = offset;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/*
|
|
87
|
+
* Scrolls to the given element in the window. The second argument is
|
|
88
|
+
* ignored
|
|
89
|
+
*/
|
|
90
|
+
this.scrollInWindow = function(element, ignored) {
|
|
91
|
+
var offset, viewHeight, viewScroll, height;
|
|
92
|
+
offset = element.offsetTop;
|
|
93
|
+
height = element.offsetHeight;
|
|
94
|
+
viewHeight = window.innerHeight;
|
|
95
|
+
viewScroll = window.scrollY;
|
|
96
|
+
|
|
97
|
+
if (offset - viewScroll + height > viewHeight) {
|
|
98
|
+
window.scrollTo(window.scrollX, offset - viewHeight + height);
|
|
99
|
+
}
|
|
100
|
+
if (offset < viewScroll) {
|
|
101
|
+
window.scrollTo(window.scrollX, offset);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aliki Search Implementation
|
|
3
|
+
*
|
|
4
|
+
* Search algorithm with the following priorities:
|
|
5
|
+
* 1. Exact full_name match always wins (for namespace/method queries)
|
|
6
|
+
* 2. Exact name match gets high priority
|
|
7
|
+
* 3. Match types:
|
|
8
|
+
* - Namespace queries (::) and method queries (# or .) match against full_name
|
|
9
|
+
* - Regular queries match against unqualified name
|
|
10
|
+
* - Prefix (10000) > substring (5000) > fuzzy (1000)
|
|
11
|
+
* 4. First character determines type priority:
|
|
12
|
+
* - Starts with lowercase: methods first
|
|
13
|
+
* - Starts with uppercase: classes/modules/constants first
|
|
14
|
+
* 5. Within same type priority:
|
|
15
|
+
* - Unqualified match > qualified match
|
|
16
|
+
* - Shorter name > longer name
|
|
17
|
+
* 6. Class methods > instance methods
|
|
18
|
+
* 7. Result limit: 30
|
|
19
|
+
* 8. Minimum query length: 1 character
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
var MAX_RESULTS = 30;
|
|
23
|
+
var MIN_QUERY_LENGTH = 1;
|
|
24
|
+
|
|
25
|
+
/*
|
|
26
|
+
* Scoring constants - organized in tiers where each tier dominates lower tiers.
|
|
27
|
+
* This ensures match type always beats type priority, etc.
|
|
28
|
+
*
|
|
29
|
+
* Tier 0: Exact matches (immediate return)
|
|
30
|
+
* Tier 1: Match type (prefix > substring > fuzzy)
|
|
31
|
+
* Tier 2: Exact name bonus
|
|
32
|
+
* Tier 3: Type priority (method vs class based on query case)
|
|
33
|
+
* Tier 4: Minor bonuses (top-level, class method, name length)
|
|
34
|
+
*/
|
|
35
|
+
var SCORE_EXACT_FULL_NAME = 1000000; // Tier 0: Query exactly matches full_name
|
|
36
|
+
var SCORE_MATCH_PREFIX = 10000; // Tier 1: Query is prefix of name
|
|
37
|
+
var SCORE_MATCH_SUBSTRING = 5000; // Tier 1: Query is substring of name
|
|
38
|
+
var SCORE_MATCH_FUZZY = 1000; // Tier 1: Query chars appear in order
|
|
39
|
+
var SCORE_EXACT_NAME = 500; // Tier 2: Name exactly equals query
|
|
40
|
+
var SCORE_TYPE_PRIORITY = 100; // Tier 3: Preferred type (method/class)
|
|
41
|
+
var SCORE_TOP_LEVEL = 50; // Tier 4: Top-level over namespaced
|
|
42
|
+
var SCORE_CLASS_METHOD = 10; // Tier 4: Class method over instance method
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if all characters in query appear in order in target
|
|
46
|
+
* e.g., "addalias" fuzzy matches "add_foo_alias"
|
|
47
|
+
*/
|
|
48
|
+
function fuzzyMatch(target, query) {
|
|
49
|
+
var ti = 0;
|
|
50
|
+
for (var qi = 0; qi < query.length; qi++) {
|
|
51
|
+
ti = target.indexOf(query[qi], ti);
|
|
52
|
+
if (ti === -1) return false;
|
|
53
|
+
ti++;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Parse and normalize a search query
|
|
60
|
+
* @param {string} query - The raw search query
|
|
61
|
+
* @returns {Object} Parsed query with normalized form and flags
|
|
62
|
+
*/
|
|
63
|
+
function parseQuery(query) {
|
|
64
|
+
// Lowercase for case-insensitive matching (so "hash" finds both Hash class and #hash methods)
|
|
65
|
+
var normalized = query.toLowerCase();
|
|
66
|
+
var isNamespaceQuery = query.includes('::');
|
|
67
|
+
var isMethodQuery = query.includes('#') || query.includes('.');
|
|
68
|
+
|
|
69
|
+
// Normalize . to :: (RDoc uses :: for class methods in full_name)
|
|
70
|
+
if (query.includes('.')) {
|
|
71
|
+
normalized = normalized.replace(/\./g, '::');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
original: query,
|
|
76
|
+
normalized: normalized,
|
|
77
|
+
isNamespaceQuery: isNamespaceQuery,
|
|
78
|
+
isMethodQuery: isMethodQuery,
|
|
79
|
+
// Namespace and method queries match against full_name instead of name
|
|
80
|
+
matchesFullName: isNamespaceQuery || isMethodQuery,
|
|
81
|
+
// If query starts with lowercase, prioritize methods; otherwise prioritize classes/modules/constants
|
|
82
|
+
prioritizeMethod: !/^[A-Z]/.test(query)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Main search function
|
|
88
|
+
* @param {string} query - The search query
|
|
89
|
+
* @param {Array} index - The search index to search in
|
|
90
|
+
* @returns {Array} Array of matching entries, sorted by relevance
|
|
91
|
+
*/
|
|
92
|
+
function search(query, index) {
|
|
93
|
+
if (!query || query.length < MIN_QUERY_LENGTH) {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
var q = parseQuery(query);
|
|
98
|
+
var results = [];
|
|
99
|
+
|
|
100
|
+
for (var i = 0; i < index.length; i++) {
|
|
101
|
+
var entry = index[i];
|
|
102
|
+
var score = computeScore(entry, q);
|
|
103
|
+
|
|
104
|
+
if (score !== null) {
|
|
105
|
+
results.push({ entry: entry, score: score });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
results.sort(function(a, b) {
|
|
110
|
+
return b.score - a.score;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return results.slice(0, MAX_RESULTS).map(function(r) {
|
|
114
|
+
return r.entry;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Compute the relevance score for an entry
|
|
120
|
+
* @param {Object} entry - The search index entry
|
|
121
|
+
* @param {Object} q - Parsed query from parseQuery()
|
|
122
|
+
* @returns {number|null} Score or null if no match
|
|
123
|
+
*/
|
|
124
|
+
function computeScore(entry, q) {
|
|
125
|
+
var name = entry.name;
|
|
126
|
+
var fullName = entry.full_name;
|
|
127
|
+
var type = entry.type;
|
|
128
|
+
|
|
129
|
+
var nameLower = name.toLowerCase();
|
|
130
|
+
var fullNameLower = fullName.toLowerCase();
|
|
131
|
+
|
|
132
|
+
// Exact full_name match (e.g., "Array#filter" matches Array#filter)
|
|
133
|
+
if (q.matchesFullName && fullNameLower === q.normalized) {
|
|
134
|
+
return SCORE_EXACT_FULL_NAME;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
var matchScore = 0;
|
|
138
|
+
var target = q.matchesFullName ? fullNameLower : nameLower;
|
|
139
|
+
|
|
140
|
+
if (target.startsWith(q.normalized)) {
|
|
141
|
+
matchScore = SCORE_MATCH_PREFIX; // Prefix (e.g., "Arr" matches "Array")
|
|
142
|
+
} else if (target.includes(q.normalized)) {
|
|
143
|
+
matchScore = SCORE_MATCH_SUBSTRING; // Substring (e.g., "ray" matches "Array")
|
|
144
|
+
} else if (fuzzyMatch(target, q.normalized)) {
|
|
145
|
+
matchScore = SCORE_MATCH_FUZZY; // Fuzzy (e.g., "addalias" matches "add_foo_alias")
|
|
146
|
+
} else {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
var score = matchScore;
|
|
151
|
+
var isMethod = (type === 'instance_method' || type === 'class_method');
|
|
152
|
+
|
|
153
|
+
if (q.prioritizeMethod ? isMethod : !isMethod) {
|
|
154
|
+
score += SCORE_TYPE_PRIORITY;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (type === 'class_method') score += SCORE_CLASS_METHOD;
|
|
158
|
+
if (name === fullName) score += SCORE_TOP_LEVEL; // Top-level (Hash) > namespaced (Foo::Hash)
|
|
159
|
+
if (nameLower === q.normalized) score += SCORE_EXACT_NAME; // Exact name match
|
|
160
|
+
score -= name.length;
|
|
161
|
+
|
|
162
|
+
return score;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* SearchRanker class for compatibility with the Search UI
|
|
167
|
+
* Provides ready() and find() interface
|
|
168
|
+
*/
|
|
169
|
+
function SearchRanker(index) {
|
|
170
|
+
this.index = index;
|
|
171
|
+
this.handlers = [];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
SearchRanker.prototype.ready = function(fn) {
|
|
175
|
+
this.handlers.push(fn);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
SearchRanker.prototype.find = function(query) {
|
|
179
|
+
var q = parseQuery(query);
|
|
180
|
+
var rawResults = search(query, this.index);
|
|
181
|
+
var results = rawResults.map(function(entry) {
|
|
182
|
+
return formatResult(entry, q);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
var _this = this;
|
|
186
|
+
this.handlers.forEach(function(fn) {
|
|
187
|
+
fn.call(_this, results, true);
|
|
188
|
+
});
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Format a search result entry for display
|
|
193
|
+
*/
|
|
194
|
+
function formatResult(entry, q) {
|
|
195
|
+
var result = {
|
|
196
|
+
title: highlightMatch(entry.full_name, q),
|
|
197
|
+
path: entry.path,
|
|
198
|
+
type: entry.type
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
if (entry.snippet) {
|
|
202
|
+
result.snippet = entry.snippet;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Add highlight markers (\u0001 and \u0002) to matching portions of text
|
|
210
|
+
* @param {string} text - The text to highlight
|
|
211
|
+
* @param {Object} q - Parsed query from parseQuery()
|
|
212
|
+
*/
|
|
213
|
+
function highlightMatch(text, q) {
|
|
214
|
+
if (!text || !q) return text;
|
|
215
|
+
|
|
216
|
+
var textLower = text.toLowerCase();
|
|
217
|
+
var query = q.normalized;
|
|
218
|
+
|
|
219
|
+
// Try contiguous match first (prefix or substring)
|
|
220
|
+
var matchIndex = textLower.indexOf(query);
|
|
221
|
+
if (matchIndex !== -1) {
|
|
222
|
+
return text.substring(0, matchIndex) +
|
|
223
|
+
'\u0001' + text.substring(matchIndex, matchIndex + query.length) + '\u0002' +
|
|
224
|
+
text.substring(matchIndex + query.length);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Fall back to fuzzy highlight (highlight each matched character)
|
|
228
|
+
var result = '';
|
|
229
|
+
var ti = 0;
|
|
230
|
+
for (var qi = 0; qi < query.length; qi++) {
|
|
231
|
+
var charIndex = textLower.indexOf(query[qi], ti);
|
|
232
|
+
if (charIndex === -1) return text;
|
|
233
|
+
result += text.substring(ti, charIndex);
|
|
234
|
+
result += '\u0001' + text[charIndex] + '\u0002';
|
|
235
|
+
ti = charIndex + 1;
|
|
236
|
+
}
|
|
237
|
+
result += text.substring(ti);
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<body role="document" class="file has-toc">
|
|
2
|
+
<%= render '_icons.rhtml' %>
|
|
2
3
|
<%= render '_header.rhtml' %>
|
|
3
4
|
<%= render '_sidebar_toggle.rhtml' %>
|
|
4
5
|
|
|
5
|
-
<nav id="navigation" role="navigation">
|
|
6
|
+
<nav id="navigation" role="navigation" hidden>
|
|
6
7
|
<%= render '_sidebar_pages.rhtml' %>
|
|
7
8
|
<%= render '_sidebar_classes.rhtml' %>
|
|
8
9
|
</nav>
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
</ol>
|
|
34
34
|
<% end %>
|
|
35
35
|
|
|
36
|
+
<span id="<%= h klass.legacy_aref %>" class="legacy-anchor"></span>
|
|
36
37
|
<h1 id="<%= h klass.aref %>" class="anchor-link <%= klass.type %>">
|
|
37
38
|
<%= klass.type %> <%= klass.full_name %>
|
|
38
39
|
</h1>
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
</section>
|
|
43
44
|
|
|
44
45
|
<%- klass.each_section do |section, constants, attributes| %>
|
|
46
|
+
<span id="<%= section.legacy_aref %>" class="legacy-anchor"></span>
|
|
45
47
|
<section id="<%= section.aref %>" class="documentation-section anchor-link">
|
|
46
48
|
<%- if section.title then %>
|
|
47
49
|
<header class="documentation-section-title">
|
|
@@ -117,10 +119,10 @@
|
|
|
117
119
|
</section>
|
|
118
120
|
<%- end %>
|
|
119
121
|
|
|
120
|
-
<%- klass.methods_by_type(section).each do |type, visibilities|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
<%- klass.methods_by_type(section).each do |type, visibilities| %>
|
|
123
|
+
<%- next if visibilities.empty? %>
|
|
124
|
+
<%- visibilities.each do |visibility, methods| %>
|
|
125
|
+
<%- next if methods.empty? %>
|
|
124
126
|
<section id="<%= visibility %>-<%= type %>-<%= section.aref %>-method-details" class="method-section anchor-link">
|
|
125
127
|
<header>
|
|
126
128
|
<h3><%= visibility.to_s.capitalize %> <%= type.capitalize %> Methods</h3>
|
|
@@ -163,15 +165,13 @@
|
|
|
163
165
|
<summary>Source</summary>
|
|
164
166
|
</details>
|
|
165
167
|
</div>
|
|
168
|
+
<div class="method-source-code" id="<%= method.html_name %>-source">
|
|
169
|
+
<pre><%= method.markup_code %></pre>
|
|
170
|
+
</div>
|
|
166
171
|
<%- end %>
|
|
167
172
|
|
|
168
173
|
<%- unless method.skip_description? then %>
|
|
169
174
|
<div class="method-description">
|
|
170
|
-
<%- if method.token_stream then %>
|
|
171
|
-
<div class="method-source-code" id="<%= method.html_name %>-source">
|
|
172
|
-
<pre><%= method.markup_code %></pre>
|
|
173
|
-
</div>
|
|
174
|
-
<%- end %>
|
|
175
175
|
<%- if method.mixin_from then %>
|
|
176
176
|
<div class="mixin-from">
|
|
177
177
|
<%= method.singleton ? "Extended" : "Included" %> from <a href="<%= klass.aref_to(method.mixin_from.path) %>"><%= method.mixin_from.full_name %></a>
|
|
@@ -215,8 +215,8 @@
|
|
|
215
215
|
|
|
216
216
|
<%- end %>
|
|
217
217
|
</section>
|
|
218
|
-
<%- end
|
|
219
|
-
|
|
218
|
+
<%- end %>
|
|
219
|
+
<%- end %>
|
|
220
220
|
</section>
|
|
221
221
|
<%- end %>
|
|
222
222
|
</main>
|
|
@@ -92,6 +92,25 @@ main .anchor-link:target {
|
|
|
92
92
|
scroll-margin-top: 1rem;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
/* Legacy anchor for backward compatibility with old label- prefix links */
|
|
96
|
+
.legacy-anchor {
|
|
97
|
+
display: block;
|
|
98
|
+
position: relative;
|
|
99
|
+
visibility: hidden;
|
|
100
|
+
scroll-margin-top: 1rem;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* When a legacy anchor is targeted, highlight the next heading sibling */
|
|
104
|
+
.legacy-anchor:target + h1,
|
|
105
|
+
.legacy-anchor:target + h2,
|
|
106
|
+
.legacy-anchor:target + h3,
|
|
107
|
+
.legacy-anchor:target + h4,
|
|
108
|
+
.legacy-anchor:target + h5,
|
|
109
|
+
.legacy-anchor:target + h6 {
|
|
110
|
+
margin-left: -10px;
|
|
111
|
+
border-left: 10px solid var(--border-color);
|
|
112
|
+
}
|
|
113
|
+
|
|
95
114
|
/* 4. Links */
|
|
96
115
|
a {
|
|
97
116
|
color: var(--link-color);
|
data/lib/rdoc/markdown.kpeg
CHANGED