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,244 @@
|
|
|
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
|
+
* Aliki Search Implementation
|
|
8
|
+
*
|
|
9
|
+
* Search algorithm with the following priorities:
|
|
10
|
+
* 1. Exact full_name match always wins (for namespace/method queries)
|
|
11
|
+
* 2. Exact name match gets high priority
|
|
12
|
+
* 3. Match types:
|
|
13
|
+
* - Namespace queries (::) and method queries (# or .) match against full_name
|
|
14
|
+
* - Regular queries match against unqualified name
|
|
15
|
+
* - Prefix (10000) > substring (5000) > fuzzy (1000)
|
|
16
|
+
* 4. First character determines type priority:
|
|
17
|
+
* - Starts with lowercase: methods first
|
|
18
|
+
* - Starts with uppercase: classes/modules/constants first
|
|
19
|
+
* 5. Within same type priority:
|
|
20
|
+
* - Unqualified match > qualified match
|
|
21
|
+
* - Shorter name > longer name
|
|
22
|
+
* 6. Class methods > instance methods
|
|
23
|
+
* 7. Result limit: 30
|
|
24
|
+
* 8. Minimum query length: 1 character
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
var MAX_RESULTS = 30;
|
|
28
|
+
var MIN_QUERY_LENGTH = 1;
|
|
29
|
+
|
|
30
|
+
/*
|
|
31
|
+
* Scoring constants - organized in tiers where each tier dominates lower tiers.
|
|
32
|
+
* This ensures match type always beats type priority, etc.
|
|
33
|
+
*
|
|
34
|
+
* Tier 0: Exact matches (immediate return)
|
|
35
|
+
* Tier 1: Match type (prefix > substring > fuzzy)
|
|
36
|
+
* Tier 2: Exact name bonus
|
|
37
|
+
* Tier 3: Type priority (method vs class based on query case)
|
|
38
|
+
* Tier 4: Minor bonuses (top-level, class method, name length)
|
|
39
|
+
*/
|
|
40
|
+
var SCORE_EXACT_FULL_NAME = 1000000; // Tier 0: Query exactly matches full_name
|
|
41
|
+
var SCORE_MATCH_PREFIX = 10000; // Tier 1: Query is prefix of name
|
|
42
|
+
var SCORE_MATCH_SUBSTRING = 5000; // Tier 1: Query is substring of name
|
|
43
|
+
var SCORE_MATCH_FUZZY = 1000; // Tier 1: Query chars appear in order
|
|
44
|
+
var SCORE_EXACT_NAME = 500; // Tier 2: Name exactly equals query
|
|
45
|
+
var SCORE_TYPE_PRIORITY = 100; // Tier 3: Preferred type (method/class)
|
|
46
|
+
var SCORE_TOP_LEVEL = 50; // Tier 4: Top-level over namespaced
|
|
47
|
+
var SCORE_CLASS_METHOD = 10; // Tier 4: Class method over instance method
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if all characters in query appear in order in target
|
|
51
|
+
* e.g., "addalias" fuzzy matches "add_foo_alias"
|
|
52
|
+
*/
|
|
53
|
+
function fuzzyMatch(target, query) {
|
|
54
|
+
var ti = 0;
|
|
55
|
+
for (var qi = 0; qi < query.length; qi++) {
|
|
56
|
+
ti = target.indexOf(query[qi], ti);
|
|
57
|
+
if (ti === -1) return false;
|
|
58
|
+
ti++;
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Parse and normalize a search query
|
|
65
|
+
* @param {string} query - The raw search query
|
|
66
|
+
* @returns {Object} Parsed query with normalized form and flags
|
|
67
|
+
*/
|
|
68
|
+
function parseQuery(query) {
|
|
69
|
+
// Lowercase for case-insensitive matching (so "hash" finds both Hash class and #hash methods)
|
|
70
|
+
var normalized = query.toLowerCase();
|
|
71
|
+
var isNamespaceQuery = query.includes('::');
|
|
72
|
+
var isMethodQuery = query.includes('#') || query.includes('.');
|
|
73
|
+
|
|
74
|
+
// Normalize . to :: (RDoc uses :: for class methods in full_name)
|
|
75
|
+
if (query.includes('.')) {
|
|
76
|
+
normalized = normalized.replace(/\./g, '::');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
original: query,
|
|
81
|
+
normalized: normalized,
|
|
82
|
+
isNamespaceQuery: isNamespaceQuery,
|
|
83
|
+
isMethodQuery: isMethodQuery,
|
|
84
|
+
// Namespace and method queries match against full_name instead of name
|
|
85
|
+
matchesFullName: isNamespaceQuery || isMethodQuery,
|
|
86
|
+
// If query starts with lowercase, prioritize methods; otherwise prioritize classes/modules/constants
|
|
87
|
+
prioritizeMethod: !/^[A-Z]/.test(query)
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Main search function
|
|
93
|
+
* @param {string} query - The search query
|
|
94
|
+
* @param {Array} index - The search index to search in
|
|
95
|
+
* @returns {Array} Array of matching entries, sorted by relevance
|
|
96
|
+
*/
|
|
97
|
+
function search(query, index) {
|
|
98
|
+
if (!query || query.length < MIN_QUERY_LENGTH) {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
var q = parseQuery(query);
|
|
103
|
+
var results = [];
|
|
104
|
+
|
|
105
|
+
for (var i = 0; i < index.length; i++) {
|
|
106
|
+
var entry = index[i];
|
|
107
|
+
var score = computeScore(entry, q);
|
|
108
|
+
|
|
109
|
+
if (score !== null) {
|
|
110
|
+
results.push({ entry: entry, score: score });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
results.sort(function(a, b) {
|
|
115
|
+
return b.score - a.score;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return results.slice(0, MAX_RESULTS).map(function(r) {
|
|
119
|
+
return r.entry;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Compute the relevance score for an entry
|
|
125
|
+
* @param {Object} entry - The search index entry
|
|
126
|
+
* @param {Object} q - Parsed query from parseQuery()
|
|
127
|
+
* @returns {number|null} Score or null if no match
|
|
128
|
+
*/
|
|
129
|
+
function computeScore(entry, q) {
|
|
130
|
+
var name = entry.name;
|
|
131
|
+
var fullName = entry.full_name;
|
|
132
|
+
var type = entry.type;
|
|
133
|
+
|
|
134
|
+
var nameLower = name.toLowerCase();
|
|
135
|
+
var fullNameLower = fullName.toLowerCase();
|
|
136
|
+
|
|
137
|
+
// Exact full_name match (e.g., "Array#filter" matches Array#filter)
|
|
138
|
+
if (q.matchesFullName && fullNameLower === q.normalized) {
|
|
139
|
+
return SCORE_EXACT_FULL_NAME;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
var matchScore = 0;
|
|
143
|
+
var target = q.matchesFullName ? fullNameLower : nameLower;
|
|
144
|
+
|
|
145
|
+
if (target.startsWith(q.normalized)) {
|
|
146
|
+
matchScore = SCORE_MATCH_PREFIX; // Prefix (e.g., "Arr" matches "Array")
|
|
147
|
+
} else if (target.includes(q.normalized)) {
|
|
148
|
+
matchScore = SCORE_MATCH_SUBSTRING; // Substring (e.g., "ray" matches "Array")
|
|
149
|
+
} else if (fuzzyMatch(target, q.normalized)) {
|
|
150
|
+
matchScore = SCORE_MATCH_FUZZY; // Fuzzy (e.g., "addalias" matches "add_foo_alias")
|
|
151
|
+
} else {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
var score = matchScore;
|
|
156
|
+
var isMethod = (type === 'instance_method' || type === 'class_method');
|
|
157
|
+
|
|
158
|
+
if (q.prioritizeMethod ? isMethod : !isMethod) {
|
|
159
|
+
score += SCORE_TYPE_PRIORITY;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (type === 'class_method') score += SCORE_CLASS_METHOD;
|
|
163
|
+
if (name === fullName) score += SCORE_TOP_LEVEL; // Top-level (Hash) > namespaced (Foo::Hash)
|
|
164
|
+
if (nameLower === q.normalized) score += SCORE_EXACT_NAME; // Exact name match
|
|
165
|
+
score -= name.length;
|
|
166
|
+
|
|
167
|
+
return score;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* SearchRanker class for compatibility with the Search UI
|
|
172
|
+
* Provides ready() and find() interface
|
|
173
|
+
*/
|
|
174
|
+
function SearchRanker(index) {
|
|
175
|
+
this.index = index;
|
|
176
|
+
this.handlers = [];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
SearchRanker.prototype.ready = function(fn) {
|
|
180
|
+
this.handlers.push(fn);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
SearchRanker.prototype.find = function(query) {
|
|
184
|
+
var q = parseQuery(query);
|
|
185
|
+
var rawResults = search(query, this.index);
|
|
186
|
+
var results = rawResults.map(function(entry) {
|
|
187
|
+
return formatResult(entry, q);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
var _this = this;
|
|
191
|
+
this.handlers.forEach(function(fn) {
|
|
192
|
+
fn.call(_this, results, true);
|
|
193
|
+
});
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Format a search result entry for display
|
|
198
|
+
*/
|
|
199
|
+
function formatResult(entry, q) {
|
|
200
|
+
var result = {
|
|
201
|
+
title: highlightMatch(entry.full_name, q),
|
|
202
|
+
path: entry.path,
|
|
203
|
+
type: entry.type
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
if (entry.snippet) {
|
|
207
|
+
result.snippet = entry.snippet;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Add highlight markers (\u0001 and \u0002) to matching portions of text
|
|
215
|
+
* @param {string} text - The text to highlight
|
|
216
|
+
* @param {Object} q - Parsed query from parseQuery()
|
|
217
|
+
*/
|
|
218
|
+
function highlightMatch(text, q) {
|
|
219
|
+
if (!text || !q) return text;
|
|
220
|
+
|
|
221
|
+
var textLower = text.toLowerCase();
|
|
222
|
+
var query = q.normalized;
|
|
223
|
+
|
|
224
|
+
// Try contiguous match first (prefix or substring)
|
|
225
|
+
var matchIndex = textLower.indexOf(query);
|
|
226
|
+
if (matchIndex !== -1) {
|
|
227
|
+
return text.substring(0, matchIndex) +
|
|
228
|
+
'\u0001' + text.substring(matchIndex, matchIndex + query.length) + '\u0002' +
|
|
229
|
+
text.substring(matchIndex + query.length);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Fall back to fuzzy highlight (highlight each matched character)
|
|
233
|
+
var result = '';
|
|
234
|
+
var ti = 0;
|
|
235
|
+
for (var qi = 0; qi < query.length; qi++) {
|
|
236
|
+
var charIndex = textLower.indexOf(query[qi], ti);
|
|
237
|
+
if (charIndex === -1) return text;
|
|
238
|
+
result += text.substring(ti, charIndex);
|
|
239
|
+
result += '\u0001' + text[charIndex] + '\u0002';
|
|
240
|
+
ti = charIndex + 1;
|
|
241
|
+
}
|
|
242
|
+
result += text.substring(ti);
|
|
243
|
+
return result;
|
|
244
|
+
}
|
data/theme/default/script.js
CHANGED
|
@@ -1,6 +1,92 @@
|
|
|
1
1
|
(function() {
|
|
2
|
+
// クリップボードへのコピー。Clipboard API が使えない環境
|
|
3
|
+
// (非セキュアコンテキストや古いブラウザ)では従来の
|
|
4
|
+
// textarea + execCommand にフォールバックする
|
|
5
|
+
function writeClipboard(text) {
|
|
6
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
7
|
+
return navigator.clipboard.writeText(text)
|
|
8
|
+
}
|
|
9
|
+
return new Promise(function(resolve, reject) {
|
|
10
|
+
const textarea = document.createElement('textarea')
|
|
11
|
+
textarea.setAttribute('class', 'highlight__copy-text')
|
|
12
|
+
textarea.value = text
|
|
13
|
+
document.body.appendChild(textarea)
|
|
14
|
+
textarea.select()
|
|
15
|
+
const ok = document.execCommand('copy')
|
|
16
|
+
document.body.removeChild(textarea)
|
|
17
|
+
if (ok) {
|
|
18
|
+
resolve()
|
|
19
|
+
} else {
|
|
20
|
+
reject(new Error('copy command failed'))
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function hasClass(elem, name) {
|
|
26
|
+
return Boolean(elem && elem.className &&
|
|
27
|
+
String(elem.className).split(' ').indexOf(name) >= 0)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function findChildByClass(elem, name) {
|
|
31
|
+
const children = elem.children
|
|
32
|
+
for (let i = 0; i < children.length; i++) {
|
|
33
|
+
if (hasClass(children[i], name)) return children[i]
|
|
34
|
+
}
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// elem(pre)の直前にツールバー行 <div class="highlight__toolbar"> を
|
|
39
|
+
// 用意し、その右端のボタン置き場(highlight__button-group)を返す。
|
|
40
|
+
// ボタンを pre の中に float させる方式は、カーソル形状のちらつき・
|
|
41
|
+
// 編集時のレイアウト崩れ・pre の上 padding を 0 にする必要など無理が
|
|
42
|
+
// 多かったため、pre の外に出している。コンパイラが pre の直前に置く
|
|
43
|
+
// caption(タブ)があればツールバー左端に取り込み、pre への密着を保つ。
|
|
44
|
+
// js/run.js も同じボタン置き場に RUN ボタンを入れる
|
|
45
|
+
function buttonGroup(elem) {
|
|
46
|
+
const prev = elem.previousElementSibling
|
|
47
|
+
if (hasClass(prev, 'highlight__toolbar')) {
|
|
48
|
+
return findChildByClass(prev, 'highlight__button-group')
|
|
49
|
+
}
|
|
50
|
+
const toolbar = document.createElement('div')
|
|
51
|
+
toolbar.setAttribute('class', 'highlight__toolbar')
|
|
52
|
+
const group = document.createElement('span')
|
|
53
|
+
group.setAttribute('class', 'highlight__button-group')
|
|
54
|
+
if (hasClass(prev, 'caption')) {
|
|
55
|
+
elem.parentNode.insertBefore(toolbar, prev)
|
|
56
|
+
toolbar.appendChild(prev) // caption をタブとして左端へ移動
|
|
57
|
+
} else {
|
|
58
|
+
elem.parentNode.insertBefore(toolbar, elem)
|
|
59
|
+
}
|
|
60
|
+
toolbar.appendChild(group)
|
|
61
|
+
return group
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// elem の直前のツールバーに COPY ボタンを付ける。getText はクリック時に
|
|
65
|
+
// 評価されるので、RUN の実行結果のように内容が変わる要素にも使える。
|
|
66
|
+
// elem は DOM ツリーに挿入済みであること(直前にツールバーを差し込む)
|
|
67
|
+
function addCopyButton(elem, getText) {
|
|
68
|
+
const btn = document.createElement('span')
|
|
69
|
+
btn.setAttribute('class', 'highlight__copy-button')
|
|
70
|
+
btn.onclick = function(){
|
|
71
|
+
writeClipboard(getText()).then(function() {
|
|
72
|
+
btn.classList.add("copied")
|
|
73
|
+
window.setTimeout(function() { btn.classList.remove("copied") }, 1000)
|
|
74
|
+
}).catch(function() {
|
|
75
|
+
// コピー失敗時は従来どおり何も表示しない
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
// COPY は常にグループ右端(RUN は run.js が先頭に prepend する)
|
|
79
|
+
buttonGroup(elem).appendChild(btn)
|
|
80
|
+
return btn
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// RUN 出力(js/run.js)など、後から動的に生成される pre 用の公開フック
|
|
84
|
+
window.ruremaAddCopyButton = addCopyButton
|
|
85
|
+
|
|
2
86
|
window.onload = function() {
|
|
3
|
-
|
|
87
|
+
// 言語指定なしのコードブロックは class を持たない素の <pre> になるため、
|
|
88
|
+
// highlight クラスではなく pre 要素全体に COPY ボタンを付ける
|
|
89
|
+
const elems = document.querySelectorAll('pre')
|
|
4
90
|
|
|
5
91
|
let tempDiv = document.createElement('div')
|
|
6
92
|
|
|
@@ -11,23 +97,10 @@
|
|
|
11
97
|
const caption = tempDiv.getElementsByClassName("caption")[0]
|
|
12
98
|
if (caption) tempDiv.removeChild(caption)
|
|
13
99
|
|
|
14
|
-
//
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
elem.appendChild(copyText)
|
|
19
|
-
|
|
20
|
-
// COPY button
|
|
21
|
-
const btn = document.createElement('span')
|
|
22
|
-
btn.setAttribute('class', 'highlight__copy-button')
|
|
23
|
-
elem.insertBefore(btn, elem.firstChild)
|
|
24
|
-
|
|
25
|
-
btn.onclick = function(){
|
|
26
|
-
copyText.select()
|
|
27
|
-
document.execCommand("copy")
|
|
28
|
-
btn.classList.add("copied")
|
|
29
|
-
window.setTimeout(function() { btn.classList.remove("copied") }, 1000)
|
|
30
|
-
}
|
|
100
|
+
// RUN ボタンでサンプルを編集しても COPY は元のテキストを保持する
|
|
101
|
+
const text = tempDiv.textContent.replace(/^\n+/, "").replace(/\n{2,}$/, "\n")
|
|
102
|
+
|
|
103
|
+
addCopyButton(elem, function() { return text })
|
|
31
104
|
}
|
|
32
105
|
)
|
|
33
106
|
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/* Client-side search box (statichtml output).
|
|
2
|
+
Styled to match rurema's theme (accent color #33a). */
|
|
3
|
+
|
|
4
|
+
/* Full-width sticky top bar: manual-home link on the left, search on the right.
|
|
5
|
+
Negative margins break out of the body's 20px top / 2% side margins so the
|
|
6
|
+
bar spans the viewport; the matching padding keeps its content aligned with
|
|
7
|
+
the page body. */
|
|
8
|
+
#rurema-topbar {
|
|
9
|
+
position: sticky;
|
|
10
|
+
top: 0;
|
|
11
|
+
z-index: 1000;
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: space-between;
|
|
15
|
+
gap: 1em;
|
|
16
|
+
margin: -20px -2% 1em;
|
|
17
|
+
padding: 6px 2%;
|
|
18
|
+
background: #f7f7fb;
|
|
19
|
+
border-bottom: 2px solid #33a;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#rurema-brand {
|
|
23
|
+
font-size: 0.95em;
|
|
24
|
+
white-space: nowrap;
|
|
25
|
+
overflow: hidden;
|
|
26
|
+
text-overflow: ellipsis;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#search-section {
|
|
30
|
+
position: relative;
|
|
31
|
+
flex: 0 0 auto;
|
|
32
|
+
width: 26em;
|
|
33
|
+
max-width: 50%;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#search-field {
|
|
37
|
+
width: 100%;
|
|
38
|
+
box-sizing: border-box;
|
|
39
|
+
padding: 0.35em 0.6em;
|
|
40
|
+
font-size: 0.95em;
|
|
41
|
+
border: 1px solid #99a;
|
|
42
|
+
border-radius: 4px;
|
|
43
|
+
background: #fff;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#search-field:focus {
|
|
47
|
+
outline: none;
|
|
48
|
+
border-color: #33a;
|
|
49
|
+
box-shadow: 0 0 0 2px rgba(51, 51, 170, 0.25);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.search-results {
|
|
53
|
+
list-style: none;
|
|
54
|
+
margin: 0.25em 0 0;
|
|
55
|
+
padding: 0;
|
|
56
|
+
position: absolute;
|
|
57
|
+
top: 100%;
|
|
58
|
+
left: 0;
|
|
59
|
+
right: 0;
|
|
60
|
+
background: #fff;
|
|
61
|
+
border: 1px solid #33a;
|
|
62
|
+
border-radius: 4px;
|
|
63
|
+
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
|
|
64
|
+
max-height: 75vh;
|
|
65
|
+
overflow-y: auto;
|
|
66
|
+
z-index: 1001;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.search-results[aria-expanded="false"] {
|
|
70
|
+
display: none;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.search-results li {
|
|
74
|
+
padding: 0.35em 0.6em;
|
|
75
|
+
border-bottom: 1px solid #eee;
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
line-height: 1.25;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.search-results li:last-child {
|
|
81
|
+
border-bottom: none;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.search-results li.search-selected {
|
|
85
|
+
background: #e8eaf6; /* light tint of #33a */
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.search-results .search-match {
|
|
89
|
+
margin: 0;
|
|
90
|
+
font-weight: normal;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.search-results em {
|
|
94
|
+
font-style: normal;
|
|
95
|
+
background: #ff9;
|
|
96
|
+
border-radius: 2px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* On hover the global `a:hover` rule paints the whole anchor with the #33a
|
|
100
|
+
accent and white text. Round the anchor (and pad it slightly) so its
|
|
101
|
+
background follows the rounded highlight instead of poking square corners
|
|
102
|
+
out behind it, and recolor the highlighted match so the yellow background
|
|
103
|
+
stays legible against the white-on-blue hover text. */
|
|
104
|
+
.search-results .search-match > a {
|
|
105
|
+
padding: 1px;
|
|
106
|
+
border-radius: 3px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.search-results .search-match > a:hover > em {
|
|
110
|
+
color: #33a;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.search-type {
|
|
114
|
+
display: inline-block;
|
|
115
|
+
margin-left: 0.5em;
|
|
116
|
+
padding: 0 0.4em;
|
|
117
|
+
font-size: 0.75em;
|
|
118
|
+
color: #fff;
|
|
119
|
+
background: #33a;
|
|
120
|
+
border-radius: 3px;
|
|
121
|
+
vertical-align: middle;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.search-snippet {
|
|
125
|
+
color: #555;
|
|
126
|
+
font-size: 0.85em;
|
|
127
|
+
margin-top: 0.15em;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@media only screen and (max-width: 768px) {
|
|
131
|
+
#rurema-topbar {
|
|
132
|
+
flex-wrap: wrap;
|
|
133
|
+
gap: 0.4em;
|
|
134
|
+
}
|
|
135
|
+
#search-section {
|
|
136
|
+
width: 100%;
|
|
137
|
+
max-width: none;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
html {
|
|
142
|
+
scroll-padding-top: 3.5rem;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
@media only screen and (max-width: 768px) {
|
|
146
|
+
html {
|
|
147
|
+
scroll-padding-top: 6rem;
|
|
148
|
+
}
|
|
149
|
+
}
|