bitclust-core 1.3.0 → 1.6.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/Gemfile +3 -0
- data/README.md +65 -0
- data/Rakefile +32 -0
- data/data/bitclust/catalog/ja_JP.UTF-8 +19 -1
- data/data/bitclust/searchpage/index.html +90 -0
- data/data/bitclust/template/class +69 -8
- data/data/bitclust/template/function +2 -2
- data/data/bitclust/template/layout +11 -3
- data/data/bitclust/template/method +2 -2
- data/data/bitclust/template/search +1 -1
- data/data/bitclust/template.epub/class +22 -1
- data/data/bitclust/template.epub/function +2 -2
- data/data/bitclust/template.epub/layout +1 -3
- data/data/bitclust/template.epub/method +2 -2
- data/data/bitclust/template.lillia/class +26 -5
- data/data/bitclust/template.lillia/layout +1 -3
- data/data/bitclust/template.lillia/method +2 -2
- data/data/bitclust/template.offline/class +48 -4
- data/data/bitclust/template.offline/function +2 -2
- data/data/bitclust/template.offline/layout +27 -3
- data/data/bitclust/template.offline/method +2 -2
- data/lib/bitclust/app.rb +16 -17
- data/lib/bitclust/capi_converter.rb +20 -0
- data/lib/bitclust/classentry.rb +67 -17
- data/lib/bitclust/compat.rb +2 -2
- data/lib/bitclust/completion.rb +58 -27
- data/lib/bitclust/crossrubyutils.rb +8 -5
- data/lib/bitclust/database.rb +28 -8
- data/lib/bitclust/doc_converter.rb +63 -0
- data/lib/bitclust/docentry.rb +1 -1
- data/lib/bitclust/entity_splitter.rb +221 -0
- data/lib/bitclust/entry.rb +47 -7
- data/lib/bitclust/functiondatabase.rb +13 -1
- data/lib/bitclust/functionentry.rb +5 -1
- data/lib/bitclust/functionreferenceparser.rb +4 -4
- data/lib/bitclust/generators/epub.rb +10 -3
- data/lib/bitclust/htmlutils.rb +1 -1
- data/lib/bitclust/include_graph.rb +375 -0
- data/lib/bitclust/include_pruner.rb +144 -0
- data/lib/bitclust/interface.rb +1 -1
- data/lib/bitclust/libraryentry.rb +8 -7
- data/lib/bitclust/lineinput.rb +7 -5
- data/lib/bitclust/link_checker.rb +179 -0
- data/lib/bitclust/markdown_bridge.rb +186 -0
- data/lib/bitclust/markdown_orchestrator.rb +267 -0
- data/lib/bitclust/markdown_to_rrd.rb +824 -0
- data/lib/bitclust/markdown_tree.rb +217 -0
- data/lib/bitclust/mdcompiler.rb +883 -0
- data/lib/bitclust/mdparser.rb +347 -0
- data/lib/bitclust/messagecatalog.rb +3 -3
- data/lib/bitclust/method_since_calculator.rb +117 -0
- data/lib/bitclust/methoddatabase.rb +124 -14
- data/lib/bitclust/methodentry.rb +125 -7
- data/lib/bitclust/methodid.rb +12 -11
- data/lib/bitclust/methodsignature.rb +10 -4
- data/lib/bitclust/nameutils.rb +41 -22
- data/lib/bitclust/preprocessor.rb +66 -29
- data/lib/bitclust/rdcompiler.rb +195 -95
- data/lib/bitclust/refsdatabase.rb +23 -8
- data/lib/bitclust/reloadable_request_handler.rb +72 -0
- data/lib/bitclust/requesthandler.rb +21 -16
- data/lib/bitclust/ridatabase.rb +10 -4
- data/lib/bitclust/rrd_to_markdown.rb +925 -0
- data/lib/bitclust/rrdparser.rb +196 -30
- data/lib/bitclust/runner.rb +17 -2
- data/lib/bitclust/screen.rb +80 -11
- data/lib/bitclust/search_index_generator.rb +288 -0
- data/lib/bitclust/searcher.rb +69 -20
- data/lib/bitclust/simplesearcher.rb +24 -12
- data/lib/bitclust/subcommand.rb +7 -1
- data/lib/bitclust/subcommands/ancestors_command.rb +9 -2
- data/lib/bitclust/subcommands/checklink_command.rb +54 -0
- data/lib/bitclust/subcommands/chm_command.rb +15 -7
- data/lib/bitclust/subcommands/classes_command.rb +5 -0
- data/lib/bitclust/subcommands/extract_command.rb +5 -0
- data/lib/bitclust/subcommands/htmlfile_command.rb +21 -7
- data/lib/bitclust/subcommands/init_command.rb +4 -1
- data/lib/bitclust/subcommands/list_command.rb +12 -4
- data/lib/bitclust/subcommands/lookup_command.rb +28 -9
- data/lib/bitclust/subcommands/methods_command.rb +10 -2
- data/lib/bitclust/subcommands/methodsince_command.rb +80 -0
- data/lib/bitclust/subcommands/preproc_command.rb +5 -0
- data/lib/bitclust/subcommands/property_command.rb +2 -1
- data/lib/bitclust/subcommands/searchpage_command.rb +102 -0
- data/lib/bitclust/subcommands/server_command.rb +10 -8
- data/lib/bitclust/subcommands/setup_command.rb +24 -10
- data/lib/bitclust/subcommands/statichtml_command.rb +129 -6
- data/lib/bitclust/subcommands/update_command.rb +110 -2
- data/lib/bitclust/syntax_highlighter.rb +48 -4
- data/lib/bitclust/textutils.rb +3 -3
- data/lib/bitclust/version.rb +1 -1
- data/lib/bitclust/version_badges.rb +90 -0
- data/lib/bitclust/whole_file_gate.rb +137 -0
- data/test/test_app.rb +269 -0
- data/test/test_bitclust.rb +2 -0
- data/test/test_capi_converter.rb +91 -0
- data/test/test_class_screen.rb +413 -0
- data/test/test_copy_doc_md.rb +53 -0
- data/test/test_doc_converter.rb +78 -0
- data/test/test_entity_splitter.rb +231 -0
- data/test/test_entry.rb +207 -2
- data/test/test_eol_warning.rb +43 -0
- data/test/test_functionreferenceparser.rb +11 -0
- data/test/test_include_graph.rb +640 -0
- data/test/test_include_pruner.rb +133 -0
- data/test/test_link_checker.rb +114 -0
- data/test/test_lookup_command.rb +135 -0
- data/test/test_markdown_bridge.rb +217 -0
- data/test/test_markdown_orchestrator.rb +478 -0
- data/test/test_markdown_to_rrd.rb +757 -0
- data/test/test_markdown_tree.rb +369 -0
- data/test/test_mdcompiler.rb +1276 -0
- data/test/test_mdparser.rb +538 -0
- data/test/test_method_screen.rb +166 -0
- data/test/test_method_since_calculator.rb +290 -0
- data/test/test_methoddatabase.rb +16 -0
- data/test/test_methodentry.rb +328 -0
- data/test/test_methodsignature.rb +3 -1
- data/test/test_methodsince_command.rb +168 -0
- data/test/test_nameutils.rb +22 -0
- data/test/test_preprocessor.rb +215 -0
- data/test/test_rdcompiler.rb +401 -30
- data/test/test_refsdatabase.rb +32 -0
- data/test/test_rrd_to_markdown.rb +968 -0
- data/test/test_rrdparser.rb +295 -2
- data/test/test_run_ruby_wasm.rb +54 -0
- data/test/test_run_worker_prelude.rb +83 -0
- data/test/test_runner.rb +51 -0
- data/test/test_search_index_generator.rb +513 -0
- data/test/test_search_screen.rb +48 -0
- data/test/test_searcher.rb +132 -0
- data/test/test_searchpage_command.rb +167 -0
- data/test/test_simplesearcher.rb +28 -0
- data/test/test_statichtml_command.rb +204 -0
- data/test/test_syntax_highlighter.rb +60 -0
- data/test/test_whole_file_gate.rb +175 -0
- data/theme/default/js/NOTICE +42 -0
- data/theme/default/js/run-worker.js +111 -0
- data/theme/default/js/run.js +350 -0
- data/theme/default/js/search_controller.js +134 -0
- data/theme/default/js/search_init.js +176 -0
- data/theme/default/js/search_navigation.js +110 -0
- data/theme/default/js/search_page.js +202 -0
- data/theme/default/js/search_ranker.js +244 -0
- data/theme/default/script.js +91 -18
- data/theme/default/search.css +149 -0
- data/theme/default/style.css +193 -21
- data/theme/default/syntax-highlight.css +8 -0
- data/theme/lillia/style.css +45 -1
- metadata +161 -20
- data/README +0 -32
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/*
|
|
3
|
+
* Part of BitClust. Distributed under the Ruby License or the MIT License.
|
|
4
|
+
*
|
|
5
|
+
* Wires up the client-side search box for statichtml output.
|
|
6
|
+
*
|
|
7
|
+
* The sibling files search_navigation.js, search_ranker.js and
|
|
8
|
+
* search_controller.js are vendored verbatim from RDoc's Aliki theme.
|
|
9
|
+
* They expect two globals to be defined before this script runs:
|
|
10
|
+
*
|
|
11
|
+
* search_data - defined by js/search_data.js (the index)
|
|
12
|
+
* index_rel_prefix - relative path from the current page to the site root,
|
|
13
|
+
* set inline in the page <head> by the layout template.
|
|
14
|
+
*/
|
|
15
|
+
(function() {
|
|
16
|
+
// BitClust extension (the vendored Aliki files above are kept verbatim).
|
|
17
|
+
//
|
|
18
|
+
// RDoc's parseQuery() rewrites "." to "::" (its class-method separator) and
|
|
19
|
+
// matches "::"/"."/"#" queries against full_name instead of name. Ruby's
|
|
20
|
+
// special variables are indexed with their "$" sigil in full_name, and one of
|
|
21
|
+
// them ($.) contains a literal "." — so a "$." query would become "$::" and
|
|
22
|
+
// never match. Wrap parseQuery so that any "$"-prefixed (special variable)
|
|
23
|
+
// query keeps its literal text and is matched against full_name.
|
|
24
|
+
// See https://github.com/rurema/bitclust/issues/194
|
|
25
|
+
//
|
|
26
|
+
// Separately (bitclust#279): BitClust keeps a literal "." in full_name for
|
|
27
|
+
// singleton methods ("File.open") and ".#"/"?." for module functions
|
|
28
|
+
// ("Kernel.#open" pre-4.0, "Kernel?.open" 4.0+ — see #250), instead of
|
|
29
|
+
// RDoc's "::" convention. The same "." -> "::" rewrite above then makes
|
|
30
|
+
// every one of those dot-qualified queries miss its own entry. Fold "?."
|
|
31
|
+
// to ".#" here so both module-function spellings collapse to the same
|
|
32
|
+
// query text before the "." -> "::" rewrite runs; the other half of the
|
|
33
|
+
// fix (matching against SearchIndexGenerator's match_name instead of
|
|
34
|
+
// full_name) is the computeScore wrap below.
|
|
35
|
+
if (typeof parseQuery === 'function') {
|
|
36
|
+
var alikiParseQuery = parseQuery;
|
|
37
|
+
parseQuery = function(query) {
|
|
38
|
+
var folded = query.indexOf('?.') === -1 ? query : query.replace(/\?\./g, '.#');
|
|
39
|
+
var q = alikiParseQuery(folded);
|
|
40
|
+
if (query.charAt(0) === '$') {
|
|
41
|
+
q.normalized = query.toLowerCase(); // undo the "." -> "::" rewrite
|
|
42
|
+
q.matchesFullName = true; // the "$" sigil lives in full_name
|
|
43
|
+
}
|
|
44
|
+
q.original = query; // keep the real original text, not the folded one
|
|
45
|
+
return q;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// bitclust#279 (continued): computeScore() scores a query against
|
|
50
|
+
// entry.full_name, which — per the comment above — doesn't use "::" the
|
|
51
|
+
// way parseQuery's rewrite expects. SearchIndexGenerator adds a
|
|
52
|
+
// match_name to affected entries (full_name with "?." folded to ".#" and
|
|
53
|
+
// then every "." turned into "::", mirroring parseQuery's own rewrite —
|
|
54
|
+
// see search_index_generator.rb's header comment). When an entry carries
|
|
55
|
+
// one, score a shallow clone with full_name swapped to match_name instead
|
|
56
|
+
// — the original entry object (and its displayed full_name) is never
|
|
57
|
+
// touched, so this only affects which entries match, not what is shown.
|
|
58
|
+
// Entries without a match_name (instance methods, constants, classes,
|
|
59
|
+
// special variables, ...) are scored exactly as before.
|
|
60
|
+
if (typeof computeScore === 'function') {
|
|
61
|
+
var alikiComputeScore = computeScore;
|
|
62
|
+
computeScore = function(entry, q) {
|
|
63
|
+
if (entry && entry.match_name) {
|
|
64
|
+
return alikiComputeScore({ name: entry.name, full_name: entry.match_name, type: entry.type }, q);
|
|
65
|
+
}
|
|
66
|
+
return alikiComputeScore(entry, q);
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// bitclust#279 (continued): highlightMatch() compares q.normalized (with
|
|
71
|
+
// "." already rewritten to "::" and "?." folded to ".#" by the wrap
|
|
72
|
+
// above) against the displayed full_name, which keeps its literal
|
|
73
|
+
// "."/".#"/"?." — so the dot-qualified queries fixed above matched their
|
|
74
|
+
// entry but never earned an <em> highlight (at best the vendored fuzzy
|
|
75
|
+
// fallback gave up at the first ":"). Whenever the vendored code found
|
|
76
|
+
// nothing to mark, retry a contiguous match with the user's *original*
|
|
77
|
+
// query text (q.original, restored by the parseQuery wrap), treating
|
|
78
|
+
// ".#" and "?." as the same module-function spelling. Both marks are two
|
|
79
|
+
// characters long, so indexes into the folded strings map 1:1 onto the
|
|
80
|
+
// displayed text and the markers can be spliced straight into it.
|
|
81
|
+
if (typeof highlightMatch === 'function') {
|
|
82
|
+
var alikiHighlightMatch = highlightMatch;
|
|
83
|
+
highlightMatch = function(text, q) {
|
|
84
|
+
var marked = alikiHighlightMatch(text, q);
|
|
85
|
+
if (marked !== text) return marked; // the vendored code managed
|
|
86
|
+
if (!text || !q || !q.original) return marked;
|
|
87
|
+
var canonText = text.toLowerCase().replace(/\.#/g, '?.');
|
|
88
|
+
var canonQuery = q.original.toLowerCase().replace(/\.#/g, '?.');
|
|
89
|
+
if (!canonQuery) return marked;
|
|
90
|
+
var start = canonText.indexOf(canonQuery);
|
|
91
|
+
if (start === -1) return marked;
|
|
92
|
+
var end = start + canonQuery.length;
|
|
93
|
+
return text.substring(0, start) +
|
|
94
|
+
'\u0001' + text.substring(start, end) + '\u0002' +
|
|
95
|
+
text.substring(end);
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function createSearchInstance(input, result) {
|
|
100
|
+
if (!input || !result) return null;
|
|
101
|
+
|
|
102
|
+
var search = new SearchController(search_data, input, result);
|
|
103
|
+
|
|
104
|
+
search.renderItem = function(r) {
|
|
105
|
+
var li = document.createElement('li');
|
|
106
|
+
var html = '<p class="search-match"><a href="' +
|
|
107
|
+
index_rel_prefix + this.escapeHTML(r.path) + '">' +
|
|
108
|
+
this.hlt(r.title) + '</a>';
|
|
109
|
+
if (r.type) {
|
|
110
|
+
var typeClass = r.type.replace(/_/g, '-');
|
|
111
|
+
html += '<span class="search-type search-type-' +
|
|
112
|
+
this.escapeHTML(typeClass) + '">' + this.formatType(r.type) + '</span>';
|
|
113
|
+
}
|
|
114
|
+
html += '</p>';
|
|
115
|
+
if (r.snippet) html += '<div class="search-snippet">' + r.snippet + '</div>';
|
|
116
|
+
li.innerHTML = html;
|
|
117
|
+
return li;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
search.formatType = function(type) {
|
|
121
|
+
var labels = {
|
|
122
|
+
'class': 'class', 'module': 'module', 'object': 'object',
|
|
123
|
+
'constant': 'const', 'instance_method': 'method', 'class_method': 'method',
|
|
124
|
+
'variable': 'var', 'library': 'lib', 'document': 'doc', 'function': 'func',
|
|
125
|
+
'heading': 'section'
|
|
126
|
+
};
|
|
127
|
+
return labels[type] || type;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
search.select = function(selected) {
|
|
131
|
+
if (selected) window.location.href = selected.firstChild.firstChild.href;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
return search;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function hookSearch() {
|
|
138
|
+
var input = document.querySelector('#search-field');
|
|
139
|
+
var result = document.querySelector('#search-results');
|
|
140
|
+
if (!input || !result) return;
|
|
141
|
+
|
|
142
|
+
var search = createSearchInstance(input, result);
|
|
143
|
+
if (!search) return;
|
|
144
|
+
|
|
145
|
+
document.addEventListener('click', function(e) {
|
|
146
|
+
if (!e.target.closest('#search-section')) search.hide();
|
|
147
|
+
});
|
|
148
|
+
document.addEventListener('keydown', function(e) {
|
|
149
|
+
if (e.key === 'Escape' && input.matches(':focus')) { search.hide(); input.blur(); }
|
|
150
|
+
});
|
|
151
|
+
input.addEventListener('focus', function() {
|
|
152
|
+
if (input.value.trim()) search.show();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (typeof URLSearchParams !== 'undefined') {
|
|
156
|
+
var q = new URLSearchParams(window.location.search).get('q');
|
|
157
|
+
if (q) { input.value = q; search.search(q, false); }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Press "/" anywhere to focus the search box.
|
|
162
|
+
function hookFocus() {
|
|
163
|
+
document.addEventListener('keydown', function(e) {
|
|
164
|
+
if (document.activeElement && document.activeElement.tagName === 'INPUT') return;
|
|
165
|
+
if (e.key === '/') {
|
|
166
|
+
var f = document.querySelector('#search-field');
|
|
167
|
+
if (f) { e.preventDefault(); f.focus(); }
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
173
|
+
hookSearch();
|
|
174
|
+
hookFocus();
|
|
175
|
+
});
|
|
176
|
+
})();
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Vendored from RDoc's Aliki theme (lib/rdoc/generator/template/aliki/js/), RDoc v7.2.0.
|
|
3
|
+
* Copyright (c) 2025 Stan Lo. Released under the MIT License.
|
|
4
|
+
* See https://github.com/ruby/rdoc/blob/v7.2.0/LEGAL.rdoc
|
|
5
|
+
*/
|
|
6
|
+
/*
|
|
7
|
+
* SearchNavigation allows movement using the arrow keys through the search results.
|
|
8
|
+
*
|
|
9
|
+
* When using this library you will need to set scrollIntoView to the
|
|
10
|
+
* appropriate function for your layout. Use scrollInWindow if the container
|
|
11
|
+
* is not scrollable and scrollInElement if the container is a separate
|
|
12
|
+
* scrolling region.
|
|
13
|
+
*/
|
|
14
|
+
SearchNavigation = new function() {
|
|
15
|
+
this.initNavigation = function() {
|
|
16
|
+
var _this = this;
|
|
17
|
+
|
|
18
|
+
document.addEventListener('keydown', function(e) {
|
|
19
|
+
_this.onkeydown(e);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
this.navigationActive = true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this.setNavigationActive = function(state) {
|
|
26
|
+
this.navigationActive = state;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.onkeydown = function(e) {
|
|
30
|
+
if (!this.navigationActive) return;
|
|
31
|
+
switch(e.key) {
|
|
32
|
+
case 'ArrowLeft':
|
|
33
|
+
if (this.moveLeft()) e.preventDefault();
|
|
34
|
+
break;
|
|
35
|
+
case 'ArrowUp':
|
|
36
|
+
if (e.key == 'ArrowUp' || e.ctrlKey) {
|
|
37
|
+
if (this.moveUp()) e.preventDefault();
|
|
38
|
+
}
|
|
39
|
+
break;
|
|
40
|
+
case 'ArrowRight':
|
|
41
|
+
if (this.moveRight()) e.preventDefault();
|
|
42
|
+
break;
|
|
43
|
+
case 'ArrowDown':
|
|
44
|
+
if (e.key == 'ArrowDown' || e.ctrlKey) {
|
|
45
|
+
if (this.moveDown()) e.preventDefault();
|
|
46
|
+
}
|
|
47
|
+
break;
|
|
48
|
+
case 'Enter':
|
|
49
|
+
if (this.current) e.preventDefault();
|
|
50
|
+
this.select(this.current);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
if (e.ctrlKey && e.shiftKey) this.select(this.current);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.moveRight = function() {
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.moveLeft = function() {
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.move = function(isDown) {
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
this.moveUp = function() {
|
|
66
|
+
return this.move(false);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.moveDown = function() {
|
|
70
|
+
return this.move(true);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
* Scrolls to the given element in the scrollable element view.
|
|
75
|
+
*/
|
|
76
|
+
this.scrollInElement = function(element, view) {
|
|
77
|
+
var offset, viewHeight, viewScroll, height;
|
|
78
|
+
offset = element.offsetTop;
|
|
79
|
+
height = element.offsetHeight;
|
|
80
|
+
viewHeight = view.offsetHeight;
|
|
81
|
+
viewScroll = view.scrollTop;
|
|
82
|
+
|
|
83
|
+
if (offset - viewScroll + height > viewHeight) {
|
|
84
|
+
view.scrollTop = offset - viewHeight + height;
|
|
85
|
+
}
|
|
86
|
+
if (offset < viewScroll) {
|
|
87
|
+
view.scrollTop = offset;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/*
|
|
92
|
+
* Scrolls to the given element in the window. The second argument is
|
|
93
|
+
* ignored
|
|
94
|
+
*/
|
|
95
|
+
this.scrollInWindow = function(element, ignored) {
|
|
96
|
+
var offset, viewHeight, viewScroll, height;
|
|
97
|
+
offset = element.offsetTop;
|
|
98
|
+
height = element.offsetHeight;
|
|
99
|
+
viewHeight = window.innerHeight;
|
|
100
|
+
viewScroll = window.scrollY;
|
|
101
|
+
|
|
102
|
+
if (offset - viewScroll + height > viewHeight) {
|
|
103
|
+
window.scrollTo(window.scrollX, offset - viewHeight + height);
|
|
104
|
+
}
|
|
105
|
+
if (offset < viewScroll) {
|
|
106
|
+
window.scrollTo(window.scrollX, offset);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/*
|
|
3
|
+
* Part of BitClust. Distributed under the Ruby License or the MIT License.
|
|
4
|
+
*
|
|
5
|
+
* Wires up the standalone cross-version search page (the static replacement
|
|
6
|
+
* for the server-backed rurema-search app at /ja/search/).
|
|
7
|
+
*
|
|
8
|
+
* The sibling files search_navigation.js, search_ranker.js and
|
|
9
|
+
* search_controller.js are vendored verbatim from RDoc's Aliki theme.
|
|
10
|
+
* They expect these globals to be defined before this script runs:
|
|
11
|
+
*
|
|
12
|
+
* search_data - defined by js/search_data.js. Each entry carries a
|
|
13
|
+
* `versions` array (ascending) added by
|
|
14
|
+
* SearchIndexGenerator.merge.
|
|
15
|
+
* search_versions - all indexed versions, ascending (inline in the page).
|
|
16
|
+
* search_version_base - relative path from this page to the directory that
|
|
17
|
+
* holds the per-version document roots ("../" when the
|
|
18
|
+
* page lives at /ja/search/).
|
|
19
|
+
*/
|
|
20
|
+
(function() {
|
|
21
|
+
// BitClust extension (the vendored Aliki files above are kept verbatim).
|
|
22
|
+
//
|
|
23
|
+
// Same special-variable handling as search_init.js: keep "$"-prefixed
|
|
24
|
+
// queries literal (no "." -> "::" rewrite) and match them against
|
|
25
|
+
// full_name, where the "$" sigil lives.
|
|
26
|
+
// See https://github.com/rurema/bitclust/issues/194
|
|
27
|
+
//
|
|
28
|
+
// Same bitclust#279 handling as search_init.js too: fold "?." to ".#" so
|
|
29
|
+
// both module-function spellings ("Kernel.#open" pre-4.0, "Kernel?.open"
|
|
30
|
+
// 4.0+ — see #250) normalize the same way, and score dot-qualified
|
|
31
|
+
// entries via their match_name (added by SearchIndexGenerator) instead of
|
|
32
|
+
// full_name, since BitClust keeps a literal "." there for display while
|
|
33
|
+
// parseQuery's rewrite expects "::". See search_init.js for the full
|
|
34
|
+
// rationale; SearchIndexGenerator.merge keeps match_name on merged
|
|
35
|
+
// cross-version entries same as any other field.
|
|
36
|
+
if (typeof parseQuery === 'function') {
|
|
37
|
+
var alikiParseQuery = parseQuery;
|
|
38
|
+
parseQuery = function(query) {
|
|
39
|
+
var folded = query.indexOf('?.') === -1 ? query : query.replace(/\?\./g, '.#');
|
|
40
|
+
var q = alikiParseQuery(folded);
|
|
41
|
+
if (query.charAt(0) === '$') {
|
|
42
|
+
q.normalized = query.toLowerCase();
|
|
43
|
+
q.matchesFullName = true;
|
|
44
|
+
}
|
|
45
|
+
q.original = query;
|
|
46
|
+
return q;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (typeof computeScore === 'function') {
|
|
51
|
+
var alikiComputeScore = computeScore;
|
|
52
|
+
computeScore = function(entry, q) {
|
|
53
|
+
if (entry && entry.match_name) {
|
|
54
|
+
return alikiComputeScore({ name: entry.name, full_name: entry.match_name, type: entry.type }, q);
|
|
55
|
+
}
|
|
56
|
+
return alikiComputeScore(entry, q);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Same highlightMatch wrap as search_init.js: when the vendored code
|
|
61
|
+
// found nothing to mark (dot-qualified queries never contiguous-match a
|
|
62
|
+
// literal-dot full_name once "." has been rewritten to "::"), retry with
|
|
63
|
+
// the user's original query text, treating ".#" and "?." as the same
|
|
64
|
+
// module-function spelling. On this page the merged index only carries
|
|
65
|
+
// the "?." spelling (SearchIndexGenerator.merge folds ".#" away), so
|
|
66
|
+
// this is what lets a query typed in the pre-4.0 ".#" notation still
|
|
67
|
+
// light up the "?."-spelled row it matched. Both marks are two
|
|
68
|
+
// characters, so indexes into the folded strings map 1:1 onto the text.
|
|
69
|
+
if (typeof highlightMatch === 'function') {
|
|
70
|
+
var alikiHighlightMatch = highlightMatch;
|
|
71
|
+
highlightMatch = function(text, q) {
|
|
72
|
+
var marked = alikiHighlightMatch(text, q);
|
|
73
|
+
if (marked !== text) return marked; // the vendored code managed
|
|
74
|
+
if (!text || !q || !q.original) return marked;
|
|
75
|
+
var canonText = text.toLowerCase().replace(/\.#/g, '?.');
|
|
76
|
+
var canonQuery = q.original.toLowerCase().replace(/\.#/g, '?.');
|
|
77
|
+
if (!canonQuery) return marked;
|
|
78
|
+
var start = canonText.indexOf(canonQuery);
|
|
79
|
+
if (start === -1) return marked;
|
|
80
|
+
var end = start + canonQuery.length;
|
|
81
|
+
return text.substring(0, start) +
|
|
82
|
+
'\u0001' + text.substring(start, end) + '\u0002' +
|
|
83
|
+
text.substring(end);
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function versionHref(version, path) {
|
|
88
|
+
return search_version_base + version + '/' + path;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function compareVersions(a, b) {
|
|
92
|
+
var as = a.split('.');
|
|
93
|
+
var bs = b.split('.');
|
|
94
|
+
for (var i = 0; i < Math.max(as.length, bs.length); i++) {
|
|
95
|
+
var d = parseInt(as[i] || '0', 10) - parseInt(bs[i] || '0', 10);
|
|
96
|
+
if (d) return d;
|
|
97
|
+
}
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ranker の formatResult は {title, path, type} だけを結果に残し、統合 index の
|
|
102
|
+
// versions を落とす(vendored ファイルは verbatim 維持)。path から versions を
|
|
103
|
+
// 引けるよう対応表を作る。同じ path が複数エントリに現れる稀なケース
|
|
104
|
+
// (版によって type 等が違う)は、ページとしては存在する版の和集合が正しい
|
|
105
|
+
function buildVersionsByPath() {
|
|
106
|
+
var map = {};
|
|
107
|
+
for (var i = 0; i < search_data.index.length; i++) {
|
|
108
|
+
var e = search_data.index[i];
|
|
109
|
+
var list = map[e.path] || (map[e.path] = []);
|
|
110
|
+
var vs = e.versions || [];
|
|
111
|
+
for (var j = 0; j < vs.length; j++) {
|
|
112
|
+
if (list.indexOf(vs[j]) < 0) list.push(vs[j]);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return map;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// rurema-search compatibility: accept ?q=, its ?query= parameter, and its
|
|
119
|
+
// /query:<word>/ path form (the latter only reaches this page when the
|
|
120
|
+
// server rewrites unknown /ja/search/* paths here).
|
|
121
|
+
function initialQuery() {
|
|
122
|
+
var q = null;
|
|
123
|
+
if (typeof URLSearchParams !== 'undefined') {
|
|
124
|
+
var params = new URLSearchParams(window.location.search);
|
|
125
|
+
q = params.get('q') || params.get('query');
|
|
126
|
+
}
|
|
127
|
+
if (!q) {
|
|
128
|
+
var m = window.location.pathname.match(/\/query:([^\/]+)/);
|
|
129
|
+
if (m) {
|
|
130
|
+
try {
|
|
131
|
+
q = decodeURIComponent(m[1]);
|
|
132
|
+
} catch (e) {
|
|
133
|
+
q = m[1];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return q;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function init() {
|
|
141
|
+
var input = document.querySelector('#search-field');
|
|
142
|
+
var result = document.querySelector('#search-results');
|
|
143
|
+
if (!input || !result) return;
|
|
144
|
+
|
|
145
|
+
var search = new SearchController(search_data, input, result);
|
|
146
|
+
var versionsByPath = buildVersionsByPath();
|
|
147
|
+
|
|
148
|
+
search.renderItem = function(r) {
|
|
149
|
+
var li = document.createElement('li');
|
|
150
|
+
var versions = (versionsByPath[r.path] || [])
|
|
151
|
+
.slice().sort(compareVersions).reverse(); // newest first
|
|
152
|
+
var newest = versions[0] || '';
|
|
153
|
+
var html = '<p class="search-match"><a href="' +
|
|
154
|
+
this.escapeHTML(versionHref(newest, r.path)) + '">' +
|
|
155
|
+
this.hlt(r.title) + '</a>';
|
|
156
|
+
if (r.type) {
|
|
157
|
+
var typeClass = r.type.replace(/_/g, '-');
|
|
158
|
+
html += '<span class="search-type search-type-' +
|
|
159
|
+
this.escapeHTML(typeClass) + '">' + this.formatType(r.type) + '</span>';
|
|
160
|
+
}
|
|
161
|
+
html += '</p><p class="search-versions">';
|
|
162
|
+
for (var i = 0; i < versions.length; i++) {
|
|
163
|
+
html += '<a href="' + this.escapeHTML(versionHref(versions[i], r.path)) +
|
|
164
|
+
'">' + this.escapeHTML(versions[i]) + '</a>';
|
|
165
|
+
}
|
|
166
|
+
html += '</p>';
|
|
167
|
+
li.innerHTML = html;
|
|
168
|
+
return li;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
search.formatType = function(type) {
|
|
172
|
+
var labels = {
|
|
173
|
+
'class': 'class', 'module': 'module', 'object': 'object',
|
|
174
|
+
'constant': 'const', 'instance_method': 'method', 'class_method': 'method',
|
|
175
|
+
'variable': 'var', 'library': 'lib', 'document': 'doc', 'function': 'func',
|
|
176
|
+
'heading': 'section'
|
|
177
|
+
};
|
|
178
|
+
return labels[type] || type;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
search.select = function(selected) {
|
|
182
|
+
if (selected) window.location.href = selected.firstChild.firstChild.href;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// Keep the query shareable: mirror the box into the ?q= parameter.
|
|
186
|
+
input.addEventListener('input', function() {
|
|
187
|
+
var q = input.value.trim();
|
|
188
|
+
var url = q ? '?q=' + encodeURIComponent(q) : window.location.pathname;
|
|
189
|
+
window.history.replaceState(null, '', url);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
var q = initialQuery();
|
|
193
|
+
if (q) {
|
|
194
|
+
input.value = q;
|
|
195
|
+
window.history.replaceState(null, '', '?q=' + encodeURIComponent(q));
|
|
196
|
+
search.search(q, false);
|
|
197
|
+
}
|
|
198
|
+
input.focus();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
202
|
+
})();
|