docyard 0.6.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 +34 -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 +82 -50
- data/lib/docyard/builder.rb +20 -10
- data/lib/docyard/cli.rb +6 -3
- data/lib/docyard/components/aliases.rb +29 -0
- data/lib/docyard/components/processors/callout_processor.rb +124 -0
- data/lib/docyard/components/processors/code_block_diff_preprocessor.rb +106 -0
- data/lib/docyard/components/processors/code_block_focus_preprocessor.rb +79 -0
- data/lib/docyard/components/processors/code_block_options_preprocessor.rb +78 -0
- data/lib/docyard/components/processors/code_block_processor.rb +175 -0
- data/lib/docyard/components/processors/code_snippet_import_preprocessor.rb +127 -0
- data/lib/docyard/components/processors/heading_anchor_processor.rb +39 -0
- data/lib/docyard/components/processors/icon_processor.rb +53 -0
- data/lib/docyard/components/processors/table_of_contents_processor.rb +68 -0
- data/lib/docyard/components/processors/table_wrapper_processor.rb +22 -0
- data/lib/docyard/components/processors/tabs_processor.rb +48 -0
- data/lib/docyard/components/support/code_block/feature_extractor.rb +117 -0
- data/lib/docyard/components/support/code_block/icon_detector.rb +44 -0
- data/lib/docyard/components/support/code_block/line_parser.rb +84 -0
- data/lib/docyard/components/support/code_block/line_wrapper.rb +50 -0
- data/lib/docyard/components/support/code_block/patterns.rb +55 -0
- data/lib/docyard/components/support/code_detector.rb +61 -0
- data/lib/docyard/components/support/tabs/icon_detector.rb +62 -0
- data/lib/docyard/components/support/tabs/parser.rb +195 -0
- data/lib/docyard/components/support/tabs/range_finder.rb +46 -0
- data/lib/docyard/config/branding_resolver.rb +183 -0
- data/lib/docyard/{constants.rb → config/constants.rb} +7 -4
- data/lib/docyard/config/validator.rb +122 -99
- data/lib/docyard/config.rb +38 -36
- data/lib/docyard/initializer.rb +15 -76
- data/lib/docyard/navigation/breadcrumb_builder.rb +133 -0
- data/lib/docyard/{prev_next_builder.rb → navigation/prev_next_builder.rb} +6 -3
- data/lib/docyard/navigation/sidebar/children_discoverer.rb +51 -0
- data/lib/docyard/navigation/sidebar/config_parser.rb +208 -0
- data/lib/docyard/navigation/sidebar/file_resolver.rb +78 -0
- data/lib/docyard/{sidebar → navigation/sidebar}/file_system_scanner.rb +2 -1
- data/lib/docyard/navigation/sidebar/item.rb +96 -0
- 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 +144 -0
- data/lib/docyard/navigation/sidebar/sorter.rb +21 -0
- data/lib/docyard/navigation/sidebar/tree_builder.rb +139 -0
- data/lib/docyard/navigation/sidebar/tree_filter.rb +55 -0
- data/lib/docyard/navigation/sidebar_builder.rb +159 -0
- data/lib/docyard/rendering/icon_helpers.rb +13 -0
- data/lib/docyard/{icons → rendering/icons}/phosphor.rb +26 -1
- data/lib/docyard/{markdown.rb → rendering/markdown.rb} +19 -13
- data/lib/docyard/rendering/renderer.rb +163 -0
- 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 +74 -0
- data/lib/docyard/search/dev_indexer.rb +155 -0
- data/lib/docyard/search/pagefind_support.rb +33 -0
- data/lib/docyard/{asset_handler.rb → server/asset_handler.rb} +24 -19
- data/lib/docyard/{server.rb → server/dev_server.rb} +32 -9
- data/lib/docyard/server/pagefind_handler.rb +63 -0
- data/lib/docyard/{preview_server.rb → server/preview_server.rb} +2 -2
- data/lib/docyard/server/rack_application.rb +192 -0
- data/lib/docyard/server/resolution_result.rb +29 -0
- data/lib/docyard/{router.rb → server/router.rb} +4 -4
- 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 +561 -0
- 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 +503 -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 +610 -0
- 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 +19 -56
- 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 +6 -4
- 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 +9 -3
- data/lib/docyard/templates/partials/_scripts.html.erb +7 -0
- data/lib/docyard/templates/partials/_search_modal.html.erb +41 -0
- data/lib/docyard/templates/partials/_search_trigger.html.erb +18 -0
- 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/utils/html_helpers.rb +14 -0
- data/lib/docyard/utils/path_resolver.rb +2 -1
- data/lib/docyard/utils/url_helpers.rb +20 -0
- data/lib/docyard/version.rb +1 -1
- data/lib/docyard.rb +22 -15
- metadata +89 -50
- data/lib/docyard/components/callout_processor.rb +0 -121
- data/lib/docyard/components/code_block_diff_preprocessor.rb +0 -104
- data/lib/docyard/components/code_block_feature_extractor.rb +0 -113
- data/lib/docyard/components/code_block_focus_preprocessor.rb +0 -77
- data/lib/docyard/components/code_block_icon_detector.rb +0 -40
- data/lib/docyard/components/code_block_line_wrapper.rb +0 -46
- data/lib/docyard/components/code_block_options_preprocessor.rb +0 -76
- data/lib/docyard/components/code_block_patterns.rb +0 -51
- data/lib/docyard/components/code_block_processor.rb +0 -176
- data/lib/docyard/components/code_detector.rb +0 -59
- data/lib/docyard/components/code_line_parser.rb +0 -80
- data/lib/docyard/components/code_snippet_import_preprocessor.rb +0 -125
- data/lib/docyard/components/heading_anchor_processor.rb +0 -34
- data/lib/docyard/components/icon_detector.rb +0 -57
- data/lib/docyard/components/icon_processor.rb +0 -51
- data/lib/docyard/components/table_of_contents_processor.rb +0 -64
- data/lib/docyard/components/table_wrapper_processor.rb +0 -18
- data/lib/docyard/components/tabs_parser.rb +0 -191
- data/lib/docyard/components/tabs_processor.rb +0 -44
- data/lib/docyard/components/tabs_range_finder.rb +0 -42
- data/lib/docyard/rack_application.rb +0 -172
- data/lib/docyard/renderer.rb +0 -120
- data/lib/docyard/routing/resolution_result.rb +0 -31
- data/lib/docyard/sidebar/config_parser.rb +0 -180
- data/lib/docyard/sidebar/item.rb +0 -58
- data/lib/docyard/sidebar/renderer.rb +0 -137
- data/lib/docyard/sidebar/tree_builder.rb +0 -59
- data/lib/docyard/sidebar_builder.rb +0 -102
- 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
- /data/lib/docyard/{sidebar → navigation/sidebar}/title_extractor.rb +0 -0
- /data/lib/docyard/{icons → rendering/icons}/LICENSE.phosphor +0 -0
- /data/lib/docyard/{icons → rendering/icons}/file_types.rb +0 -0
- /data/lib/docyard/{icons.rb → rendering/icons.rb} +0 -0
- /data/lib/docyard/{language_mapping.rb → rendering/language_mapping.rb} +0 -0
- /data/lib/docyard/{file_watcher.rb → server/file_watcher.rb} +0 -0
- /data/lib/docyard/{errors.rb → utils/errors.rb} +0 -0
- /data/lib/docyard/{logging.rb → utils/logging.rb} +0 -0
|
@@ -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
|
+
})();
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TableOfContentsManager handles TOC interactions and scroll tracking
|
|
3
|
-
* Features: scroll spy, active highlighting, smooth scrolling, mobile toggle
|
|
4
|
-
*/
|
|
5
1
|
class TableOfContentsManager {
|
|
6
2
|
constructor() {
|
|
7
3
|
this.toc = document.querySelector('.docyard-toc');
|
|
@@ -20,51 +16,80 @@ class TableOfContentsManager {
|
|
|
20
16
|
init() {
|
|
21
17
|
if (this.headings.length === 0) return;
|
|
22
18
|
|
|
19
|
+
this.aside = document.querySelector('.doc-aside');
|
|
20
|
+
this.createIndicator();
|
|
23
21
|
this.setupScrollSpy();
|
|
24
22
|
this.setupSmoothScrolling();
|
|
25
23
|
this.setupMobileToggle();
|
|
26
24
|
this.setupKeyboardNavigation();
|
|
25
|
+
this.setupScrollFadeIndicators();
|
|
26
|
+
this.setupAsideOverflowDetection();
|
|
27
27
|
this.handleInitialHash();
|
|
28
|
+
|
|
29
|
+
requestAnimationFrame(() => {
|
|
30
|
+
this.updateIndicator();
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
createIndicator() {
|
|
36
|
+
const list = this.toc.querySelector('.docyard-toc__list');
|
|
37
|
+
if (!list) return;
|
|
38
|
+
|
|
39
|
+
this.indicator = document.createElement('div');
|
|
40
|
+
this.indicator.className = 'docyard-toc__indicator';
|
|
41
|
+
this.indicator.style.opacity = '0';
|
|
42
|
+
list.appendChild(this.indicator);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
updateIndicator() {
|
|
47
|
+
if (!this.indicator || !this.activeLink) return;
|
|
48
|
+
|
|
49
|
+
const list = this.toc.querySelector('.docyard-toc__list');
|
|
50
|
+
if (!list) return;
|
|
51
|
+
|
|
52
|
+
const listRect = list.getBoundingClientRect();
|
|
53
|
+
const linkRect = this.activeLink.getBoundingClientRect();
|
|
54
|
+
|
|
55
|
+
const top = linkRect.top - listRect.top;
|
|
56
|
+
const height = linkRect.height;
|
|
57
|
+
|
|
58
|
+
this.indicator.style.top = `${top}px`;
|
|
59
|
+
this.indicator.style.height = `${height}px`;
|
|
60
|
+
this.indicator.style.opacity = '1';
|
|
28
61
|
}
|
|
29
62
|
|
|
30
|
-
|
|
31
|
-
* Check if viewport is mobile/tablet (TOC in secondary header)
|
|
32
|
-
* @returns {boolean}
|
|
33
|
-
*/
|
|
63
|
+
|
|
34
64
|
isMobile() {
|
|
35
65
|
return window.innerWidth <= 1280;
|
|
36
66
|
}
|
|
37
67
|
|
|
38
|
-
|
|
39
|
-
* Check if viewport is tablet (both headers visible)
|
|
40
|
-
* @returns {boolean}
|
|
41
|
-
*/
|
|
68
|
+
|
|
42
69
|
isTablet() {
|
|
43
70
|
return window.innerWidth > 1024 && window.innerWidth <= 1280;
|
|
44
71
|
}
|
|
45
72
|
|
|
46
|
-
|
|
47
|
-
* Get scroll offset based on viewport and header state
|
|
48
|
-
* @returns {number}
|
|
49
|
-
*/
|
|
73
|
+
|
|
50
74
|
getScrollOffset() {
|
|
75
|
+
const hasTabs = document.body.classList.contains('has-tabs');
|
|
76
|
+
const headerHeight = 64;
|
|
77
|
+
const tabBarHeight = hasTabs ? 48 : 0;
|
|
78
|
+
const buffer = 24;
|
|
79
|
+
|
|
51
80
|
if (this.isTablet()) {
|
|
52
|
-
return
|
|
81
|
+
return headerHeight + 48 + buffer;
|
|
53
82
|
}
|
|
54
83
|
|
|
55
84
|
if (window.innerWidth <= 1024) {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
return isHeaderHidden ? 128 : 64;
|
|
85
|
+
const secondaryHeaderHeight = 48;
|
|
86
|
+
return headerHeight + secondaryHeaderHeight + buffer;
|
|
59
87
|
}
|
|
60
88
|
|
|
61
|
-
return
|
|
89
|
+
return headerHeight + tabBarHeight + buffer;
|
|
62
90
|
}
|
|
63
91
|
|
|
64
|
-
|
|
65
|
-
* Get all headings referenced in TOC
|
|
66
|
-
* @returns {Array<HTMLElement>}
|
|
67
|
-
*/
|
|
92
|
+
|
|
68
93
|
getHeadings() {
|
|
69
94
|
return this.links
|
|
70
95
|
.map(link => {
|
|
@@ -74,9 +99,7 @@ class TableOfContentsManager {
|
|
|
74
99
|
.filter(Boolean);
|
|
75
100
|
}
|
|
76
101
|
|
|
77
|
-
|
|
78
|
-
* Setup Intersection Observer for scroll spy
|
|
79
|
-
*/
|
|
102
|
+
|
|
80
103
|
setupScrollSpy() {
|
|
81
104
|
const options = {
|
|
82
105
|
root: null,
|
|
@@ -97,10 +120,7 @@ class TableOfContentsManager {
|
|
|
97
120
|
});
|
|
98
121
|
}
|
|
99
122
|
|
|
100
|
-
|
|
101
|
-
* Set active link in TOC
|
|
102
|
-
* @param {string} id - Heading ID
|
|
103
|
-
*/
|
|
123
|
+
|
|
104
124
|
setActiveLink(id) {
|
|
105
125
|
const link = this.links.find(l => l.dataset.headingId === id);
|
|
106
126
|
if (!link || link === this.activeLink) return;
|
|
@@ -112,22 +132,23 @@ class TableOfContentsManager {
|
|
|
112
132
|
link.classList.add('is-active');
|
|
113
133
|
this.activeLink = link;
|
|
114
134
|
|
|
135
|
+
this.updateIndicator();
|
|
115
136
|
this.scrollLinkIntoView(link);
|
|
116
137
|
}
|
|
117
138
|
|
|
118
|
-
|
|
119
|
-
* Scroll TOC to keep active link visible
|
|
120
|
-
* @param {HTMLElement} link - Active link element
|
|
121
|
-
*/
|
|
139
|
+
|
|
122
140
|
scrollLinkIntoView(link) {
|
|
123
141
|
if (this.isMobile()) return;
|
|
124
142
|
|
|
143
|
+
const scrollContainer = this.toc.querySelector('.docyard-toc__scroll');
|
|
144
|
+
if (!scrollContainer) return;
|
|
145
|
+
|
|
125
146
|
if (!this.ticking) {
|
|
126
147
|
requestAnimationFrame(() => {
|
|
127
|
-
const
|
|
148
|
+
const containerRect = scrollContainer.getBoundingClientRect();
|
|
128
149
|
const linkRect = link.getBoundingClientRect();
|
|
129
150
|
|
|
130
|
-
if (linkRect.top <
|
|
151
|
+
if (linkRect.top < containerRect.top || linkRect.bottom > containerRect.bottom) {
|
|
131
152
|
link.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
132
153
|
}
|
|
133
154
|
|
|
@@ -137,9 +158,7 @@ class TableOfContentsManager {
|
|
|
137
158
|
}
|
|
138
159
|
}
|
|
139
160
|
|
|
140
|
-
|
|
141
|
-
* Setup smooth scrolling for TOC links
|
|
142
|
-
*/
|
|
161
|
+
|
|
143
162
|
setupSmoothScrolling() {
|
|
144
163
|
this.links.forEach(link => {
|
|
145
164
|
link.addEventListener('click', (e) => {
|
|
@@ -160,7 +179,6 @@ class TableOfContentsManager {
|
|
|
160
179
|
|
|
161
180
|
heading.focus({ preventScroll: true });
|
|
162
181
|
|
|
163
|
-
// Close mobile menu after clicking a link
|
|
164
182
|
if (this.isMobile()) {
|
|
165
183
|
this.collapseMobile();
|
|
166
184
|
}
|
|
@@ -169,9 +187,7 @@ class TableOfContentsManager {
|
|
|
169
187
|
});
|
|
170
188
|
}
|
|
171
189
|
|
|
172
|
-
|
|
173
|
-
* Setup mobile toggle functionality
|
|
174
|
-
*/
|
|
190
|
+
|
|
175
191
|
setupMobileToggle() {
|
|
176
192
|
const secondaryToggle = document.querySelector('.secondary-header-toc-toggle');
|
|
177
193
|
|
|
@@ -186,12 +202,12 @@ class TableOfContentsManager {
|
|
|
186
202
|
|
|
187
203
|
if (!isExpanded) {
|
|
188
204
|
document.body.style.overflow = 'hidden';
|
|
205
|
+
requestAnimationFrame(() => this.updateIndicator());
|
|
189
206
|
} else {
|
|
190
207
|
document.body.style.overflow = '';
|
|
191
208
|
}
|
|
192
209
|
});
|
|
193
210
|
|
|
194
|
-
// Close menu when clicking outside (mobile only)
|
|
195
211
|
document.addEventListener('click', (e) => {
|
|
196
212
|
if (!this.isMobile()) return;
|
|
197
213
|
|
|
@@ -205,11 +221,19 @@ class TableOfContentsManager {
|
|
|
205
221
|
this.collapseMobile();
|
|
206
222
|
}
|
|
207
223
|
});
|
|
224
|
+
|
|
225
|
+
document.addEventListener('keydown', (e) => {
|
|
226
|
+
if (e.key === 'Escape') {
|
|
227
|
+
const isExpanded = secondaryToggle.getAttribute('aria-expanded') === 'true';
|
|
228
|
+
if (isExpanded) {
|
|
229
|
+
this.collapseMobile();
|
|
230
|
+
secondaryToggle.focus();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
});
|
|
208
234
|
}
|
|
209
235
|
|
|
210
|
-
|
|
211
|
-
* Collapse mobile TOC
|
|
212
|
-
*/
|
|
236
|
+
|
|
213
237
|
collapseMobile() {
|
|
214
238
|
const secondaryToggle = document.querySelector('.secondary-header-toc-toggle');
|
|
215
239
|
if (secondaryToggle) {
|
|
@@ -221,9 +245,7 @@ class TableOfContentsManager {
|
|
|
221
245
|
}
|
|
222
246
|
}
|
|
223
247
|
|
|
224
|
-
|
|
225
|
-
* Setup keyboard navigation
|
|
226
|
-
*/
|
|
248
|
+
|
|
227
249
|
setupKeyboardNavigation() {
|
|
228
250
|
this.links.forEach((link, index) => {
|
|
229
251
|
link.addEventListener('keydown', (e) => {
|
|
@@ -257,9 +279,78 @@ class TableOfContentsManager {
|
|
|
257
279
|
});
|
|
258
280
|
}
|
|
259
281
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
282
|
+
|
|
283
|
+
setupAsideOverflowDetection() {
|
|
284
|
+
if (!this.aside) return;
|
|
285
|
+
|
|
286
|
+
const checkOverflow = () => {
|
|
287
|
+
if (this.isMobile()) {
|
|
288
|
+
this.aside.classList.remove('has-overflow');
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
this.aside.classList.remove('has-overflow');
|
|
293
|
+
|
|
294
|
+
const footer = this.aside.querySelector('.doc-footer-desktop');
|
|
295
|
+
const tocScroll = this.toc.querySelector('.docyard-toc__scroll');
|
|
296
|
+
if (!footer || !tocScroll) return;
|
|
297
|
+
|
|
298
|
+
requestAnimationFrame(() => {
|
|
299
|
+
const asideHeight = this.aside.clientHeight;
|
|
300
|
+
const tocNaturalHeight = this.toc.offsetHeight;
|
|
301
|
+
const footerHeight = footer.offsetHeight;
|
|
302
|
+
const totalContentHeight = tocNaturalHeight + footerHeight;
|
|
303
|
+
|
|
304
|
+
if (totalContentHeight > asideHeight) {
|
|
305
|
+
this.aside.classList.add('has-overflow');
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
checkOverflow();
|
|
311
|
+
window.addEventListener('resize', checkOverflow);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
setupScrollFadeIndicators() {
|
|
315
|
+
const scrollContainer = this.toc.querySelector('.docyard-toc__scroll');
|
|
316
|
+
if (!scrollContainer) return;
|
|
317
|
+
|
|
318
|
+
const updateFadeIndicators = () => {
|
|
319
|
+
if (this.isMobile()) {
|
|
320
|
+
this.toc.classList.remove('can-scroll-top', 'can-scroll-bottom');
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const scrollTop = scrollContainer.scrollTop;
|
|
325
|
+
const scrollHeight = scrollContainer.scrollHeight;
|
|
326
|
+
const clientHeight = scrollContainer.clientHeight;
|
|
327
|
+
const threshold = 10;
|
|
328
|
+
|
|
329
|
+
if (scrollTop > threshold) {
|
|
330
|
+
this.toc.classList.add('can-scroll-top');
|
|
331
|
+
} else {
|
|
332
|
+
this.toc.classList.remove('can-scroll-top');
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (scrollTop + clientHeight < scrollHeight - threshold) {
|
|
336
|
+
this.toc.classList.add('can-scroll-bottom');
|
|
337
|
+
} else {
|
|
338
|
+
this.toc.classList.remove('can-scroll-bottom');
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
updateFadeIndicators();
|
|
343
|
+
scrollContainer.addEventListener('scroll', () => {
|
|
344
|
+
updateFadeIndicators();
|
|
345
|
+
this.updateIndicator();
|
|
346
|
+
});
|
|
347
|
+
window.addEventListener('resize', () => {
|
|
348
|
+
updateFadeIndicators();
|
|
349
|
+
this.updateIndicator();
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
|
|
263
354
|
handleInitialHash() {
|
|
264
355
|
const hash = window.location.hash.slice(1);
|
|
265
356
|
if (!hash) return;
|
|
@@ -267,20 +358,16 @@ class TableOfContentsManager {
|
|
|
267
358
|
const heading = document.getElementById(hash);
|
|
268
359
|
if (!heading) return;
|
|
269
360
|
|
|
270
|
-
|
|
271
|
-
this.setActiveLink(hash);
|
|
361
|
+
this.setActiveLink(hash);
|
|
272
362
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}, 100);
|
|
363
|
+
const offsetTop = heading.getBoundingClientRect().top + window.pageYOffset - this.getScrollOffset();
|
|
364
|
+
window.scrollTo({
|
|
365
|
+
top: offsetTop,
|
|
366
|
+
behavior: 'instant'
|
|
367
|
+
});
|
|
279
368
|
}
|
|
280
369
|
|
|
281
|
-
|
|
282
|
-
* Cleanup and destroy
|
|
283
|
-
*/
|
|
370
|
+
|
|
284
371
|
destroy() {
|
|
285
372
|
if (this.observer) {
|
|
286
373
|
this.observer.disconnect();
|