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
@@ -1,13 +1,5 @@
1
- /**
2
- * CodeBlockManager - Manages code block copy functionality
3
- *
4
- * @class CodeBlockManager
5
- */
6
1
  class CodeBlockManager {
7
- /**
8
- * Create a CodeBlockManager instance
9
- * @param {HTMLElement} container - The .docyard-code-block container element
10
- */
2
+
11
3
  constructor(container) {
12
4
  if (!container) return;
13
5
 
@@ -15,7 +7,9 @@ class CodeBlockManager {
15
7
  this.copyButton = container.querySelector('.docyard-code-block__copy');
16
8
  this.codeText = this.copyButton?.getAttribute('data-code') || '';
17
9
 
18
- this.originalIcon = this.copyButton?.innerHTML || '';
10
+ this.iconElement = this.copyButton?.querySelector('.docyard-code-block__copy-icon');
11
+ this.textElement = this.copyButton?.querySelector('.docyard-code-block__copy-text');
12
+ this.originalIcon = this.iconElement?.innerHTML || '';
19
13
 
20
14
  this.checkIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 256 256"><path d="M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z"/></svg>';
21
15
 
@@ -24,18 +18,14 @@ class CodeBlockManager {
24
18
  this.init();
25
19
  }
26
20
 
27
- /**
28
- * Initialize the code block component
29
- */
21
+
30
22
  init() {
31
23
  if (!this.copyButton) return;
32
24
 
33
25
  this.copyButton.addEventListener('click', this.handleCopy);
34
26
  }
35
27
 
36
- /**
37
- * Handle copy button click
38
- */
28
+
39
29
  async handleCopy() {
40
30
  try {
41
31
  await this.copyToClipboard(this.codeText);
@@ -46,11 +36,7 @@ class CodeBlockManager {
46
36
  }
47
37
  }
48
38
 
49
- /**
50
- * Copy text to clipboard
51
- * @param {string} text - Text to copy
52
- * @returns {Promise<void>}
53
- */
39
+
54
40
  async copyToClipboard(text) {
55
41
  if (navigator.clipboard && window.isSecureContext) {
56
42
  await navigator.clipboard.writeText(text);
@@ -59,10 +45,7 @@ class CodeBlockManager {
59
45
  }
60
46
  }
61
47
 
62
- /**
63
- * Fallback copy method for older browsers
64
- * @param {string} text - Text to copy
65
- */
48
+
66
49
  fallbackCopy(text) {
67
50
  const textArea = document.createElement('textarea');
68
51
  textArea.value = text;
@@ -82,14 +65,17 @@ class CodeBlockManager {
82
65
  }
83
66
  }
84
67
 
85
- /**
86
- * Show success state
87
- */
68
+
88
69
  showSuccess() {
89
70
  this.copyButton.classList.add('is-success');
90
71
  this.copyButton.setAttribute('aria-label', 'Copied to clipboard!');
91
72
 
92
- this.copyButton.innerHTML = this.checkIcon;
73
+ if (this.iconElement) {
74
+ this.iconElement.innerHTML = this.checkIcon;
75
+ }
76
+ if (this.textElement) {
77
+ this.textElement.textContent = 'Copied';
78
+ }
93
79
 
94
80
  if (this.resetTimeout) {
95
81
  clearTimeout(this.resetTimeout);
@@ -100,9 +86,7 @@ class CodeBlockManager {
100
86
  }, 2000);
101
87
  }
102
88
 
103
- /**
104
- * Show error state
105
- */
89
+
106
90
  showError() {
107
91
  this.copyButton.classList.add('is-error');
108
92
  this.copyButton.setAttribute('aria-label', 'Failed to copy');
@@ -116,19 +100,20 @@ class CodeBlockManager {
116
100
  }, 2000);
117
101
  }
118
102
 
119
- /**
120
- * Reset button to default state
121
- */
103
+
122
104
  resetState() {
123
105
  this.copyButton.classList.remove('is-success', 'is-error');
124
106
  this.copyButton.setAttribute('aria-label', 'Copy code to clipboard');
125
107
 
126
- this.copyButton.innerHTML = this.originalIcon;
108
+ if (this.iconElement) {
109
+ this.iconElement.innerHTML = this.originalIcon;
110
+ }
111
+ if (this.textElement) {
112
+ this.textElement.textContent = 'Copy';
113
+ }
127
114
  }
128
115
 
129
- /**
130
- * Cleanup - remove event listeners
131
- */
116
+
132
117
  destroy() {
133
118
  if (this.copyButton) {
134
119
  this.copyButton.removeEventListener('click', this.handleCopy);
@@ -140,9 +125,6 @@ class CodeBlockManager {
140
125
  }
141
126
  }
142
127
 
143
- /**
144
- * Auto-initialize all code blocks on page load
145
- */
146
128
  function initializeCodeBlocks() {
147
129
  const codeBlocks = document.querySelectorAll('.docyard-code-block');
148
130
 
@@ -1,7 +1,3 @@
1
- /**
2
- * HeadingAnchorManager handles anchor link interactions
3
- * Provides copy-to-clipboard functionality with visual feedback
4
- */
5
1
  class HeadingAnchorManager {
6
2
  constructor() {
7
3
  this.anchors = document.querySelectorAll('.heading-anchor');
@@ -14,11 +10,7 @@ class HeadingAnchorManager {
14
10
  });
15
11
  }
16
12
 
17
- /**
18
- * Handle anchor link click
19
- * @param {Event} e - Click event
20
- * @param {HTMLElement} anchor - Anchor element
21
- */
13
+
22
14
  handleClick(e, anchor) {
23
15
  e.preventDefault();
24
16
 
@@ -31,15 +23,32 @@ class HeadingAnchorManager {
31
23
 
32
24
  const heading = document.getElementById(headingId);
33
25
  if (heading) {
34
- heading.scrollIntoView({ behavior: 'smooth', block: 'start' });
26
+ const offsetTop = heading.getBoundingClientRect().top + window.pageYOffset - this.getScrollOffset();
27
+ window.scrollTo({
28
+ top: offsetTop,
29
+ behavior: 'smooth'
30
+ });
35
31
  }
36
32
  }
37
33
 
38
- /**
39
- * Copy text to clipboard with visual feedback
40
- * @param {string} text - Text to copy
41
- * @param {HTMLElement} anchor - Anchor element for feedback
42
- */
34
+ getScrollOffset() {
35
+ const hasTabs = document.body.classList.contains('has-tabs');
36
+ const headerHeight = 64;
37
+ const tabBarHeight = hasTabs ? 48 : 0;
38
+ const buffer = 24;
39
+
40
+ if (window.innerWidth > 1024 && window.innerWidth <= 1280) {
41
+ return headerHeight + 48 + buffer;
42
+ }
43
+
44
+ if (window.innerWidth <= 1024) {
45
+ return headerHeight + 48 + buffer;
46
+ }
47
+
48
+ return headerHeight + tabBarHeight + buffer;
49
+ }
50
+
51
+
43
52
  async copyToClipboard(text, anchor) {
44
53
  try {
45
54
  await navigator.clipboard.writeText(text);
@@ -50,10 +59,7 @@ class HeadingAnchorManager {
50
59
  }
51
60
  }
52
61
 
53
- /**
54
- * Fallback copy method for older browsers
55
- * @param {string} text - Text to copy
56
- */
62
+
57
63
  fallbackCopyToClipboard(text) {
58
64
  const textarea = document.createElement('textarea');
59
65
  textarea.value = text;
@@ -65,11 +71,7 @@ class HeadingAnchorManager {
65
71
  document.body.removeChild(textarea);
66
72
  }
67
73
 
68
- /**
69
- * Show visual feedback on copy
70
- * @param {HTMLElement} anchor - Anchor element
71
- * @param {boolean} success - Whether copy succeeded
72
- */
74
+
73
75
  showFeedback(anchor, success) {
74
76
  const originalTitle = anchor.getAttribute('aria-label');
75
77
  anchor.setAttribute('aria-label', success ? 'Link copied!' : 'Failed to copy');
@@ -1,6 +1,3 @@
1
- // Docyard Navigation JavaScript
2
- // Handles sidebar navigation, mobile menu, accordion groups, and scroll behavior
3
-
4
1
  (function() {
5
2
  'use strict';
6
3
 
@@ -13,18 +10,36 @@
13
10
  return;
14
11
  }
15
12
 
13
+ var scrollPosition = 0;
14
+
15
+ function lockBodyScroll() {
16
+ scrollPosition = window.pageYOffset;
17
+ document.body.style.overflow = 'hidden';
18
+ document.body.style.position = 'fixed';
19
+ document.body.style.top = -scrollPosition + 'px';
20
+ document.body.style.width = '100%';
21
+ }
22
+
23
+ function unlockBodyScroll() {
24
+ document.body.style.removeProperty('overflow');
25
+ document.body.style.removeProperty('position');
26
+ document.body.style.removeProperty('top');
27
+ document.body.style.removeProperty('width');
28
+ window.scrollTo(0, scrollPosition);
29
+ }
30
+
16
31
  function openMenu() {
32
+ lockBodyScroll();
17
33
  sidebar.classList.add('is-open');
18
34
  overlay.classList.add('is-visible');
19
35
  toggle.setAttribute('aria-expanded', 'true');
20
- document.body.style.overflow = 'hidden';
21
36
  }
22
37
 
23
38
  function closeMenu() {
24
39
  sidebar.classList.remove('is-open');
25
40
  overlay.classList.remove('is-visible');
26
41
  toggle.setAttribute('aria-expanded', 'false');
27
- document.body.style.overflow = '';
42
+ unlockBodyScroll();
28
43
  }
29
44
 
30
45
  function toggleMenu() {
@@ -52,24 +67,86 @@
52
67
  }
53
68
 
54
69
  function initAccordion() {
55
- const toggles = document.querySelectorAll('.nav-group-toggle');
70
+ var TOGGLE_STATE_KEY = 'docyard_toggle_states';
71
+ var toggles = document.querySelectorAll('[data-nav-toggle]');
72
+
73
+ function getDefaultCollapsed(navGroup) {
74
+ return navGroup.getAttribute('data-default-collapsed') === 'true';
75
+ }
76
+
77
+ function collapseGroup(navGroup, animate) {
78
+ var header = navGroup.querySelector('[data-nav-toggle]');
79
+ var children = navGroup.querySelector('.nav-group-children');
80
+ if (!header || !children) return;
81
+
82
+ if (animate) {
83
+ children.style.transition = 'max-height 0.2s cubic-bezier(0.4, 0, 1, 1)';
84
+ } else {
85
+ children.style.transition = 'none';
86
+ }
87
+ header.setAttribute('aria-expanded', 'false');
88
+ children.classList.add('collapsed');
89
+ children.style.maxHeight = '0';
90
+ }
91
+
92
+ function expandGroup(navGroup, animate) {
93
+ var header = navGroup.querySelector('[data-nav-toggle]');
94
+ var children = navGroup.querySelector('.nav-group-children');
95
+ if (!header || !children) return;
96
+
97
+ var fullHeight = children.scrollHeight;
98
+ if (animate) {
99
+ children.style.transition = 'max-height 0.35s cubic-bezier(0.34, 1.56, 0.64, 1)';
100
+ } else {
101
+ children.style.transition = 'none';
102
+ }
103
+ header.setAttribute('aria-expanded', 'true');
104
+ children.classList.remove('collapsed');
105
+ children.style.maxHeight = fullHeight + 'px';
106
+ }
107
+
108
+ function revertOthersToDefault(currentNavGroup) {
109
+ document.querySelectorAll('.nav-group').forEach(function(navGroup) {
110
+ if (navGroup === currentNavGroup) return;
111
+
112
+ var defaultCollapsed = getDefaultCollapsed(navGroup);
113
+ var children = navGroup.querySelector('.nav-group-children');
114
+ if (!children) return;
115
+
116
+ var isCurrentlyCollapsed = children.classList.contains('collapsed');
117
+
118
+ if (defaultCollapsed && !isCurrentlyCollapsed) {
119
+ collapseGroup(navGroup, true);
120
+ } else if (!defaultCollapsed && isCurrentlyCollapsed) {
121
+ expandGroup(navGroup, true);
122
+ }
123
+ });
124
+ }
56
125
 
57
126
  toggles.forEach(function(toggle) {
58
- toggle.addEventListener('click', function() {
59
- const expanded = toggle.getAttribute('aria-expanded') === 'true';
60
- const children = toggle.nextElementSibling;
127
+ toggle.addEventListener('click', function(e) {
128
+ var expanded = toggle.getAttribute('aria-expanded') === 'true';
129
+ var navGroup = toggle.closest('.nav-group');
130
+ var children = navGroup ? navGroup.querySelector('.nav-group-children') : null;
61
131
 
62
- if (!children || !children.classList.contains('nav-group-children')) {
132
+ if (!children) {
63
133
  return;
64
134
  }
65
135
 
66
- toggle.setAttribute('aria-expanded', !expanded);
67
- children.classList.toggle('collapsed');
136
+ revertOthersToDefault(navGroup);
137
+
138
+ if (toggle.tagName === 'A') {
139
+ var href = toggle.getAttribute('href');
140
+ var states = {};
141
+ states[href] = true;
142
+ sessionStorage.setItem(TOGGLE_STATE_KEY, JSON.stringify(states));
143
+ return;
144
+ }
68
145
 
69
146
  if (expanded) {
70
- children.style.maxHeight = '0';
147
+ collapseGroup(navGroup, true);
71
148
  } else {
72
- children.style.maxHeight = children.scrollHeight + 'px';
149
+ expandGroup(navGroup, true);
73
150
  }
74
151
  });
75
152
  });
@@ -80,32 +157,80 @@
80
157
  }
81
158
 
82
159
  function expandActiveGroups() {
83
- const activeLinks = document.querySelectorAll('a.active');
160
+ var TOGGLE_STATE_KEY = 'docyard_toggle_states';
161
+ var currentUrl = window.location.pathname;
162
+ var lastUrl = sessionStorage.getItem('docyard_last_url') || '';
163
+ var toggleStates = JSON.parse(sessionStorage.getItem(TOGGLE_STATE_KEY) || '{}');
84
164
 
85
- activeLinks.forEach(function(activeLink) {
86
- let parent = activeLink.closest('.nav-group-children');
165
+ sessionStorage.setItem('docyard_last_url', currentUrl);
166
+ sessionStorage.removeItem(TOGGLE_STATE_KEY);
87
167
 
88
- while (parent) {
89
- if (parent.classList.contains('nav-group-children')) {
90
- parent.classList.remove('collapsed');
168
+ document.querySelectorAll('[data-nav-toggle]').forEach(function(toggle) {
169
+ if (toggle.tagName !== 'A') return;
91
170
 
92
- const toggle = parent.previousElementSibling;
93
- if (toggle && toggle.classList.contains('nav-group-toggle')) {
94
- toggle.setAttribute('aria-expanded', 'true');
171
+ var href = toggle.getAttribute('href');
172
+ var shouldOpen = toggleStates[href] === true;
95
173
 
96
- parent.style.maxHeight = 'none';
97
- const height = parent.scrollHeight;
98
- parent.style.maxHeight = height + 'px';
99
- }
100
- }
174
+ if (!shouldOpen) return;
175
+
176
+ var navGroup = toggle.closest('.nav-group');
177
+ var children = navGroup ? navGroup.querySelector('.nav-group-children') : null;
178
+
179
+ if (!children || !children.classList.contains('collapsed')) return;
101
180
 
102
- parent = parent.parentElement?.closest('.nav-group-children');
181
+ var fullHeight = children.scrollHeight;
182
+
183
+ children.style.transition = 'none';
184
+ children.style.maxHeight = '0';
185
+ children.offsetHeight;
186
+
187
+ children.style.transition = 'max-height 0.35s cubic-bezier(0.34, 1.56, 0.64, 1)';
188
+ requestAnimationFrame(function() {
189
+ toggle.setAttribute('aria-expanded', 'true');
190
+ children.classList.remove('collapsed');
191
+ children.style.maxHeight = fullHeight + 'px';
192
+ });
193
+ });
194
+
195
+ var expandedGroups = document.querySelectorAll('.nav-group-children:not(.collapsed)');
196
+
197
+ expandedGroups.forEach(function(group) {
198
+ if (group.style.maxHeight) {
199
+ return;
200
+ }
201
+
202
+ var navGroup = group.closest('.nav-group');
203
+ var header = navGroup ? navGroup.querySelector('.nav-group-header') : null;
204
+ var headerHref = header && header.tagName === 'A' ? header.getAttribute('href') : null;
205
+
206
+ if (headerHref && toggleStates[headerHref] === true) {
207
+ group.style.maxHeight = group.scrollHeight + 'px';
208
+ return;
209
+ }
210
+
211
+ var wasInGroup = headerHref && (lastUrl === headerHref || lastUrl.startsWith(headerHref + '/'));
212
+ var shouldAnimate = header && header.classList.contains('active') && !wasInGroup;
213
+ var fullHeight = group.scrollHeight;
214
+
215
+ if (shouldAnimate) {
216
+ group.style.transition = 'none';
217
+ group.style.maxHeight = '0';
218
+ group.classList.add('collapsed');
219
+ group.offsetHeight;
220
+
221
+ group.style.transition = 'max-height 0.35s cubic-bezier(0.34, 1.56, 0.64, 1)';
222
+ requestAnimationFrame(function() {
223
+ group.classList.remove('collapsed');
224
+ group.style.maxHeight = fullHeight + 'px';
225
+ });
226
+ } else {
227
+ group.style.maxHeight = fullHeight + 'px';
103
228
  }
104
229
  });
105
230
  }
106
231
 
107
232
  function initSidebarScroll() {
108
- const scrollContainer = document.querySelector('.sidebar nav');
233
+ const scrollContainer = document.querySelector('.sidebar-scroll');
109
234
  if (!scrollContainer) return;
110
235
 
111
236
  const STORAGE_KEY = 'docyard_sidebar_scroll';
@@ -153,54 +278,38 @@
153
278
  }
154
279
  }
155
280
 
156
- function initScrollBehavior() {
157
- const header = document.querySelector('.header');
158
- const secondaryHeader = document.querySelector('.secondary-header');
159
-
160
- if (!header || !secondaryHeader) return;
161
-
162
- let lastScrollTop = 0;
163
- let ticking = false;
281
+ function initScrollFadeIndicators() {
282
+ const sidebar = document.querySelector('.sidebar');
283
+ const scrollContainer = document.querySelector('.sidebar-scroll');
284
+ if (!sidebar || !scrollContainer) return;
164
285
 
165
- function isMobile() {
166
- return window.innerWidth <= 1024;
167
- }
286
+ function updateFadeIndicators() {
287
+ const scrollTop = scrollContainer.scrollTop;
288
+ const scrollHeight = scrollContainer.scrollHeight;
289
+ const clientHeight = scrollContainer.clientHeight;
290
+ const threshold = 10;
168
291
 
169
- function updateHeaders() {
170
- if (!isMobile()) {
171
- header.classList.remove('hide-on-scroll');
172
- secondaryHeader.classList.remove('shift-up');
173
- ticking = false;
174
- return;
292
+ if (scrollTop > threshold) {
293
+ sidebar.classList.add('can-scroll-top');
294
+ } else {
295
+ sidebar.classList.remove('can-scroll-top');
175
296
  }
176
297
 
177
- const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
178
-
179
- if (scrollTop > lastScrollTop && scrollTop > 100) {
180
- header.classList.add('hide-on-scroll');
181
- secondaryHeader.classList.add('shift-up');
182
- } else if (scrollTop < lastScrollTop) {
183
- header.classList.remove('hide-on-scroll');
184
- secondaryHeader.classList.remove('shift-up');
298
+ if (scrollTop + clientHeight < scrollHeight - threshold) {
299
+ sidebar.classList.add('can-scroll-bottom');
300
+ } else {
301
+ sidebar.classList.remove('can-scroll-bottom');
185
302
  }
186
-
187
- lastScrollTop = scrollTop <= 0 ? 0 : scrollTop;
188
- ticking = false;
189
303
  }
190
304
 
191
- window.addEventListener('scroll', function() {
192
- if (!ticking) {
193
- window.requestAnimationFrame(updateHeaders);
194
- ticking = true;
195
- }
196
- });
305
+ updateFadeIndicators();
197
306
 
198
- window.addEventListener('resize', function() {
199
- if (!isMobile()) {
200
- header.classList.remove('hide-on-scroll');
201
- secondaryHeader.classList.remove('shift-up');
202
- }
203
- });
307
+ scrollContainer.addEventListener('scroll', updateFadeIndicators);
308
+
309
+ window.addEventListener('resize', updateFadeIndicators);
310
+ }
311
+
312
+ function initScrollBehavior() {
204
313
  }
205
314
 
206
315
  if ('scrollRestoration' in history) {
@@ -213,6 +322,7 @@
213
322
  initAccordion();
214
323
  expandActiveGroups();
215
324
  initSidebarScroll();
325
+ initScrollFadeIndicators();
216
326
  initScrollBehavior();
217
327
  });
218
328
  } else {
@@ -220,6 +330,7 @@
220
330
  initAccordion();
221
331
  expandActiveGroups();
222
332
  initSidebarScroll();
333
+ initScrollFadeIndicators();
223
334
  initScrollBehavior();
224
335
  }
225
336
  })();