rdoc 6.15.1 → 6.16.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/History.rdoc +1 -1
- data/lib/rdoc/code_object/top_level.rb +18 -17
- data/lib/rdoc/comment.rb +190 -8
- data/lib/rdoc/generator/aliki.rb +42 -0
- data/lib/rdoc/generator/template/aliki/_aside_toc.rhtml +8 -0
- data/lib/rdoc/generator/template/aliki/_footer.rhtml +23 -0
- data/lib/rdoc/generator/template/aliki/_head.rhtml +91 -0
- data/lib/rdoc/generator/template/aliki/_header.rhtml +56 -0
- data/lib/rdoc/generator/template/aliki/_sidebar_ancestors.rhtml +6 -0
- data/lib/rdoc/generator/template/aliki/_sidebar_classes.rhtml +5 -0
- data/lib/rdoc/generator/template/aliki/_sidebar_extends.rhtml +15 -0
- data/lib/rdoc/generator/template/aliki/_sidebar_includes.rhtml +15 -0
- data/lib/rdoc/generator/template/aliki/_sidebar_installed.rhtml +16 -0
- data/lib/rdoc/generator/template/aliki/_sidebar_methods.rhtml +21 -0
- data/lib/rdoc/generator/template/aliki/_sidebar_pages.rhtml +37 -0
- data/lib/rdoc/generator/template/aliki/_sidebar_search.rhtml +15 -0
- data/lib/rdoc/generator/template/aliki/_sidebar_sections.rhtml +11 -0
- data/lib/rdoc/generator/template/aliki/_sidebar_toggle.rhtml +3 -0
- data/lib/rdoc/generator/template/aliki/class.rhtml +219 -0
- data/lib/rdoc/generator/template/aliki/css/rdoc.css +1612 -0
- data/lib/rdoc/generator/template/aliki/index.rhtml +21 -0
- data/lib/rdoc/generator/template/aliki/js/aliki.js +483 -0
- data/lib/rdoc/generator/template/aliki/js/c_highlighter.js +299 -0
- data/lib/rdoc/generator/template/aliki/js/search.js +120 -0
- data/lib/rdoc/generator/template/aliki/js/theme-toggle.js +112 -0
- data/lib/rdoc/generator/template/aliki/page.rhtml +17 -0
- data/lib/rdoc/generator/template/aliki/servlet_not_found.rhtml +14 -0
- data/lib/rdoc/generator/template/aliki/servlet_root.rhtml +65 -0
- data/lib/rdoc/generator/template/darkfish/_head.rhtml +2 -7
- data/lib/rdoc/generator/template/darkfish/_sidebar_search.rhtml +1 -0
- data/lib/rdoc/generator/template/darkfish/table_of_contents.rhtml +1 -1
- data/lib/rdoc/generator/template/json_index/js/searcher.js +5 -1
- data/lib/rdoc/generator.rb +1 -0
- data/lib/rdoc/markup/pre_process.rb +34 -10
- data/lib/rdoc/markup/to_html.rb +6 -4
- data/lib/rdoc/options.rb +21 -10
- data/lib/rdoc/parser/c.rb +15 -46
- data/lib/rdoc/parser/prism_ruby.rb +121 -113
- data/lib/rdoc/parser/ruby.rb +8 -8
- data/lib/rdoc/parser/ruby_tools.rb +5 -7
- data/lib/rdoc/parser/simple.rb +4 -21
- data/lib/rdoc/rdoc.rb +1 -0
- data/lib/rdoc/text.rb +1 -1
- data/lib/rdoc/token_stream.rb +13 -1
- data/lib/rdoc/tom_doc.rb +1 -1
- data/lib/rdoc/version.rb +1 -1
- metadata +27 -2
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side C syntax highlighter for RDoc
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
(function() {
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
// C control flow and storage class keywords
|
|
9
|
+
const C_KEYWORDS = new Set([
|
|
10
|
+
'auto', 'break', 'case', 'continue', 'default', 'do', 'else', 'extern',
|
|
11
|
+
'for', 'goto', 'if', 'inline', 'register', 'return', 'sizeof', 'static',
|
|
12
|
+
'switch', 'while',
|
|
13
|
+
'_Alignas', '_Alignof', '_Generic', '_Noreturn', '_Static_assert', '_Thread_local'
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
// C type keywords and type qualifiers
|
|
17
|
+
const C_TYPE_KEYWORDS = new Set([
|
|
18
|
+
'bool', 'char', 'const', 'double', 'enum', 'float', 'int', 'long',
|
|
19
|
+
'restrict', 'short', 'signed', 'struct', 'typedef', 'union', 'unsigned',
|
|
20
|
+
'void', 'volatile', '_Atomic', '_Bool', '_Complex', '_Imaginary'
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
// Library-defined types (typedef'd in headers, not language keywords)
|
|
24
|
+
// Includes: Ruby C API types (VALUE, ID), POSIX types (size_t, ssize_t),
|
|
25
|
+
// fixed-width integer types (uint32_t, int64_t), and standard I/O types (FILE)
|
|
26
|
+
const C_TYPES = new Set([
|
|
27
|
+
'VALUE', 'ID', 'size_t', 'ssize_t', 'ptrdiff_t', 'uintptr_t', 'intptr_t',
|
|
28
|
+
'uint8_t', 'uint16_t', 'uint32_t', 'uint64_t',
|
|
29
|
+
'int8_t', 'int16_t', 'int32_t', 'int64_t',
|
|
30
|
+
'FILE', 'DIR', 'va_list'
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
// Common Ruby VALUE macros and boolean literals
|
|
34
|
+
const RUBY_MACROS = new Set([
|
|
35
|
+
'Qtrue', 'Qfalse', 'Qnil', 'Qundef', 'NULL', 'TRUE', 'FALSE', 'true', 'false'
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
const OPERATORS = new Set([
|
|
39
|
+
'==', '!=', '<=', '>=', '&&', '||', '<<', '>>', '++', '--',
|
|
40
|
+
'+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '->',
|
|
41
|
+
'+', '-', '*', '/', '%', '<', '>', '=', '!', '&', '|', '^', '~'
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
// Single character that can start an operator
|
|
45
|
+
const OPERATOR_CHARS = new Set('+-*/%<>=!&|^~');
|
|
46
|
+
|
|
47
|
+
function isMacro(word) {
|
|
48
|
+
return RUBY_MACROS.has(word) || /^[A-Z][A-Z0-9_]*$/.test(word);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isType(word) {
|
|
52
|
+
return C_TYPE_KEYWORDS.has(word) || C_TYPES.has(word) || /_t$/.test(word);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Escape HTML special characters
|
|
57
|
+
*/
|
|
58
|
+
function escapeHtml(text) {
|
|
59
|
+
return text
|
|
60
|
+
.replace(/&/g, '&')
|
|
61
|
+
.replace(/</g, '<')
|
|
62
|
+
.replace(/>/g, '>')
|
|
63
|
+
.replace(/"/g, '"')
|
|
64
|
+
.replace(/'/g, ''');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Check if position is at line start (only whitespace before it)
|
|
69
|
+
*/
|
|
70
|
+
function isLineStart(code, pos) {
|
|
71
|
+
if (pos === 0) return true;
|
|
72
|
+
for (let i = pos - 1; i >= 0; i--) {
|
|
73
|
+
const ch = code[i];
|
|
74
|
+
if (ch === '\n') return true;
|
|
75
|
+
if (ch !== ' ' && ch !== '\t') return false;
|
|
76
|
+
}
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Highlight C source code
|
|
82
|
+
*/
|
|
83
|
+
function highlightC(code) {
|
|
84
|
+
const tokens = [];
|
|
85
|
+
let i = 0;
|
|
86
|
+
const len = code.length;
|
|
87
|
+
|
|
88
|
+
while (i < len) {
|
|
89
|
+
const char = code[i];
|
|
90
|
+
|
|
91
|
+
// Multi-line comment
|
|
92
|
+
if (char === '/' && code[i + 1] === '*') {
|
|
93
|
+
let end = code.indexOf('*/', i + 2);
|
|
94
|
+
end = (end === -1) ? len : end + 2;
|
|
95
|
+
const comment = code.substring(i, end);
|
|
96
|
+
tokens.push('<span class="c-comment">', escapeHtml(comment), '</span>');
|
|
97
|
+
i = end;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Single-line comment
|
|
102
|
+
if (char === '/' && code[i + 1] === '/') {
|
|
103
|
+
const end = code.indexOf('\n', i);
|
|
104
|
+
const commentEnd = (end === -1) ? len : end;
|
|
105
|
+
const comment = code.substring(i, commentEnd);
|
|
106
|
+
tokens.push('<span class="c-comment">', escapeHtml(comment), '</span>');
|
|
107
|
+
i = commentEnd;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Preprocessor directive (must be at line start)
|
|
112
|
+
if (char === '#' && isLineStart(code, i)) {
|
|
113
|
+
let end = i + 1;
|
|
114
|
+
while (end < len && code[end] !== '\n') {
|
|
115
|
+
if (code[end] === '\\' && end + 1 < len && code[end + 1] === '\n') {
|
|
116
|
+
end += 2; // Handle line continuation
|
|
117
|
+
} else {
|
|
118
|
+
end++;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const preprocessor = code.substring(i, end);
|
|
122
|
+
tokens.push('<span class="c-preprocessor">', escapeHtml(preprocessor), '</span>');
|
|
123
|
+
i = end;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// String literal
|
|
128
|
+
if (char === '"') {
|
|
129
|
+
let end = i + 1;
|
|
130
|
+
while (end < len && code[end] !== '"') {
|
|
131
|
+
if (code[end] === '\\' && end + 1 < len) {
|
|
132
|
+
end += 2; // Skip escaped character
|
|
133
|
+
} else {
|
|
134
|
+
end++;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (end < len) end++; // Include closing quote
|
|
138
|
+
const string = code.substring(i, end);
|
|
139
|
+
tokens.push('<span class="c-string">', escapeHtml(string), '</span>');
|
|
140
|
+
i = end;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Character literal
|
|
145
|
+
if (char === "'") {
|
|
146
|
+
let end = i + 1;
|
|
147
|
+
// Handle escape sequences like '\n', '\\', '\''
|
|
148
|
+
if (end < len && code[end] === '\\' && end + 1 < len) {
|
|
149
|
+
end += 2; // Skip backslash and escaped char
|
|
150
|
+
} else if (end < len) {
|
|
151
|
+
end++; // Single character
|
|
152
|
+
}
|
|
153
|
+
if (end < len && code[end] === "'") end++; // Closing quote
|
|
154
|
+
const charLit = code.substring(i, end);
|
|
155
|
+
tokens.push('<span class="c-value">', escapeHtml(charLit), '</span>');
|
|
156
|
+
i = end;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Number (integer or float)
|
|
161
|
+
if (char >= '0' && char <= '9') {
|
|
162
|
+
let end = i;
|
|
163
|
+
|
|
164
|
+
// Hexadecimal
|
|
165
|
+
if (char === '0' && (code[i + 1] === 'x' || code[i + 1] === 'X')) {
|
|
166
|
+
end = i + 2;
|
|
167
|
+
while (end < len) {
|
|
168
|
+
const ch = code[end];
|
|
169
|
+
if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) {
|
|
170
|
+
end++;
|
|
171
|
+
} else {
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Octal
|
|
177
|
+
else if (char === '0' && code[i + 1] >= '0' && code[i + 1] <= '7') {
|
|
178
|
+
end = i + 1;
|
|
179
|
+
while (end < len && code[end] >= '0' && code[end] <= '7') end++;
|
|
180
|
+
}
|
|
181
|
+
// Decimal/Float
|
|
182
|
+
else {
|
|
183
|
+
while (end < len) {
|
|
184
|
+
const ch = code[end];
|
|
185
|
+
if ((ch >= '0' && ch <= '9') || ch === '.') {
|
|
186
|
+
end++;
|
|
187
|
+
} else {
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Scientific notation
|
|
192
|
+
if (end < len && (code[end] === 'e' || code[end] === 'E')) {
|
|
193
|
+
end++;
|
|
194
|
+
if (end < len && (code[end] === '+' || code[end] === '-')) end++;
|
|
195
|
+
while (end < len && code[end] >= '0' && code[end] <= '9') end++;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Suffix (u, l, f, etc.)
|
|
200
|
+
while (end < len) {
|
|
201
|
+
const ch = code[end];
|
|
202
|
+
if (ch === 'u' || ch === 'U' || ch === 'l' || ch === 'L' || ch === 'f' || ch === 'F') {
|
|
203
|
+
end++;
|
|
204
|
+
} else {
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const number = code.substring(i, end);
|
|
210
|
+
tokens.push('<span class="c-value">', escapeHtml(number), '</span>');
|
|
211
|
+
i = end;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Identifier or keyword
|
|
216
|
+
if ((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || char === '_') {
|
|
217
|
+
let end = i + 1;
|
|
218
|
+
while (end < len) {
|
|
219
|
+
const ch = code[end];
|
|
220
|
+
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
|
|
221
|
+
(ch >= '0' && ch <= '9') || ch === '_') {
|
|
222
|
+
end++;
|
|
223
|
+
} else {
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const word = code.substring(i, end);
|
|
228
|
+
|
|
229
|
+
if (C_KEYWORDS.has(word)) {
|
|
230
|
+
tokens.push('<span class="c-keyword">', escapeHtml(word), '</span>');
|
|
231
|
+
} else if (isType(word)) {
|
|
232
|
+
// Check types before macros (VALUE, ID are types, not macros)
|
|
233
|
+
tokens.push('<span class="c-type">', escapeHtml(word), '</span>');
|
|
234
|
+
} else if (isMacro(word)) {
|
|
235
|
+
tokens.push('<span class="c-macro">', escapeHtml(word), '</span>');
|
|
236
|
+
} else {
|
|
237
|
+
// Check if followed by '(' -> function name
|
|
238
|
+
let nextCharIdx = end;
|
|
239
|
+
while (nextCharIdx < len && (code[nextCharIdx] === ' ' || code[nextCharIdx] === '\t')) {
|
|
240
|
+
nextCharIdx++;
|
|
241
|
+
}
|
|
242
|
+
if (nextCharIdx < len && code[nextCharIdx] === '(') {
|
|
243
|
+
tokens.push('<span class="c-function">', escapeHtml(word), '</span>');
|
|
244
|
+
} else {
|
|
245
|
+
tokens.push('<span class="c-identifier">', escapeHtml(word), '</span>');
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
i = end;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Operators
|
|
253
|
+
if (OPERATOR_CHARS.has(char)) {
|
|
254
|
+
let op = char;
|
|
255
|
+
// Check for two-character operators
|
|
256
|
+
if (i + 1 < len) {
|
|
257
|
+
const twoChar = char + code[i + 1];
|
|
258
|
+
if (OPERATORS.has(twoChar)) {
|
|
259
|
+
op = twoChar;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
tokens.push('<span class="c-operator">', escapeHtml(op), '</span>');
|
|
263
|
+
i += op.length;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Everything else (punctuation, whitespace)
|
|
268
|
+
tokens.push(escapeHtml(char));
|
|
269
|
+
i++;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return tokens.join('');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Initialize C syntax highlighting on page load
|
|
277
|
+
*/
|
|
278
|
+
function initHighlighting() {
|
|
279
|
+
const codeBlocks = document.querySelectorAll('pre[data-language="c"]');
|
|
280
|
+
|
|
281
|
+
codeBlocks.forEach(block => {
|
|
282
|
+
if (block.getAttribute('data-highlighted') === 'true') {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const code = block.textContent;
|
|
287
|
+
const highlighted = highlightC(code);
|
|
288
|
+
|
|
289
|
+
block.innerHTML = highlighted;
|
|
290
|
+
block.setAttribute('data-highlighted', 'true');
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (document.readyState === 'loading') {
|
|
295
|
+
document.addEventListener('DOMContentLoaded', initHighlighting);
|
|
296
|
+
} else {
|
|
297
|
+
initHighlighting();
|
|
298
|
+
}
|
|
299
|
+
})();
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Search = function(data, input, result) {
|
|
2
|
+
this.data = data;
|
|
3
|
+
this.input = input;
|
|
4
|
+
this.result = result;
|
|
5
|
+
|
|
6
|
+
this.current = null;
|
|
7
|
+
this.view = this.result.parentNode;
|
|
8
|
+
this.searcher = new Searcher(data.index);
|
|
9
|
+
this.init();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
Search.prototype = Object.assign({}, Navigation, new function() {
|
|
13
|
+
var suid = 1;
|
|
14
|
+
|
|
15
|
+
this.init = function() {
|
|
16
|
+
var _this = this;
|
|
17
|
+
var observer = function(e) {
|
|
18
|
+
switch(e.key) {
|
|
19
|
+
case 'ArrowUp':
|
|
20
|
+
case 'ArrowDown':
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
_this.search(_this.input.value);
|
|
24
|
+
};
|
|
25
|
+
this.input.addEventListener('keyup', observer);
|
|
26
|
+
this.input.addEventListener('click', observer); // mac's clear field
|
|
27
|
+
|
|
28
|
+
this.searcher.ready(function(results, isLast) {
|
|
29
|
+
_this.addResults(results, isLast);
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
this.initNavigation();
|
|
33
|
+
this.setNavigationActive(false);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.search = function(value, selectFirstMatch) {
|
|
37
|
+
this.selectFirstMatch = selectFirstMatch;
|
|
38
|
+
|
|
39
|
+
value = value.trim().toLowerCase();
|
|
40
|
+
if (value) {
|
|
41
|
+
this.setNavigationActive(true);
|
|
42
|
+
} else {
|
|
43
|
+
this.setNavigationActive(false);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (value == '') {
|
|
47
|
+
this.lastQuery = value;
|
|
48
|
+
this.result.innerHTML = '';
|
|
49
|
+
this.result.setAttribute('aria-expanded', 'false');
|
|
50
|
+
this.setNavigationActive(false);
|
|
51
|
+
} else if (value != this.lastQuery) {
|
|
52
|
+
this.lastQuery = value;
|
|
53
|
+
this.result.setAttribute('aria-busy', 'true');
|
|
54
|
+
this.result.setAttribute('aria-expanded', 'true');
|
|
55
|
+
this.firstRun = true;
|
|
56
|
+
this.searcher.find(value);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.addResults = function(results, isLast) {
|
|
61
|
+
var target = this.result;
|
|
62
|
+
if (this.firstRun && (results.length > 0 || isLast)) {
|
|
63
|
+
this.current = null;
|
|
64
|
+
this.result.innerHTML = '';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (var i=0, l = results.length; i < l; i++) {
|
|
68
|
+
var item = this.renderItem.call(this, results[i]);
|
|
69
|
+
item.setAttribute('id', 'search-result-' + target.childElementCount);
|
|
70
|
+
target.appendChild(item);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (this.firstRun && results.length > 0) {
|
|
74
|
+
this.firstRun = false;
|
|
75
|
+
this.current = target.firstChild;
|
|
76
|
+
this.current.classList.add('search-selected');
|
|
77
|
+
}
|
|
78
|
+
//TODO: ECMAScript
|
|
79
|
+
//if (jQuery.browser.msie) this.$element[0].className += '';
|
|
80
|
+
|
|
81
|
+
if (this.selectFirstMatch && this.current) {
|
|
82
|
+
this.selectFirstMatch = false;
|
|
83
|
+
this.select(this.current);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (isLast) {
|
|
87
|
+
this.selectFirstMatch = false;
|
|
88
|
+
this.result.setAttribute('aria-busy', 'false');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.move = function(isDown) {
|
|
93
|
+
if (!this.current) return;
|
|
94
|
+
var next = isDown ? this.current.nextElementSibling : this.current.previousElementSibling;
|
|
95
|
+
if (next) {
|
|
96
|
+
this.current.classList.remove('search-selected');
|
|
97
|
+
next.classList.add('search-selected');
|
|
98
|
+
this.input.setAttribute('aria-activedescendant', next.getAttribute('id'));
|
|
99
|
+
this.scrollIntoView(next, this.view);
|
|
100
|
+
this.current = next;
|
|
101
|
+
this.input.value = next.firstChild.firstChild.text;
|
|
102
|
+
this.input.select();
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
this.hlt = function(html) {
|
|
108
|
+
return this.escapeHTML(html).
|
|
109
|
+
replace(/\u0001/g, '<em>').
|
|
110
|
+
replace(/\u0002/g, '</em>');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
this.escapeHTML = function(html) {
|
|
114
|
+
return html.replace(/[&<>"`']/g, function(c) {
|
|
115
|
+
return '&#' + c.charCodeAt(0) + ';';
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
});
|
|
120
|
+
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const STORAGE_KEY = 'rdoc-theme';
|
|
5
|
+
const THEME_LIGHT = 'light';
|
|
6
|
+
const THEME_DARK = 'dark';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get the user's theme preference
|
|
10
|
+
* Priority: localStorage > system preference > light (default)
|
|
11
|
+
*/
|
|
12
|
+
function getThemePreference() {
|
|
13
|
+
// Check localStorage first
|
|
14
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
15
|
+
if (stored === THEME_LIGHT || stored === THEME_DARK) {
|
|
16
|
+
return stored;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Check system preference
|
|
20
|
+
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
21
|
+
return THEME_DARK;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return THEME_LIGHT;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Apply theme to document
|
|
29
|
+
*/
|
|
30
|
+
function applyTheme(theme) {
|
|
31
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
32
|
+
localStorage.setItem(STORAGE_KEY, theme);
|
|
33
|
+
|
|
34
|
+
// Update toggle button icon
|
|
35
|
+
const toggleBtn = document.getElementById('theme-toggle');
|
|
36
|
+
if (toggleBtn) {
|
|
37
|
+
const icon = toggleBtn.querySelector('.theme-toggle-icon');
|
|
38
|
+
if (icon) {
|
|
39
|
+
icon.textContent = theme === THEME_DARK ? '☀️' : '🌙';
|
|
40
|
+
}
|
|
41
|
+
toggleBtn.setAttribute('aria-label',
|
|
42
|
+
theme === THEME_DARK ? 'Switch to light mode' : 'Switch to dark mode'
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Toggle between light and dark themes
|
|
49
|
+
*/
|
|
50
|
+
function toggleTheme() {
|
|
51
|
+
const currentTheme = document.documentElement.getAttribute('data-theme') || THEME_LIGHT;
|
|
52
|
+
const newTheme = currentTheme === THEME_LIGHT ? THEME_DARK : THEME_LIGHT;
|
|
53
|
+
applyTheme(newTheme);
|
|
54
|
+
|
|
55
|
+
// Announce to screen readers
|
|
56
|
+
announceThemeChange(newTheme);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Announce theme change to screen readers
|
|
61
|
+
*/
|
|
62
|
+
function announceThemeChange(theme) {
|
|
63
|
+
const announcement = document.createElement('div');
|
|
64
|
+
announcement.setAttribute('role', 'status');
|
|
65
|
+
announcement.setAttribute('aria-live', 'polite');
|
|
66
|
+
announcement.className = 'sr-only';
|
|
67
|
+
announcement.textContent = `Switched to ${theme} mode`;
|
|
68
|
+
document.body.appendChild(announcement);
|
|
69
|
+
|
|
70
|
+
// Remove after announcement
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
document.body.removeChild(announcement);
|
|
73
|
+
}, 1000);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Initialize theme on page load
|
|
78
|
+
*/
|
|
79
|
+
function initTheme() {
|
|
80
|
+
// Apply theme immediately to prevent flash
|
|
81
|
+
const theme = getThemePreference();
|
|
82
|
+
applyTheme(theme);
|
|
83
|
+
|
|
84
|
+
// Set up toggle button listener
|
|
85
|
+
const toggleBtn = document.getElementById('theme-toggle');
|
|
86
|
+
if (toggleBtn) {
|
|
87
|
+
toggleBtn.addEventListener('click', toggleTheme);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Listen for system theme changes
|
|
91
|
+
if (window.matchMedia) {
|
|
92
|
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
|
|
93
|
+
// Only auto-switch if user hasn't manually set a preference
|
|
94
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
95
|
+
if (!stored) {
|
|
96
|
+
applyTheme(e.matches ? THEME_DARK : THEME_LIGHT);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Initialize immediately (before DOMContentLoaded to prevent flash)
|
|
103
|
+
if (document.readyState === 'loading') {
|
|
104
|
+
// Apply theme as early as possible
|
|
105
|
+
const theme = getThemePreference();
|
|
106
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
107
|
+
|
|
108
|
+
document.addEventListener('DOMContentLoaded', initTheme);
|
|
109
|
+
} else {
|
|
110
|
+
initTheme();
|
|
111
|
+
}
|
|
112
|
+
})();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<body role="document" class="file has-toc">
|
|
2
|
+
<%= render '_header.rhtml' %>
|
|
3
|
+
<%= render '_sidebar_toggle.rhtml' %>
|
|
4
|
+
|
|
5
|
+
<nav id="navigation" role="navigation">
|
|
6
|
+
<%= render '_sidebar_pages.rhtml' %>
|
|
7
|
+
<%= render '_sidebar_classes.rhtml' %>
|
|
8
|
+
</nav>
|
|
9
|
+
|
|
10
|
+
<main role="main" aria-label="Page <%= h file.full_name %>">
|
|
11
|
+
<%= file.description %>
|
|
12
|
+
</main>
|
|
13
|
+
|
|
14
|
+
<%= render '_aside_toc.rhtml' %>
|
|
15
|
+
|
|
16
|
+
<%= render '_footer.rhtml' %>
|
|
17
|
+
</body>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<body role="document">
|
|
2
|
+
<%= render '_sidebar_toggle.rhtml' %>
|
|
3
|
+
|
|
4
|
+
<nav id="navigation" role="navigation">
|
|
5
|
+
<%= render '_sidebar_pages.rhtml' %>
|
|
6
|
+
<%= render '_sidebar_classes.rhtml' %>
|
|
7
|
+
</nav>
|
|
8
|
+
|
|
9
|
+
<main role="main">
|
|
10
|
+
<h1>Not Found</h1>
|
|
11
|
+
|
|
12
|
+
<p><%= message %></p>
|
|
13
|
+
</main>
|
|
14
|
+
</body>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<body role="document">
|
|
2
|
+
<%= render '_sidebar_toggle.rhtml' %>
|
|
3
|
+
|
|
4
|
+
<nav id="navigation" role="navigation">
|
|
5
|
+
<div id="project-navigation">
|
|
6
|
+
<div id="home-section" class="nav-section">
|
|
7
|
+
<h2>
|
|
8
|
+
<a href="<%= rel_prefix %>/" rel="home">Home</a>
|
|
9
|
+
</h2>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<%= render '_sidebar_search.rhtml' %>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<%= render '_sidebar_installed.rhtml' %>
|
|
16
|
+
</nav>
|
|
17
|
+
|
|
18
|
+
<main role="main">
|
|
19
|
+
<h1>Local RDoc Documentation</h1>
|
|
20
|
+
|
|
21
|
+
<p>Here you can browse local documentation from the ruby standard library and
|
|
22
|
+
your installed gems.</p>
|
|
23
|
+
|
|
24
|
+
<%- extra_dirs = installed.select { |_, _, _, type,| type == :extra } %>
|
|
25
|
+
<%- unless extra_dirs.empty? %>
|
|
26
|
+
<h2>Extra Documentation Directories</h2>
|
|
27
|
+
|
|
28
|
+
<p>The following additional documentation directories are available:</p>
|
|
29
|
+
|
|
30
|
+
<ol>
|
|
31
|
+
<%- extra_dirs.each do |name, href, exists, _, path| %>
|
|
32
|
+
<li>
|
|
33
|
+
<%- if exists %>
|
|
34
|
+
<a href="<%= href %>"><%= h name %></a> (<%= h path %>)
|
|
35
|
+
<%- else %>
|
|
36
|
+
<%= h name %> (<%= h path %>; <i>not available</i>)
|
|
37
|
+
<%- end %>
|
|
38
|
+
</li>
|
|
39
|
+
<%- end %>
|
|
40
|
+
</ol>
|
|
41
|
+
<%- end %>
|
|
42
|
+
|
|
43
|
+
<%- gems = installed.select { |_, _, _, type,| type == :gem } %>
|
|
44
|
+
<%- missing = gems.reject { |_, _, exists,| exists } %>
|
|
45
|
+
<%- unless missing.empty? then %>
|
|
46
|
+
<h2>Missing Gem Documentation</h2>
|
|
47
|
+
|
|
48
|
+
<p>You are missing documentation for some of your installed gems.
|
|
49
|
+
You can install missing documentation for gems by running
|
|
50
|
+
<kbd>gem rdoc --all</kbd>. After installing the missing documentation you
|
|
51
|
+
only need to reload this page. The newly created documentation will
|
|
52
|
+
automatically appear.</p>
|
|
53
|
+
|
|
54
|
+
<p>You can also install documentation for a specific gem by running one of
|
|
55
|
+
the following commands.</p>
|
|
56
|
+
|
|
57
|
+
<ul>
|
|
58
|
+
<%- names = missing.map { |name,| name.sub(/-([^-]*)$/, '') }.uniq %>
|
|
59
|
+
<%- names.each do |name| %>
|
|
60
|
+
<li><kbd>gem rdoc <%= h name %></kbd></li>
|
|
61
|
+
<%- end %>
|
|
62
|
+
</ul>
|
|
63
|
+
<%- end %>
|
|
64
|
+
</main>
|
|
65
|
+
</body>
|
|
@@ -16,13 +16,8 @@
|
|
|
16
16
|
<meta name="description" content="<%= h "#{file.page_name}: #{excerpt(file.comment)}" %>">
|
|
17
17
|
<%- elsif @title %>
|
|
18
18
|
<meta name="keywords" content="ruby,documentation,<%= h @title %>">
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
main_page = @files.find { |f| f.full_name == @options.main_page } then %>
|
|
22
|
-
<meta name="description" content="<%= h "#{@title}: #{excerpt(main_page.comment)}" %>">
|
|
23
|
-
<%- else %>
|
|
24
|
-
<meta name="description" content="Documentation for <%= h @title %>">
|
|
25
|
-
<%- end %>
|
|
19
|
+
<% description = @main_page ? "#{@title}: #{excerpt(@main_page.comment)}" : "Documentation for #{@title}" %>
|
|
20
|
+
<meta name="description" content="<%= h description %>">
|
|
26
21
|
<%- end %>
|
|
27
22
|
|
|
28
23
|
<%- if canonical_url = @options.canonical_root %>
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<input id="search-field" role="combobox" aria-label="Search"
|
|
5
5
|
aria-autocomplete="list" aria-controls="search-results"
|
|
6
6
|
type="text" name="search" placeholder="Search (/) for a class, method, ..." spellcheck="false"
|
|
7
|
+
autocomplete="off"
|
|
7
8
|
title="Type to search, Up and Down to navigate, Enter to load">
|
|
8
9
|
</div>
|
|
9
10
|
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
unless table.empty? then %>
|
|
48
48
|
<ul>
|
|
49
49
|
<%- table.each do |item| %>
|
|
50
|
-
<%-
|
|
50
|
+
<%- label = item.respond_to?(:label) ? item.label(klass) : item.aref %>
|
|
51
51
|
<li><a href="<%= klass.path %>#<%= label %>"><%= item.plain_html %></a></li>
|
|
52
52
|
<%- end %>
|
|
53
53
|
</ul>
|
|
@@ -57,10 +57,14 @@ Searcher.prototype = new function() {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
function buildRegexps(queries) {
|
|
60
|
+
// A small minority of older browsers don't have RegExp.escape
|
|
61
|
+
// but it's not worth including a complex polyfill.
|
|
62
|
+
var escape = RegExp.escape || function(s) { return s };
|
|
63
|
+
|
|
60
64
|
return queries.map(function(query) {
|
|
61
65
|
var pattern = [];
|
|
62
66
|
for (var i = 0; i < query.length; i++) {
|
|
63
|
-
var char =
|
|
67
|
+
var char = escape(query[i]);
|
|
64
68
|
pattern.push('([' + char + '])([^' + char + ']*?)');
|
|
65
69
|
}
|
|
66
70
|
return new RegExp(pattern.join(''), 'i');
|