docyard 0.7.0 → 0.8.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/.rubocop.yml +5 -1
- data/CHANGELOG.md +20 -1
- data/lib/docyard/build/asset_bundler.rb +22 -7
- data/lib/docyard/build/file_copier.rb +49 -27
- data/lib/docyard/build/sitemap_generator.rb +6 -6
- data/lib/docyard/build/static_generator.rb +85 -12
- data/lib/docyard/builder.rb +6 -6
- data/lib/docyard/config/branding_resolver.rb +126 -17
- data/lib/docyard/config/constants.rb +6 -4
- data/lib/docyard/config/validator.rb +122 -99
- data/lib/docyard/config.rb +36 -43
- data/lib/docyard/initializer.rb +15 -76
- data/lib/docyard/navigation/breadcrumb_builder.rb +133 -0
- data/lib/docyard/navigation/prev_next_builder.rb +4 -1
- data/lib/docyard/navigation/sidebar/children_discoverer.rb +51 -0
- data/lib/docyard/navigation/sidebar/config_parser.rb +136 -108
- data/lib/docyard/navigation/sidebar/file_resolver.rb +78 -0
- data/lib/docyard/navigation/sidebar/file_system_scanner.rb +2 -1
- data/lib/docyard/navigation/sidebar/item.rb +45 -7
- data/lib/docyard/navigation/sidebar/local_config_loader.rb +51 -0
- data/lib/docyard/navigation/sidebar/metadata_extractor.rb +69 -0
- data/lib/docyard/navigation/sidebar/metadata_reader.rb +47 -0
- data/lib/docyard/navigation/sidebar/path_prefixer.rb +34 -0
- data/lib/docyard/navigation/sidebar/renderer.rb +55 -37
- data/lib/docyard/navigation/sidebar/sorter.rb +21 -0
- data/lib/docyard/navigation/sidebar/tree_builder.rb +99 -26
- data/lib/docyard/navigation/sidebar/tree_filter.rb +55 -0
- data/lib/docyard/navigation/sidebar_builder.rb +105 -36
- data/lib/docyard/rendering/icon_helpers.rb +13 -0
- data/lib/docyard/rendering/icons/phosphor.rb +23 -1
- data/lib/docyard/rendering/markdown.rb +5 -0
- data/lib/docyard/rendering/renderer.rb +74 -34
- data/lib/docyard/rendering/template_resolver.rb +172 -0
- data/lib/docyard/routing/fallback_resolver.rb +92 -0
- data/lib/docyard/search/build_indexer.rb +1 -1
- data/lib/docyard/search/dev_indexer.rb +51 -6
- data/lib/docyard/search/pagefind_support.rb +2 -0
- data/lib/docyard/server/asset_handler.rb +24 -19
- data/lib/docyard/server/pagefind_handler.rb +63 -0
- data/lib/docyard/server/preview_server.rb +1 -1
- data/lib/docyard/server/rack_application.rb +81 -64
- data/lib/docyard/templates/assets/css/code.css +18 -51
- data/lib/docyard/templates/assets/css/components/breadcrumbs.css +143 -0
- data/lib/docyard/templates/assets/css/components/callout.css +67 -67
- data/lib/docyard/templates/assets/css/components/code-block.css +180 -282
- data/lib/docyard/templates/assets/css/components/heading-anchor.css +28 -15
- data/lib/docyard/templates/assets/css/components/icon.css +0 -1
- data/lib/docyard/templates/assets/css/components/logo.css +0 -2
- data/lib/docyard/templates/assets/css/components/nav-menu.css +237 -0
- data/lib/docyard/templates/assets/css/components/navigation.css +186 -167
- data/lib/docyard/templates/assets/css/components/prev-next.css +76 -47
- data/lib/docyard/templates/assets/css/components/search.css +186 -174
- data/lib/docyard/templates/assets/css/components/tab-bar.css +163 -0
- data/lib/docyard/templates/assets/css/components/table-of-contents.css +127 -114
- data/lib/docyard/templates/assets/css/components/tabs.css +119 -160
- data/lib/docyard/templates/assets/css/components/theme-toggle.css +48 -44
- data/lib/docyard/templates/assets/css/landing.css +815 -0
- data/lib/docyard/templates/assets/css/layout.css +489 -87
- data/lib/docyard/templates/assets/css/main.css +1 -3
- data/lib/docyard/templates/assets/css/markdown.css +111 -93
- data/lib/docyard/templates/assets/css/reset.css +0 -3
- data/lib/docyard/templates/assets/css/typography.css +43 -41
- data/lib/docyard/templates/assets/css/variables.css +268 -208
- data/lib/docyard/templates/assets/favicon.svg +7 -8
- data/lib/docyard/templates/assets/fonts/Inter-Variable.ttf +0 -0
- data/lib/docyard/templates/assets/js/components/code-block.js +24 -42
- data/lib/docyard/templates/assets/js/components/heading-anchor.js +26 -24
- data/lib/docyard/templates/assets/js/components/navigation.js +181 -70
- data/lib/docyard/templates/assets/js/components/search.js +0 -75
- data/lib/docyard/templates/assets/js/components/sidebar-toggle.js +29 -0
- data/lib/docyard/templates/assets/js/components/tab-navigation.js +145 -0
- data/lib/docyard/templates/assets/js/components/table-of-contents.js +153 -66
- data/lib/docyard/templates/assets/js/components/tabs.js +31 -69
- data/lib/docyard/templates/assets/js/theme.js +0 -3
- data/lib/docyard/templates/assets/logo-dark.svg +8 -2
- data/lib/docyard/templates/assets/logo.svg +7 -4
- data/lib/docyard/templates/config/docyard.yml.erb +37 -34
- data/lib/docyard/templates/errors/404.html.erb +1 -1
- data/lib/docyard/templates/errors/500.html.erb +1 -1
- data/lib/docyard/templates/layouts/default.html.erb +18 -67
- data/lib/docyard/templates/layouts/splash.html.erb +176 -0
- data/lib/docyard/templates/partials/_breadcrumbs.html.erb +24 -0
- data/lib/docyard/templates/partials/_code_block.html.erb +5 -3
- data/lib/docyard/templates/partials/_doc_footer.html.erb +25 -0
- data/lib/docyard/templates/partials/_features.html.erb +15 -0
- data/lib/docyard/templates/partials/_footer.html.erb +42 -0
- data/lib/docyard/templates/partials/_head.html.erb +22 -0
- data/lib/docyard/templates/partials/_header.html.erb +49 -0
- data/lib/docyard/templates/partials/_heading_anchor.html.erb +3 -1
- data/lib/docyard/templates/partials/_hero.html.erb +27 -0
- data/lib/docyard/templates/partials/_nav_group.html.erb +25 -11
- data/lib/docyard/templates/partials/_nav_leaf.html.erb +1 -1
- data/lib/docyard/templates/partials/_nav_menu.html.erb +42 -0
- data/lib/docyard/templates/partials/_nav_nested_section.html.erb +11 -0
- data/lib/docyard/templates/partials/_nav_section.html.erb +1 -1
- data/lib/docyard/templates/partials/_prev_next.html.erb +8 -2
- data/lib/docyard/templates/partials/_scripts.html.erb +7 -0
- data/lib/docyard/templates/partials/_search_modal.html.erb +2 -6
- data/lib/docyard/templates/partials/_search_trigger.html.erb +2 -6
- data/lib/docyard/templates/partials/_sidebar.html.erb +21 -4
- data/lib/docyard/templates/partials/_tab_bar.html.erb +25 -0
- data/lib/docyard/templates/partials/_table_of_contents.html.erb +12 -12
- data/lib/docyard/templates/partials/_table_of_contents_toggle.html.erb +1 -3
- data/lib/docyard/templates/partials/_tabs.html.erb +2 -2
- data/lib/docyard/templates/partials/_theme_toggle.html.erb +2 -11
- data/lib/docyard/version.rb +1 -1
- metadata +33 -5
- data/lib/docyard/templates/markdown/getting-started/installation.md.erb +0 -77
- data/lib/docyard/templates/markdown/guides/configuration.md.erb +0 -202
- data/lib/docyard/templates/markdown/guides/markdown-features.md.erb +0 -247
- data/lib/docyard/templates/markdown/index.md.erb +0 -82
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SearchManager - Handles search functionality with Pagefind integration
|
|
3
|
-
*
|
|
4
|
-
* Features:
|
|
5
|
-
* - Cmd+K / Ctrl+K keyboard shortcut
|
|
6
|
-
* - Lazy loading of Pagefind
|
|
7
|
-
* - Keyboard navigation in results
|
|
8
|
-
* - Debounced search input
|
|
9
|
-
*
|
|
10
|
-
* @class SearchManager
|
|
11
|
-
*/
|
|
12
1
|
class SearchManager {
|
|
13
2
|
constructor() {
|
|
14
3
|
this.modal = document.querySelector('[data-search-modal]');
|
|
@@ -32,7 +21,6 @@ class SearchManager {
|
|
|
32
21
|
this.DEBOUNCE_DELAY = 150;
|
|
33
22
|
this.RESULTS_PER_PAGE = 6;
|
|
34
23
|
|
|
35
|
-
// State for "load more" functionality
|
|
36
24
|
this.allSearchResults = [];
|
|
37
25
|
this.displayedCount = 0;
|
|
38
26
|
this.currentQuery = '';
|
|
@@ -52,36 +40,29 @@ class SearchManager {
|
|
|
52
40
|
}
|
|
53
41
|
|
|
54
42
|
attachEventListeners() {
|
|
55
|
-
// Global keyboard shortcut
|
|
56
43
|
document.addEventListener('keydown', this.handleKeyDown);
|
|
57
44
|
|
|
58
|
-
// Trigger button
|
|
59
45
|
if (this.trigger) {
|
|
60
46
|
this.trigger.addEventListener('click', () => this.open());
|
|
61
47
|
}
|
|
62
48
|
|
|
63
|
-
// Backdrop click to close
|
|
64
49
|
if (this.backdrop) {
|
|
65
50
|
this.backdrop.addEventListener('click', () => this.close());
|
|
66
51
|
}
|
|
67
52
|
|
|
68
|
-
// Close button
|
|
69
53
|
if (this.closeButton) {
|
|
70
54
|
this.closeButton.addEventListener('click', () => this.close());
|
|
71
55
|
}
|
|
72
56
|
|
|
73
|
-
// Clear button
|
|
74
57
|
if (this.clearButton) {
|
|
75
58
|
this.clearButton.addEventListener('click', () => this.clearSearch());
|
|
76
59
|
}
|
|
77
60
|
|
|
78
|
-
// Search input
|
|
79
61
|
if (this.input) {
|
|
80
62
|
this.input.addEventListener('input', this.handleInput);
|
|
81
63
|
this.input.addEventListener('keydown', (e) => this.handleInputKeyDown(e));
|
|
82
64
|
}
|
|
83
65
|
|
|
84
|
-
// Results click delegation
|
|
85
66
|
if (this.resultsContainer) {
|
|
86
67
|
this.resultsContainer.addEventListener('click', this.handleResultClick);
|
|
87
68
|
}
|
|
@@ -99,21 +80,18 @@ class SearchManager {
|
|
|
99
80
|
}
|
|
100
81
|
|
|
101
82
|
handleKeyDown(event) {
|
|
102
|
-
// Cmd+K or Ctrl+K to open
|
|
103
83
|
if ((event.metaKey || event.ctrlKey) && event.key === 'k') {
|
|
104
84
|
event.preventDefault();
|
|
105
85
|
this.toggle();
|
|
106
86
|
return;
|
|
107
87
|
}
|
|
108
88
|
|
|
109
|
-
// Escape to close
|
|
110
89
|
if (event.key === 'Escape' && this.isOpen) {
|
|
111
90
|
event.preventDefault();
|
|
112
91
|
this.close();
|
|
113
92
|
return;
|
|
114
93
|
}
|
|
115
94
|
|
|
116
|
-
// Forward slash to focus search (when not in an input)
|
|
117
95
|
if (event.key === '/' && !this.isOpen && !this.isInputFocused()) {
|
|
118
96
|
event.preventDefault();
|
|
119
97
|
this.open();
|
|
@@ -140,12 +118,10 @@ class SearchManager {
|
|
|
140
118
|
handleInput(event) {
|
|
141
119
|
const query = event.target.value.trim();
|
|
142
120
|
|
|
143
|
-
// Update clear button visibility
|
|
144
121
|
if (this.clearButton) {
|
|
145
122
|
this.clearButton.hidden = query.length === 0;
|
|
146
123
|
}
|
|
147
124
|
|
|
148
|
-
// Debounce search
|
|
149
125
|
if (this.searchTimeout) {
|
|
150
126
|
clearTimeout(this.searchTimeout);
|
|
151
127
|
}
|
|
@@ -195,10 +171,8 @@ class SearchManager {
|
|
|
195
171
|
this.modal.hidden = false;
|
|
196
172
|
document.body.style.overflow = 'hidden';
|
|
197
173
|
|
|
198
|
-
// Trigger animation on next frame
|
|
199
174
|
requestAnimationFrame(() => {
|
|
200
175
|
this.modal.classList.add('is-open');
|
|
201
|
-
// Double rAF ensures focus works after CSS transition starts
|
|
202
176
|
requestAnimationFrame(() => {
|
|
203
177
|
if (this.input) {
|
|
204
178
|
this.input.focus();
|
|
@@ -206,7 +180,6 @@ class SearchManager {
|
|
|
206
180
|
});
|
|
207
181
|
});
|
|
208
182
|
|
|
209
|
-
// Initialize Pagefind if not already done
|
|
210
183
|
if (!this.pagefind) {
|
|
211
184
|
await this.initPagefind();
|
|
212
185
|
}
|
|
@@ -220,18 +193,15 @@ class SearchManager {
|
|
|
220
193
|
document.body.style.overflow = '';
|
|
221
194
|
this.selectedIndex = -1;
|
|
222
195
|
|
|
223
|
-
// Hide modal after animation completes
|
|
224
196
|
setTimeout(() => {
|
|
225
197
|
if (!this.isOpen) {
|
|
226
198
|
this.modal.hidden = true;
|
|
227
|
-
// Reset body visibility for next open
|
|
228
199
|
if (this.body) {
|
|
229
200
|
this.body.hidden = true;
|
|
230
201
|
}
|
|
231
202
|
}
|
|
232
203
|
}, 200);
|
|
233
204
|
|
|
234
|
-
// Return focus to trigger
|
|
235
205
|
if (this.trigger) {
|
|
236
206
|
this.trigger.focus();
|
|
237
207
|
}
|
|
@@ -274,13 +244,11 @@ class SearchManager {
|
|
|
274
244
|
return;
|
|
275
245
|
}
|
|
276
246
|
|
|
277
|
-
// Store state for "load more"
|
|
278
247
|
this.allSearchResults = searchResults.results;
|
|
279
248
|
this.currentQuery = query;
|
|
280
249
|
this.displayedCount = 0;
|
|
281
250
|
this.groupedResults = [];
|
|
282
251
|
|
|
283
|
-
// Load initial batch
|
|
284
252
|
await this.loadMoreResults();
|
|
285
253
|
} catch (error) {
|
|
286
254
|
console.error('Search error:', error);
|
|
@@ -294,17 +262,14 @@ class SearchManager {
|
|
|
294
262
|
|
|
295
263
|
if (startIndex >= this.allSearchResults.length) return;
|
|
296
264
|
|
|
297
|
-
// Load the next batch of results
|
|
298
265
|
const resultsData = await Promise.all(
|
|
299
266
|
this.allSearchResults.slice(startIndex, endIndex).map(r => r.data())
|
|
300
267
|
);
|
|
301
268
|
|
|
302
|
-
// Group results by page with sections nested
|
|
303
269
|
const newGrouped = this.groupResults(resultsData);
|
|
304
270
|
this.groupedResults = [...this.groupedResults, ...newGrouped];
|
|
305
271
|
this.displayedCount = endIndex;
|
|
306
272
|
|
|
307
|
-
// Flatten for keyboard navigation
|
|
308
273
|
this.results = this.flattenForNavigation(this.groupedResults);
|
|
309
274
|
this.renderGroupedResults(this.groupedResults, this.currentQuery, this.allSearchResults.length);
|
|
310
275
|
}
|
|
@@ -320,14 +285,11 @@ class SearchManager {
|
|
|
320
285
|
for (const result of resultsData) {
|
|
321
286
|
const pageTitle = result.meta?.title || this.extractTitleFromUrl(result.url);
|
|
322
287
|
|
|
323
|
-
// Get sub-results (sections) for this page
|
|
324
|
-
// Filter out sections with same title as page (H1 heading duplicates)
|
|
325
288
|
const subResults = (result.sub_results || [])
|
|
326
289
|
.filter(sub => {
|
|
327
290
|
if (sub.url === result.url) return false;
|
|
328
291
|
if (!sub.title) return false;
|
|
329
292
|
const cleanedTitle = this.cleanSectionTitle(sub.title);
|
|
330
|
-
// Skip if section title matches page title
|
|
331
293
|
if (cleanedTitle.toLowerCase() === pageTitle.toLowerCase()) return false;
|
|
332
294
|
return true;
|
|
333
295
|
})
|
|
@@ -352,7 +314,6 @@ class SearchManager {
|
|
|
352
314
|
}
|
|
353
315
|
|
|
354
316
|
cleanSectionTitle(title) {
|
|
355
|
-
// Remove trailing # that Pagefind sometimes includes
|
|
356
317
|
return title.replace(/#$/, '').trim();
|
|
357
318
|
}
|
|
358
319
|
|
|
@@ -389,7 +350,6 @@ class SearchManager {
|
|
|
389
350
|
return this.renderPageResult(page, pageIndex, isPageSelected, sectionsHtml, query);
|
|
390
351
|
}).join('');
|
|
391
352
|
|
|
392
|
-
// Add "View more results" link if there are more results
|
|
393
353
|
const hasMore = this.displayedCount < totalResults;
|
|
394
354
|
const loadMoreHtml = hasMore ? `
|
|
395
355
|
<li class="search-load-more">
|
|
@@ -401,7 +361,6 @@ class SearchManager {
|
|
|
401
361
|
|
|
402
362
|
this.resultsContainer.innerHTML = resultsHtml + loadMoreHtml;
|
|
403
363
|
|
|
404
|
-
// Attach event listener to "View more" button
|
|
405
364
|
const loadMoreBtn = this.resultsContainer.querySelector('[data-search-load-more]');
|
|
406
365
|
if (loadMoreBtn) {
|
|
407
366
|
loadMoreBtn.addEventListener('click', this.handleLoadMore);
|
|
@@ -409,7 +368,6 @@ class SearchManager {
|
|
|
409
368
|
}
|
|
410
369
|
|
|
411
370
|
renderPageResult(page, index, isSelected, sectionsHtml, query) {
|
|
412
|
-
// Article icon (Phosphor)
|
|
413
371
|
const pageIcon = `<svg class="search-result-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor">
|
|
414
372
|
<path d="M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40Zm0,160H40V56H216V200ZM184,96a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h96A8,8,0,0,1,184,96Zm0,32a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h96A8,8,0,0,1,184,128Zm0,32a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h96A8,8,0,0,1,184,160Z"></path>
|
|
415
373
|
</svg>`;
|
|
@@ -473,7 +431,6 @@ class SearchManager {
|
|
|
473
431
|
const terms = query.trim().split(/\s+/).filter(t => t.length > 1);
|
|
474
432
|
if (terms.length === 0) return escaped;
|
|
475
433
|
|
|
476
|
-
// Match exact search term only (like Stripe does)
|
|
477
434
|
const regex = new RegExp(`(${terms.map(t => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
478
435
|
.join('|')})`, 'gi');
|
|
479
436
|
return escaped.replace(regex, '<mark class="search-title-highlight">$1</mark>');
|
|
@@ -482,30 +439,22 @@ class SearchManager {
|
|
|
482
439
|
highlightQuery(text, query, title = '') {
|
|
483
440
|
if (!query || !text) return this.escapeHtml(text);
|
|
484
441
|
|
|
485
|
-
// Decode HTML entities first (Pagefind may return encoded HTML)
|
|
486
442
|
let cleanText = this.decodeHtmlEntities(text);
|
|
487
443
|
|
|
488
|
-
// Strip all HTML tags
|
|
489
444
|
cleanText = cleanText.replace(/<[^>]*>/g, '');
|
|
490
445
|
|
|
491
|
-
// Remove the title if it appears at the start of the excerpt (Pagefind often includes it)
|
|
492
446
|
if (title) {
|
|
493
|
-
// Remove "Title#" or "Title:" patterns at the start
|
|
494
447
|
const titlePattern = new RegExp(`^${title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[#:]?\\s*`, 'i');
|
|
495
448
|
cleanText = cleanText.replace(titlePattern, '');
|
|
496
449
|
}
|
|
497
450
|
|
|
498
|
-
// Clean markdown and special characters
|
|
499
451
|
cleanText = this.cleanMarkdown(cleanText);
|
|
500
452
|
|
|
501
|
-
// Escape for safe HTML
|
|
502
453
|
const escaped = this.escapeHtml(cleanText);
|
|
503
454
|
|
|
504
|
-
// Highlight exact search term only (like Stripe does)
|
|
505
455
|
const terms = query.trim().split(/\s+/).filter(t => t.length > 1);
|
|
506
456
|
if (terms.length === 0) return escaped;
|
|
507
457
|
|
|
508
|
-
// Match exact search term only
|
|
509
458
|
const regex = new RegExp(`(${terms.map(t => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
510
459
|
.join('|')})`, 'gi');
|
|
511
460
|
return escaped.replace(regex, '<mark>$1</mark>');
|
|
@@ -519,51 +468,33 @@ class SearchManager {
|
|
|
519
468
|
|
|
520
469
|
cleanMarkdown(text) {
|
|
521
470
|
return text
|
|
522
|
-
// Remove code block markers with optional language/title (```ruby, ```js title="foo")
|
|
523
471
|
.replace(/```\w*(?:\s+[^`\n]*)?\n?/g, '')
|
|
524
472
|
.replace(/```/g, '')
|
|
525
|
-
// Remove Kramdown/Jekyll directives like {:/nomarkdown}, {::nomarkdown}, {:.class}
|
|
526
473
|
.replace(/\{:?:?[^}]*\}/g, '')
|
|
527
|
-
// Remove heading anchors like "Dark Mode#" or "Styling#" (word followed by #)
|
|
528
474
|
.replace(/(\w)#(?=\s|$|[A-Z])/g, '$1')
|
|
529
|
-
// Remove markdown bold/italic
|
|
530
475
|
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
|
531
476
|
.replace(/\*([^*]+)\*/g, '$1')
|
|
532
477
|
.replace(/__([^_]+)__/g, '$1')
|
|
533
478
|
.replace(/_([^_]+)_/g, '$1')
|
|
534
|
-
// Remove markdown links [text](url)
|
|
535
479
|
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
|
|
536
|
-
// Remove inline code backticks
|
|
537
480
|
.replace(/`([^`]+)`/g, '$1')
|
|
538
|
-
// Remove standalone backticks
|
|
539
481
|
.replace(/`/g, '')
|
|
540
|
-
// Remove heading markers
|
|
541
482
|
.replace(/^#+\s*/gm, '')
|
|
542
|
-
// Remove title="..." and similar attributes
|
|
543
483
|
.replace(/\s*title=["'][^"']*["']/gi, '')
|
|
544
|
-
// Remove URLs (http, https, ftp)
|
|
545
484
|
.replace(/https?:\/\/[^\s<>"{}|\\^`[\]]+/gi, '')
|
|
546
485
|
.replace(/ftp:\/\/[^\s<>"{}|\\^`[\]]+/gi, '')
|
|
547
|
-
// Remove YAML-like patterns (key: value)
|
|
548
486
|
.replace(/\b\w+:\s*["']?[^"'\s,]+["']?(?=\s|,|$)/g, '')
|
|
549
|
-
// Remove common code syntax patterns
|
|
550
487
|
.replace(/\b(const|let|var|function|interface|class|import|export|return|if|else)\b/g, '')
|
|
551
488
|
.replace(/[=;{}()<>[\]]/g, ' ')
|
|
552
|
-
// Remove common unicode symbols
|
|
553
489
|
.replace(/[✓✔✗✘→←↑↓•·►▸▹▶]/g, '')
|
|
554
|
-
// Remove YAML-like frontmatter patterns
|
|
555
490
|
.replace(/^---[\s\S]*?---/m, '')
|
|
556
|
-
// Clean up navigation/menu text patterns
|
|
557
491
|
.replace(/Skip to main content/gi, '')
|
|
558
492
|
.replace(/On this page/gi, '')
|
|
559
493
|
.replace(/Menu/gi, '')
|
|
560
494
|
.replace(/Search\.\.\./gi, '')
|
|
561
|
-
// Remove list markers
|
|
562
495
|
.replace(/^[\s]*[-*+]\s+/gm, '')
|
|
563
496
|
.replace(/^[\s]*\d+\.\s+/gm, '')
|
|
564
|
-
// Clean up excessive whitespace
|
|
565
497
|
.replace(/\s+/g, ' ')
|
|
566
|
-
// Remove leading/trailing punctuation
|
|
567
498
|
.replace(/^[.\s,;:]+/, '')
|
|
568
499
|
.replace(/[.\s,;:]+$/, '')
|
|
569
500
|
.trim();
|
|
@@ -586,12 +517,10 @@ class SearchManager {
|
|
|
586
517
|
updateSelection(newIndex) {
|
|
587
518
|
const resultElements = this.resultsContainer.querySelectorAll('.search-result');
|
|
588
519
|
|
|
589
|
-
// Remove previous selection
|
|
590
520
|
if (this.selectedIndex >= 0 && resultElements[this.selectedIndex]) {
|
|
591
521
|
resultElements[this.selectedIndex].setAttribute('aria-selected', 'false');
|
|
592
522
|
}
|
|
593
523
|
|
|
594
|
-
// Add new selection
|
|
595
524
|
this.selectedIndex = newIndex;
|
|
596
525
|
if (resultElements[newIndex]) {
|
|
597
526
|
resultElements[newIndex].setAttribute('aria-selected', 'true');
|
|
@@ -666,14 +595,10 @@ class SearchManager {
|
|
|
666
595
|
}
|
|
667
596
|
}
|
|
668
597
|
|
|
669
|
-
/**
|
|
670
|
-
* Initialize search on page load
|
|
671
|
-
*/
|
|
672
598
|
function initializeSearch() {
|
|
673
599
|
new SearchManager();
|
|
674
600
|
}
|
|
675
601
|
|
|
676
|
-
// Initialize on DOM ready
|
|
677
602
|
if (document.readyState === 'loading') {
|
|
678
603
|
document.addEventListener('DOMContentLoaded', initializeSearch);
|
|
679
604
|
} else {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var STORAGE_KEY = 'docyard_sidebar_collapsed';
|
|
5
|
+
|
|
6
|
+
function initSidebarToggle() {
|
|
7
|
+
var toggle = document.querySelector('.breadcrumb-toggle');
|
|
8
|
+
var sidebar = document.querySelector('.sidebar');
|
|
9
|
+
|
|
10
|
+
if (!toggle || !sidebar) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
var isCollapsed = document.documentElement.classList.contains('sidebar-collapsed');
|
|
15
|
+
toggle.setAttribute('aria-expanded', !isCollapsed);
|
|
16
|
+
|
|
17
|
+
toggle.addEventListener('click', function() {
|
|
18
|
+
var collapsed = document.documentElement.classList.toggle('sidebar-collapsed');
|
|
19
|
+
localStorage.setItem(STORAGE_KEY, collapsed);
|
|
20
|
+
toggle.setAttribute('aria-expanded', !collapsed);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (document.readyState === 'loading') {
|
|
25
|
+
document.addEventListener('DOMContentLoaded', initSidebarToggle);
|
|
26
|
+
} else {
|
|
27
|
+
initSidebarToggle();
|
|
28
|
+
}
|
|
29
|
+
})();
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var tabIndicator;
|
|
5
|
+
|
|
6
|
+
function updateIndicator(activeTab) {
|
|
7
|
+
if (!activeTab || !tabIndicator) return;
|
|
8
|
+
|
|
9
|
+
var rect = activeTab.getBoundingClientRect();
|
|
10
|
+
var parentRect = activeTab.parentElement.getBoundingClientRect();
|
|
11
|
+
var inset = 4;
|
|
12
|
+
|
|
13
|
+
tabIndicator.style.left = (rect.left - parentRect.left + inset) + 'px';
|
|
14
|
+
tabIndicator.style.width = (rect.width - (inset * 2)) + 'px';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function showIndicator() {
|
|
18
|
+
if (tabIndicator) {
|
|
19
|
+
tabIndicator.classList.add('is-ready');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function initTabIndicator() {
|
|
24
|
+
tabIndicator = document.getElementById('tabIndicator');
|
|
25
|
+
var tabItems = document.querySelectorAll('.tab-item');
|
|
26
|
+
|
|
27
|
+
if (!tabIndicator || tabItems.length === 0) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
var activeTab = document.querySelector('.tab-item.is-active');
|
|
32
|
+
if (activeTab) {
|
|
33
|
+
updateIndicator(activeTab);
|
|
34
|
+
tabIndicator.offsetHeight;
|
|
35
|
+
requestAnimationFrame(showIndicator);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
window.addEventListener('resize', function() {
|
|
39
|
+
var active = document.querySelector('.tab-item.is-active');
|
|
40
|
+
if (active) {
|
|
41
|
+
updateIndicator(active);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Handle cross-document view transitions
|
|
47
|
+
function initViewTransitions() {
|
|
48
|
+
if (!document.startViewTransition) return;
|
|
49
|
+
|
|
50
|
+
// Position indicator immediately when new page is revealed (before animation)
|
|
51
|
+
window.addEventListener('pagereveal', function(e) {
|
|
52
|
+
if (!e.viewTransition) return;
|
|
53
|
+
|
|
54
|
+
tabIndicator = document.getElementById('tabIndicator');
|
|
55
|
+
var activeTab = document.querySelector('.tab-item.is-active');
|
|
56
|
+
|
|
57
|
+
if (activeTab && tabIndicator) {
|
|
58
|
+
// Position and show indicator immediately so view transition can animate it
|
|
59
|
+
updateIndicator(activeTab);
|
|
60
|
+
tabIndicator.classList.add('is-ready');
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function initNavMenu() {
|
|
66
|
+
var navMenuBtn = document.getElementById('navMenuBtn');
|
|
67
|
+
var navMenuOverlay = document.getElementById('navMenuOverlay');
|
|
68
|
+
var navMenuDropdown = document.getElementById('navMenuDropdown');
|
|
69
|
+
|
|
70
|
+
if (!navMenuBtn || !navMenuOverlay || !navMenuDropdown) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
var scrollPosition = 0;
|
|
75
|
+
|
|
76
|
+
function lockBodyScroll() {
|
|
77
|
+
scrollPosition = window.pageYOffset;
|
|
78
|
+
document.body.style.overflow = 'hidden';
|
|
79
|
+
document.body.style.position = 'fixed';
|
|
80
|
+
document.body.style.top = -scrollPosition + 'px';
|
|
81
|
+
document.body.style.width = '100%';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function unlockBodyScroll() {
|
|
85
|
+
document.body.style.removeProperty('overflow');
|
|
86
|
+
document.body.style.removeProperty('position');
|
|
87
|
+
document.body.style.removeProperty('top');
|
|
88
|
+
document.body.style.removeProperty('width');
|
|
89
|
+
window.scrollTo(0, scrollPosition);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function openDropdown() {
|
|
93
|
+
lockBodyScroll();
|
|
94
|
+
navMenuOverlay.classList.add('is-visible');
|
|
95
|
+
navMenuDropdown.classList.add('is-open');
|
|
96
|
+
navMenuBtn.setAttribute('aria-expanded', 'true');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function closeDropdown() {
|
|
100
|
+
navMenuOverlay.classList.remove('is-visible');
|
|
101
|
+
navMenuDropdown.classList.remove('is-open');
|
|
102
|
+
navMenuBtn.setAttribute('aria-expanded', 'false');
|
|
103
|
+
unlockBodyScroll();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function toggleDropdown() {
|
|
107
|
+
if (navMenuDropdown.classList.contains('is-open')) {
|
|
108
|
+
closeDropdown();
|
|
109
|
+
} else {
|
|
110
|
+
openDropdown();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
navMenuBtn.addEventListener('click', toggleDropdown);
|
|
115
|
+
navMenuOverlay.addEventListener('click', closeDropdown);
|
|
116
|
+
|
|
117
|
+
document.addEventListener('keydown', function(e) {
|
|
118
|
+
if (e.key === 'Escape' && navMenuDropdown.classList.contains('is-open')) {
|
|
119
|
+
closeDropdown();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
navMenuDropdown.querySelectorAll('a').forEach(function(link) {
|
|
124
|
+
link.addEventListener('click', closeDropdown);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
window.addEventListener('resize', function() {
|
|
128
|
+
if (window.innerWidth > 1024) {
|
|
129
|
+
closeDropdown();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function init() {
|
|
135
|
+
initViewTransitions();
|
|
136
|
+
initTabIndicator();
|
|
137
|
+
initNavMenu();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (document.readyState === 'loading') {
|
|
141
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
142
|
+
} else {
|
|
143
|
+
init();
|
|
144
|
+
}
|
|
145
|
+
})();
|