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.
Files changed (177) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +5 -1
  3. data/CHANGELOG.md +34 -1
  4. data/lib/docyard/build/asset_bundler.rb +22 -7
  5. data/lib/docyard/build/file_copier.rb +49 -27
  6. data/lib/docyard/build/sitemap_generator.rb +6 -6
  7. data/lib/docyard/build/static_generator.rb +82 -50
  8. data/lib/docyard/builder.rb +20 -10
  9. data/lib/docyard/cli.rb +6 -3
  10. data/lib/docyard/components/aliases.rb +29 -0
  11. data/lib/docyard/components/processors/callout_processor.rb +124 -0
  12. data/lib/docyard/components/processors/code_block_diff_preprocessor.rb +106 -0
  13. data/lib/docyard/components/processors/code_block_focus_preprocessor.rb +79 -0
  14. data/lib/docyard/components/processors/code_block_options_preprocessor.rb +78 -0
  15. data/lib/docyard/components/processors/code_block_processor.rb +175 -0
  16. data/lib/docyard/components/processors/code_snippet_import_preprocessor.rb +127 -0
  17. data/lib/docyard/components/processors/heading_anchor_processor.rb +39 -0
  18. data/lib/docyard/components/processors/icon_processor.rb +53 -0
  19. data/lib/docyard/components/processors/table_of_contents_processor.rb +68 -0
  20. data/lib/docyard/components/processors/table_wrapper_processor.rb +22 -0
  21. data/lib/docyard/components/processors/tabs_processor.rb +48 -0
  22. data/lib/docyard/components/support/code_block/feature_extractor.rb +117 -0
  23. data/lib/docyard/components/support/code_block/icon_detector.rb +44 -0
  24. data/lib/docyard/components/support/code_block/line_parser.rb +84 -0
  25. data/lib/docyard/components/support/code_block/line_wrapper.rb +50 -0
  26. data/lib/docyard/components/support/code_block/patterns.rb +55 -0
  27. data/lib/docyard/components/support/code_detector.rb +61 -0
  28. data/lib/docyard/components/support/tabs/icon_detector.rb +62 -0
  29. data/lib/docyard/components/support/tabs/parser.rb +195 -0
  30. data/lib/docyard/components/support/tabs/range_finder.rb +46 -0
  31. data/lib/docyard/config/branding_resolver.rb +183 -0
  32. data/lib/docyard/{constants.rb → config/constants.rb} +7 -4
  33. data/lib/docyard/config/validator.rb +122 -99
  34. data/lib/docyard/config.rb +38 -36
  35. data/lib/docyard/initializer.rb +15 -76
  36. data/lib/docyard/navigation/breadcrumb_builder.rb +133 -0
  37. data/lib/docyard/{prev_next_builder.rb → navigation/prev_next_builder.rb} +6 -3
  38. data/lib/docyard/navigation/sidebar/children_discoverer.rb +51 -0
  39. data/lib/docyard/navigation/sidebar/config_parser.rb +208 -0
  40. data/lib/docyard/navigation/sidebar/file_resolver.rb +78 -0
  41. data/lib/docyard/{sidebar → navigation/sidebar}/file_system_scanner.rb +2 -1
  42. data/lib/docyard/navigation/sidebar/item.rb +96 -0
  43. data/lib/docyard/navigation/sidebar/local_config_loader.rb +51 -0
  44. data/lib/docyard/navigation/sidebar/metadata_extractor.rb +69 -0
  45. data/lib/docyard/navigation/sidebar/metadata_reader.rb +47 -0
  46. data/lib/docyard/navigation/sidebar/path_prefixer.rb +34 -0
  47. data/lib/docyard/navigation/sidebar/renderer.rb +144 -0
  48. data/lib/docyard/navigation/sidebar/sorter.rb +21 -0
  49. data/lib/docyard/navigation/sidebar/tree_builder.rb +139 -0
  50. data/lib/docyard/navigation/sidebar/tree_filter.rb +55 -0
  51. data/lib/docyard/navigation/sidebar_builder.rb +159 -0
  52. data/lib/docyard/rendering/icon_helpers.rb +13 -0
  53. data/lib/docyard/{icons → rendering/icons}/phosphor.rb +26 -1
  54. data/lib/docyard/{markdown.rb → rendering/markdown.rb} +19 -13
  55. data/lib/docyard/rendering/renderer.rb +163 -0
  56. data/lib/docyard/rendering/template_resolver.rb +172 -0
  57. data/lib/docyard/routing/fallback_resolver.rb +92 -0
  58. data/lib/docyard/search/build_indexer.rb +74 -0
  59. data/lib/docyard/search/dev_indexer.rb +155 -0
  60. data/lib/docyard/search/pagefind_support.rb +33 -0
  61. data/lib/docyard/{asset_handler.rb → server/asset_handler.rb} +24 -19
  62. data/lib/docyard/{server.rb → server/dev_server.rb} +32 -9
  63. data/lib/docyard/server/pagefind_handler.rb +63 -0
  64. data/lib/docyard/{preview_server.rb → server/preview_server.rb} +2 -2
  65. data/lib/docyard/server/rack_application.rb +192 -0
  66. data/lib/docyard/server/resolution_result.rb +29 -0
  67. data/lib/docyard/{router.rb → server/router.rb} +4 -4
  68. data/lib/docyard/templates/assets/css/code.css +18 -51
  69. data/lib/docyard/templates/assets/css/components/breadcrumbs.css +143 -0
  70. data/lib/docyard/templates/assets/css/components/callout.css +67 -67
  71. data/lib/docyard/templates/assets/css/components/code-block.css +180 -282
  72. data/lib/docyard/templates/assets/css/components/heading-anchor.css +28 -15
  73. data/lib/docyard/templates/assets/css/components/icon.css +0 -1
  74. data/lib/docyard/templates/assets/css/components/logo.css +0 -2
  75. data/lib/docyard/templates/assets/css/components/nav-menu.css +237 -0
  76. data/lib/docyard/templates/assets/css/components/navigation.css +186 -167
  77. data/lib/docyard/templates/assets/css/components/prev-next.css +76 -47
  78. data/lib/docyard/templates/assets/css/components/search.css +561 -0
  79. data/lib/docyard/templates/assets/css/components/tab-bar.css +163 -0
  80. data/lib/docyard/templates/assets/css/components/table-of-contents.css +127 -114
  81. data/lib/docyard/templates/assets/css/components/tabs.css +119 -160
  82. data/lib/docyard/templates/assets/css/components/theme-toggle.css +48 -44
  83. data/lib/docyard/templates/assets/css/landing.css +815 -0
  84. data/lib/docyard/templates/assets/css/layout.css +503 -87
  85. data/lib/docyard/templates/assets/css/main.css +1 -3
  86. data/lib/docyard/templates/assets/css/markdown.css +111 -93
  87. data/lib/docyard/templates/assets/css/reset.css +0 -3
  88. data/lib/docyard/templates/assets/css/typography.css +43 -41
  89. data/lib/docyard/templates/assets/css/variables.css +268 -208
  90. data/lib/docyard/templates/assets/favicon.svg +7 -8
  91. data/lib/docyard/templates/assets/fonts/Inter-Variable.ttf +0 -0
  92. data/lib/docyard/templates/assets/js/components/code-block.js +24 -42
  93. data/lib/docyard/templates/assets/js/components/heading-anchor.js +26 -24
  94. data/lib/docyard/templates/assets/js/components/navigation.js +181 -70
  95. data/lib/docyard/templates/assets/js/components/search.js +610 -0
  96. data/lib/docyard/templates/assets/js/components/sidebar-toggle.js +29 -0
  97. data/lib/docyard/templates/assets/js/components/tab-navigation.js +145 -0
  98. data/lib/docyard/templates/assets/js/components/table-of-contents.js +153 -66
  99. data/lib/docyard/templates/assets/js/components/tabs.js +31 -69
  100. data/lib/docyard/templates/assets/js/theme.js +0 -3
  101. data/lib/docyard/templates/assets/logo-dark.svg +8 -2
  102. data/lib/docyard/templates/assets/logo.svg +7 -4
  103. data/lib/docyard/templates/config/docyard.yml.erb +37 -34
  104. data/lib/docyard/templates/errors/404.html.erb +1 -1
  105. data/lib/docyard/templates/errors/500.html.erb +1 -1
  106. data/lib/docyard/templates/layouts/default.html.erb +19 -56
  107. data/lib/docyard/templates/layouts/splash.html.erb +176 -0
  108. data/lib/docyard/templates/partials/_breadcrumbs.html.erb +24 -0
  109. data/lib/docyard/templates/partials/_code_block.html.erb +6 -4
  110. data/lib/docyard/templates/partials/_doc_footer.html.erb +25 -0
  111. data/lib/docyard/templates/partials/_features.html.erb +15 -0
  112. data/lib/docyard/templates/partials/_footer.html.erb +42 -0
  113. data/lib/docyard/templates/partials/_head.html.erb +22 -0
  114. data/lib/docyard/templates/partials/_header.html.erb +49 -0
  115. data/lib/docyard/templates/partials/_heading_anchor.html.erb +3 -1
  116. data/lib/docyard/templates/partials/_hero.html.erb +27 -0
  117. data/lib/docyard/templates/partials/_nav_group.html.erb +25 -11
  118. data/lib/docyard/templates/partials/_nav_leaf.html.erb +1 -1
  119. data/lib/docyard/templates/partials/_nav_menu.html.erb +42 -0
  120. data/lib/docyard/templates/partials/_nav_nested_section.html.erb +11 -0
  121. data/lib/docyard/templates/partials/_nav_section.html.erb +1 -1
  122. data/lib/docyard/templates/partials/_prev_next.html.erb +9 -3
  123. data/lib/docyard/templates/partials/_scripts.html.erb +7 -0
  124. data/lib/docyard/templates/partials/_search_modal.html.erb +41 -0
  125. data/lib/docyard/templates/partials/_search_trigger.html.erb +18 -0
  126. data/lib/docyard/templates/partials/_sidebar.html.erb +21 -4
  127. data/lib/docyard/templates/partials/_tab_bar.html.erb +25 -0
  128. data/lib/docyard/templates/partials/_table_of_contents.html.erb +12 -12
  129. data/lib/docyard/templates/partials/_table_of_contents_toggle.html.erb +1 -3
  130. data/lib/docyard/templates/partials/_tabs.html.erb +2 -2
  131. data/lib/docyard/templates/partials/_theme_toggle.html.erb +2 -11
  132. data/lib/docyard/utils/html_helpers.rb +14 -0
  133. data/lib/docyard/utils/path_resolver.rb +2 -1
  134. data/lib/docyard/utils/url_helpers.rb +20 -0
  135. data/lib/docyard/version.rb +1 -1
  136. data/lib/docyard.rb +22 -15
  137. metadata +89 -50
  138. data/lib/docyard/components/callout_processor.rb +0 -121
  139. data/lib/docyard/components/code_block_diff_preprocessor.rb +0 -104
  140. data/lib/docyard/components/code_block_feature_extractor.rb +0 -113
  141. data/lib/docyard/components/code_block_focus_preprocessor.rb +0 -77
  142. data/lib/docyard/components/code_block_icon_detector.rb +0 -40
  143. data/lib/docyard/components/code_block_line_wrapper.rb +0 -46
  144. data/lib/docyard/components/code_block_options_preprocessor.rb +0 -76
  145. data/lib/docyard/components/code_block_patterns.rb +0 -51
  146. data/lib/docyard/components/code_block_processor.rb +0 -176
  147. data/lib/docyard/components/code_detector.rb +0 -59
  148. data/lib/docyard/components/code_line_parser.rb +0 -80
  149. data/lib/docyard/components/code_snippet_import_preprocessor.rb +0 -125
  150. data/lib/docyard/components/heading_anchor_processor.rb +0 -34
  151. data/lib/docyard/components/icon_detector.rb +0 -57
  152. data/lib/docyard/components/icon_processor.rb +0 -51
  153. data/lib/docyard/components/table_of_contents_processor.rb +0 -64
  154. data/lib/docyard/components/table_wrapper_processor.rb +0 -18
  155. data/lib/docyard/components/tabs_parser.rb +0 -191
  156. data/lib/docyard/components/tabs_processor.rb +0 -44
  157. data/lib/docyard/components/tabs_range_finder.rb +0 -42
  158. data/lib/docyard/rack_application.rb +0 -172
  159. data/lib/docyard/renderer.rb +0 -120
  160. data/lib/docyard/routing/resolution_result.rb +0 -31
  161. data/lib/docyard/sidebar/config_parser.rb +0 -180
  162. data/lib/docyard/sidebar/item.rb +0 -58
  163. data/lib/docyard/sidebar/renderer.rb +0 -137
  164. data/lib/docyard/sidebar/tree_builder.rb +0 -59
  165. data/lib/docyard/sidebar_builder.rb +0 -102
  166. data/lib/docyard/templates/markdown/getting-started/installation.md.erb +0 -77
  167. data/lib/docyard/templates/markdown/guides/configuration.md.erb +0 -202
  168. data/lib/docyard/templates/markdown/guides/markdown-features.md.erb +0 -247
  169. data/lib/docyard/templates/markdown/index.md.erb +0 -82
  170. /data/lib/docyard/{sidebar → navigation/sidebar}/title_extractor.rb +0 -0
  171. /data/lib/docyard/{icons → rendering/icons}/LICENSE.phosphor +0 -0
  172. /data/lib/docyard/{icons → rendering/icons}/file_types.rb +0 -0
  173. /data/lib/docyard/{icons.rb → rendering/icons.rb} +0 -0
  174. /data/lib/docyard/{language_mapping.rb → rendering/language_mapping.rb} +0 -0
  175. /data/lib/docyard/{file_watcher.rb → server/file_watcher.rb} +0 -0
  176. /data/lib/docyard/{errors.rb → utils/errors.rb} +0 -0
  177. /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 128;
81
+ return headerHeight + 48 + buffer;
53
82
  }
54
83
 
55
84
  if (window.innerWidth <= 1024) {
56
- const header = document.querySelector('.header');
57
- const isHeaderHidden = header && header.classList.contains('hide-on-scroll');
58
- return isHeaderHidden ? 128 : 64;
85
+ const secondaryHeaderHeight = 48;
86
+ return headerHeight + secondaryHeaderHeight + buffer;
59
87
  }
60
88
 
61
- return 100;
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 tocRect = this.toc.getBoundingClientRect();
148
+ const containerRect = scrollContainer.getBoundingClientRect();
128
149
  const linkRect = link.getBoundingClientRect();
129
150
 
130
- if (linkRect.top < tocRect.top || linkRect.bottom > tocRect.bottom) {
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
- * Handle initial hash in URL on page load
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
- setTimeout(() => {
271
- this.setActiveLink(hash);
361
+ this.setActiveLink(hash);
272
362
 
273
- const offsetTop = heading.getBoundingClientRect().top + window.pageYOffset - this.getScrollOffset();
274
- window.scrollTo({
275
- top: offsetTop,
276
- behavior: 'auto'
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();