docyard 0.7.0 → 0.9.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 (155) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +5 -1
  3. data/CHANGELOG.md +43 -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 +85 -12
  8. data/lib/docyard/builder.rb +6 -6
  9. data/lib/docyard/components/aliases.rb +12 -0
  10. data/lib/docyard/components/processors/abbreviation_processor.rb +72 -0
  11. data/lib/docyard/components/processors/accordion_processor.rb +81 -0
  12. data/lib/docyard/components/processors/badge_processor.rb +72 -0
  13. data/lib/docyard/components/processors/callout_processor.rb +8 -2
  14. data/lib/docyard/components/processors/cards_processor.rb +100 -0
  15. data/lib/docyard/components/processors/code_block_options_preprocessor.rb +23 -2
  16. data/lib/docyard/components/processors/code_block_processor.rb +6 -0
  17. data/lib/docyard/components/processors/code_group_processor.rb +198 -0
  18. data/lib/docyard/components/processors/code_snippet_import_preprocessor.rb +6 -1
  19. data/lib/docyard/components/processors/custom_anchor_processor.rb +42 -0
  20. data/lib/docyard/components/processors/file_tree_processor.rb +151 -0
  21. data/lib/docyard/components/processors/image_caption_processor.rb +96 -0
  22. data/lib/docyard/components/processors/include_processor.rb +86 -0
  23. data/lib/docyard/components/processors/steps_processor.rb +89 -0
  24. data/lib/docyard/components/processors/tabs_processor.rb +9 -1
  25. data/lib/docyard/components/processors/tooltip_processor.rb +57 -0
  26. data/lib/docyard/components/processors/video_embed_processor.rb +196 -0
  27. data/lib/docyard/components/support/code_group/html_builder.rb +122 -0
  28. data/lib/docyard/components/support/markdown_code_block_helper.rb +56 -0
  29. data/lib/docyard/config/branding_resolver.rb +121 -17
  30. data/lib/docyard/config/constants.rb +6 -4
  31. data/lib/docyard/config/logo_detector.rb +39 -0
  32. data/lib/docyard/config/validator.rb +122 -99
  33. data/lib/docyard/config.rb +40 -42
  34. data/lib/docyard/initializer.rb +15 -76
  35. data/lib/docyard/navigation/breadcrumb_builder.rb +133 -0
  36. data/lib/docyard/navigation/prev_next_builder.rb +4 -1
  37. data/lib/docyard/navigation/sidebar/children_discoverer.rb +51 -0
  38. data/lib/docyard/navigation/sidebar/config_parser.rb +136 -108
  39. data/lib/docyard/navigation/sidebar/file_resolver.rb +90 -0
  40. data/lib/docyard/navigation/sidebar/file_system_scanner.rb +2 -1
  41. data/lib/docyard/navigation/sidebar/item.rb +50 -7
  42. data/lib/docyard/navigation/sidebar/local_config_loader.rb +51 -0
  43. data/lib/docyard/navigation/sidebar/metadata_extractor.rb +71 -0
  44. data/lib/docyard/navigation/sidebar/metadata_reader.rb +51 -0
  45. data/lib/docyard/navigation/sidebar/path_prefixer.rb +34 -0
  46. data/lib/docyard/navigation/sidebar/renderer.rb +60 -38
  47. data/lib/docyard/navigation/sidebar/sorter.rb +21 -0
  48. data/lib/docyard/navigation/sidebar/tree_builder.rb +100 -26
  49. data/lib/docyard/navigation/sidebar/tree_filter.rb +55 -0
  50. data/lib/docyard/navigation/sidebar_builder.rb +105 -36
  51. data/lib/docyard/rendering/icon_helpers.rb +13 -0
  52. data/lib/docyard/rendering/icons/phosphor.rb +26 -1
  53. data/lib/docyard/rendering/markdown.rb +29 -1
  54. data/lib/docyard/rendering/renderer.rb +75 -34
  55. data/lib/docyard/rendering/template_resolver.rb +172 -0
  56. data/lib/docyard/routing/fallback_resolver.rb +92 -0
  57. data/lib/docyard/search/build_indexer.rb +1 -1
  58. data/lib/docyard/search/dev_indexer.rb +51 -6
  59. data/lib/docyard/search/pagefind_support.rb +2 -0
  60. data/lib/docyard/server/asset_handler.rb +25 -19
  61. data/lib/docyard/server/pagefind_handler.rb +63 -0
  62. data/lib/docyard/server/preview_server.rb +1 -1
  63. data/lib/docyard/server/rack_application.rb +81 -64
  64. data/lib/docyard/templates/assets/css/code.css +18 -51
  65. data/lib/docyard/templates/assets/css/components/abbreviation.css +86 -0
  66. data/lib/docyard/templates/assets/css/components/accordion.css +138 -0
  67. data/lib/docyard/templates/assets/css/components/badges.css +47 -0
  68. data/lib/docyard/templates/assets/css/components/banner.css +202 -0
  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/cards.css +100 -0
  72. data/lib/docyard/templates/assets/css/components/code-block.css +190 -282
  73. data/lib/docyard/templates/assets/css/components/code-group.css +281 -0
  74. data/lib/docyard/templates/assets/css/components/figure.css +22 -0
  75. data/lib/docyard/templates/assets/css/components/file-tree.css +124 -0
  76. data/lib/docyard/templates/assets/css/components/heading-anchor.css +36 -15
  77. data/lib/docyard/templates/assets/css/components/icon.css +0 -1
  78. data/lib/docyard/templates/assets/css/components/lightbox.css +65 -0
  79. data/lib/docyard/templates/assets/css/components/logo.css +0 -2
  80. data/lib/docyard/templates/assets/css/components/nav-menu.css +237 -0
  81. data/lib/docyard/templates/assets/css/components/navigation.css +193 -167
  82. data/lib/docyard/templates/assets/css/components/prev-next.css +68 -48
  83. data/lib/docyard/templates/assets/css/components/search.css +186 -174
  84. data/lib/docyard/templates/assets/css/components/steps.css +122 -0
  85. data/lib/docyard/templates/assets/css/components/tab-bar.css +163 -0
  86. data/lib/docyard/templates/assets/css/components/table-of-contents.css +127 -114
  87. data/lib/docyard/templates/assets/css/components/tabs.css +119 -160
  88. data/lib/docyard/templates/assets/css/components/theme-toggle.css +48 -44
  89. data/lib/docyard/templates/assets/css/components/tooltip.css +113 -0
  90. data/lib/docyard/templates/assets/css/components/video.css +41 -0
  91. data/lib/docyard/templates/assets/css/landing.css +815 -0
  92. data/lib/docyard/templates/assets/css/layout.css +489 -87
  93. data/lib/docyard/templates/assets/css/main.css +1 -3
  94. data/lib/docyard/templates/assets/css/markdown.css +113 -93
  95. data/lib/docyard/templates/assets/css/reset.css +0 -3
  96. data/lib/docyard/templates/assets/css/typography.css +43 -41
  97. data/lib/docyard/templates/assets/css/variables.css +268 -208
  98. data/lib/docyard/templates/assets/favicon.svg +7 -8
  99. data/lib/docyard/templates/assets/fonts/Inter-Variable.ttf +0 -0
  100. data/lib/docyard/templates/assets/js/components/abbreviation.js +85 -0
  101. data/lib/docyard/templates/assets/js/components/banner.js +81 -0
  102. data/lib/docyard/templates/assets/js/components/code-block.js +24 -42
  103. data/lib/docyard/templates/assets/js/components/code-group.js +283 -0
  104. data/lib/docyard/templates/assets/js/components/file-tree.js +39 -0
  105. data/lib/docyard/templates/assets/js/components/heading-anchor.js +26 -24
  106. data/lib/docyard/templates/assets/js/components/lightbox.js +72 -0
  107. data/lib/docyard/templates/assets/js/components/navigation.js +181 -70
  108. data/lib/docyard/templates/assets/js/components/search.js +0 -75
  109. data/lib/docyard/templates/assets/js/components/sidebar-toggle.js +29 -0
  110. data/lib/docyard/templates/assets/js/components/tab-navigation.js +145 -0
  111. data/lib/docyard/templates/assets/js/components/table-of-contents.js +153 -66
  112. data/lib/docyard/templates/assets/js/components/tabs.js +31 -69
  113. data/lib/docyard/templates/assets/js/components/tooltip.js +118 -0
  114. data/lib/docyard/templates/assets/js/theme.js +0 -3
  115. data/lib/docyard/templates/assets/logo-dark.svg +8 -2
  116. data/lib/docyard/templates/assets/logo.svg +7 -4
  117. data/lib/docyard/templates/config/docyard.yml.erb +37 -34
  118. data/lib/docyard/templates/errors/404.html.erb +1 -1
  119. data/lib/docyard/templates/errors/500.html.erb +1 -1
  120. data/lib/docyard/templates/layouts/default.html.erb +19 -67
  121. data/lib/docyard/templates/layouts/splash.html.erb +177 -0
  122. data/lib/docyard/templates/partials/_accordion.html.erb +9 -0
  123. data/lib/docyard/templates/partials/_banner.html.erb +27 -0
  124. data/lib/docyard/templates/partials/_breadcrumbs.html.erb +24 -0
  125. data/lib/docyard/templates/partials/_card.html.erb +23 -0
  126. data/lib/docyard/templates/partials/_code_block.html.erb +5 -3
  127. data/lib/docyard/templates/partials/_doc_footer.html.erb +25 -0
  128. data/lib/docyard/templates/partials/_features.html.erb +15 -0
  129. data/lib/docyard/templates/partials/_footer.html.erb +42 -0
  130. data/lib/docyard/templates/partials/_head.html.erb +22 -0
  131. data/lib/docyard/templates/partials/_header.html.erb +49 -0
  132. data/lib/docyard/templates/partials/_heading_anchor.html.erb +3 -1
  133. data/lib/docyard/templates/partials/_hero.html.erb +27 -0
  134. data/lib/docyard/templates/partials/_nav_group.html.erb +31 -11
  135. data/lib/docyard/templates/partials/_nav_leaf.html.erb +4 -1
  136. data/lib/docyard/templates/partials/_nav_menu.html.erb +42 -0
  137. data/lib/docyard/templates/partials/_nav_nested_section.html.erb +11 -0
  138. data/lib/docyard/templates/partials/_nav_section.html.erb +1 -1
  139. data/lib/docyard/templates/partials/_prev_next.html.erb +8 -2
  140. data/lib/docyard/templates/partials/_scripts.html.erb +7 -0
  141. data/lib/docyard/templates/partials/_search_modal.html.erb +2 -6
  142. data/lib/docyard/templates/partials/_search_trigger.html.erb +2 -6
  143. data/lib/docyard/templates/partials/_sidebar.html.erb +21 -4
  144. data/lib/docyard/templates/partials/_step.html.erb +14 -0
  145. data/lib/docyard/templates/partials/_tab_bar.html.erb +25 -0
  146. data/lib/docyard/templates/partials/_table_of_contents.html.erb +12 -12
  147. data/lib/docyard/templates/partials/_table_of_contents_toggle.html.erb +1 -3
  148. data/lib/docyard/templates/partials/_tabs.html.erb +2 -2
  149. data/lib/docyard/templates/partials/_theme_toggle.html.erb +2 -11
  150. data/lib/docyard/version.rb +1 -1
  151. metadata +70 -5
  152. data/lib/docyard/templates/markdown/getting-started/installation.md.erb +0 -77
  153. data/lib/docyard/templates/markdown/guides/configuration.md.erb +0 -202
  154. data/lib/docyard/templates/markdown/guides/markdown-features.md.erb +0 -247
  155. data/lib/docyard/templates/markdown/index.md.erb +0 -82
@@ -0,0 +1,81 @@
1
+ (function () {
2
+ var STORAGE_KEY = 'docyard-announcement-dismissed';
3
+
4
+ function initAnnouncement() {
5
+ var banner = document.querySelector('.docyard-announcement');
6
+ if (!banner) return;
7
+
8
+ var isDismissible = banner.dataset.dismissible === 'true';
9
+
10
+ if (isDismissible && isDismissed()) {
11
+ banner.style.display = 'none';
12
+ document.body.classList.remove('has-announcement');
13
+ return;
14
+ }
15
+
16
+ if (isDismissible) {
17
+ var dismissButton = banner.querySelector('.docyard-announcement__dismiss');
18
+ if (dismissButton) {
19
+ dismissButton.addEventListener('click', function () {
20
+ dismissAnnouncement(banner);
21
+ });
22
+ }
23
+ }
24
+
25
+ var actionLink = banner.querySelector('.docyard-announcement__link');
26
+ var actionButton = banner.querySelector('.docyard-announcement__button');
27
+
28
+ if (actionLink) {
29
+ actionLink.addEventListener('click', function () {
30
+ saveDismissed();
31
+ });
32
+ }
33
+
34
+ if (actionButton) {
35
+ actionButton.addEventListener('click', function () {
36
+ saveDismissed();
37
+ });
38
+ }
39
+ }
40
+
41
+ function saveDismissed() {
42
+ try {
43
+ localStorage.setItem(STORAGE_KEY, Date.now().toString());
44
+ } catch (e) {
45
+ }
46
+ }
47
+
48
+ function isDismissed() {
49
+ try {
50
+ var dismissed = localStorage.getItem(STORAGE_KEY);
51
+ if (!dismissed) return false;
52
+
53
+ var dismissedAt = parseInt(dismissed, 10);
54
+ var sevenDaysAgo = Date.now() - (7 * 24 * 60 * 60 * 1000);
55
+
56
+ return dismissedAt > sevenDaysAgo;
57
+ } catch (e) {
58
+ return false;
59
+ }
60
+ }
61
+
62
+ function dismissAnnouncement(banner) {
63
+ banner.classList.add('is-dismissed');
64
+
65
+ try {
66
+ localStorage.setItem(STORAGE_KEY, Date.now().toString());
67
+ } catch (e) {
68
+ }
69
+
70
+ banner.addEventListener('animationend', function () {
71
+ banner.style.display = 'none';
72
+ document.body.classList.remove('has-announcement');
73
+ });
74
+ }
75
+
76
+ if (document.readyState === 'loading') {
77
+ document.addEventListener('DOMContentLoaded', initAnnouncement);
78
+ } else {
79
+ initAnnouncement();
80
+ }
81
+ })();
@@ -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
 
@@ -0,0 +1,283 @@
1
+ class CodeGroupManager {
2
+ constructor() {
3
+ this.groups = [];
4
+ this.init();
5
+ }
6
+
7
+ init() {
8
+ const containers = document.querySelectorAll('.docyard-code-group');
9
+ if (containers.length === 0) return;
10
+
11
+ containers.forEach(container => {
12
+ this.groups.push(new CodeGroup(container, this));
13
+ });
14
+
15
+ this.loadPreference();
16
+ }
17
+
18
+ syncTabs(label) {
19
+ this.groups.forEach(group => {
20
+ group.activateTabByLabel(label, true);
21
+ });
22
+ this.savePreference(label);
23
+ }
24
+
25
+ loadPreference() {
26
+ try {
27
+ const preferredTab = localStorage.getItem('docyard-code-group-tab');
28
+ if (!preferredTab) return;
29
+
30
+ this.groups.forEach(group => {
31
+ group.activateTabByLabel(preferredTab, false);
32
+ });
33
+ } catch (error) {
34
+ // localStorage not available
35
+ }
36
+ }
37
+
38
+ savePreference(label) {
39
+ try {
40
+ localStorage.setItem('docyard-code-group-tab', label.toLowerCase());
41
+ } catch (error) {
42
+ // localStorage not available
43
+ }
44
+ }
45
+ }
46
+
47
+ class CodeGroup {
48
+ constructor(container, manager) {
49
+ this.container = container;
50
+ this.manager = manager;
51
+ this.scrollContainer = container.querySelector('.docyard-code-group__tabs-scroll-container');
52
+ this.tabList = container.querySelector('[role="tablist"]');
53
+ this.tabs = Array.from(container.querySelectorAll('[role="tab"]'));
54
+ this.panels = Array.from(container.querySelectorAll('[role="tabpanel"]'));
55
+ this.indicator = container.querySelector('.docyard-code-group__indicator');
56
+ this.copyButton = container.querySelector('.docyard-code-group__copy');
57
+ this.activeIndex = 0;
58
+
59
+ 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>';
60
+
61
+ this.attachEventListeners();
62
+ this.updateIndicator();
63
+
64
+ requestAnimationFrame(() => {
65
+ this.updateScrollIndicators();
66
+ });
67
+ }
68
+
69
+ attachEventListeners() {
70
+ this.tabs.forEach((tab, index) => {
71
+ tab.addEventListener('click', () => this.handleTabClick(index));
72
+ });
73
+
74
+ this.tabList.addEventListener('keydown', (e) => this.handleKeyDown(e));
75
+ this.tabList.addEventListener('scroll', () => this.handleScroll());
76
+ window.addEventListener('resize', () => this.handleResize());
77
+
78
+ if (this.copyButton) {
79
+ this.copyButton.addEventListener('click', () => this.handleCopy());
80
+ }
81
+ }
82
+
83
+ handleScroll() {
84
+ if (this.scrollTimeout) {
85
+ cancelAnimationFrame(this.scrollTimeout);
86
+ }
87
+
88
+ this.scrollTimeout = requestAnimationFrame(() => {
89
+ this.updateScrollIndicators();
90
+ });
91
+ }
92
+
93
+ handleTabClick(index) {
94
+ if (index === this.activeIndex) return;
95
+
96
+ const label = this.tabs[index].dataset.label;
97
+ this.manager.syncTabs(label);
98
+ }
99
+
100
+ handleKeyDown(event) {
101
+ const { key } = event;
102
+
103
+ if (key === 'ArrowLeft' || key === 'ArrowRight') {
104
+ event.preventDefault();
105
+
106
+ if (key === 'ArrowLeft') {
107
+ this.activatePreviousTab();
108
+ } else {
109
+ this.activateNextTab();
110
+ }
111
+
112
+ this.tabs[this.activeIndex].focus();
113
+ const label = this.tabs[this.activeIndex].dataset.label;
114
+ this.manager.syncTabs(label);
115
+ }
116
+
117
+ if (key === 'Home') {
118
+ event.preventDefault();
119
+ this.activateTab(0, true);
120
+ this.tabs[0].focus();
121
+ const label = this.tabs[0].dataset.label;
122
+ this.manager.syncTabs(label);
123
+ }
124
+
125
+ if (key === 'End') {
126
+ event.preventDefault();
127
+ const lastIndex = this.tabs.length - 1;
128
+ this.activateTab(lastIndex, true);
129
+ this.tabs[lastIndex].focus();
130
+ const label = this.tabs[lastIndex].dataset.label;
131
+ this.manager.syncTabs(label);
132
+ }
133
+ }
134
+
135
+ handleResize() {
136
+ if (this.resizeTimeout) {
137
+ cancelAnimationFrame(this.resizeTimeout);
138
+ }
139
+
140
+ this.resizeTimeout = requestAnimationFrame(() => {
141
+ this.updateIndicator(false);
142
+ this.updateScrollIndicators();
143
+ });
144
+ }
145
+
146
+ activateTab(index, animate = true) {
147
+ if (index < 0 || index >= this.tabs.length) return;
148
+
149
+ this.activeIndex = index;
150
+
151
+ this.tabs.forEach((tab, i) => {
152
+ const isActive = i === index;
153
+ tab.setAttribute('aria-selected', isActive ? 'true' : 'false');
154
+ tab.setAttribute('tabindex', isActive ? '0' : '-1');
155
+ });
156
+
157
+ this.panels.forEach((panel, i) => {
158
+ const isActive = i === index;
159
+ panel.setAttribute('aria-hidden', isActive ? 'false' : 'true');
160
+ });
161
+
162
+ this.updateIndicator(animate);
163
+ }
164
+
165
+ activateTabByLabel(label, animate = true) {
166
+ const index = this.tabs.findIndex(tab =>
167
+ tab.dataset.label.toLowerCase() === label.toLowerCase()
168
+ );
169
+
170
+ if (index !== -1 && index !== this.activeIndex) {
171
+ this.activateTab(index, animate);
172
+ }
173
+ }
174
+
175
+ activateNextTab() {
176
+ const nextIndex = (this.activeIndex + 1) % this.tabs.length;
177
+ this.activateTab(nextIndex, true);
178
+ }
179
+
180
+ activatePreviousTab() {
181
+ const prevIndex = (this.activeIndex - 1 + this.tabs.length) % this.tabs.length;
182
+ this.activateTab(prevIndex, true);
183
+ }
184
+
185
+ updateIndicator(animate = true) {
186
+ if (!this.indicator || !this.tabs[this.activeIndex]) return;
187
+
188
+ const activeTab = this.tabs[this.activeIndex];
189
+ const tabListRect = this.tabList.getBoundingClientRect();
190
+ const activeTabRect = activeTab.getBoundingClientRect();
191
+
192
+ const left = activeTabRect.left - tabListRect.left + this.tabList.scrollLeft;
193
+ const width = activeTabRect.width;
194
+
195
+ this.indicator.style.width = `${width}px`;
196
+ this.indicator.style.transform = `translateX(${left}px)`;
197
+
198
+ if (!animate) {
199
+ this.indicator.style.transition = 'none';
200
+ void this.indicator.offsetWidth;
201
+ this.indicator.style.transition = '';
202
+ }
203
+ }
204
+
205
+ updateScrollIndicators() {
206
+ if (!this.tabList || !this.scrollContainer) return;
207
+
208
+ const { scrollLeft, scrollWidth, clientWidth } = this.tabList;
209
+ const hasOverflow = scrollWidth > clientWidth;
210
+
211
+ if (!hasOverflow) {
212
+ this.scrollContainer.classList.remove('can-scroll-left', 'can-scroll-right');
213
+ return;
214
+ }
215
+
216
+ const canScrollLeft = scrollLeft > 5;
217
+ this.scrollContainer.classList.toggle('can-scroll-left', canScrollLeft);
218
+
219
+ const canScrollRight = scrollLeft < scrollWidth - clientWidth - 5;
220
+ this.scrollContainer.classList.toggle('can-scroll-right', canScrollRight);
221
+ }
222
+
223
+ async handleCopy() {
224
+ const activePanel = this.panels[this.activeIndex];
225
+ if (!activePanel) return;
226
+
227
+ const codeText = activePanel.dataset.code || '';
228
+
229
+ try {
230
+ await this.copyToClipboard(codeText);
231
+ this.showCopySuccess();
232
+ } catch (error) {
233
+ console.warn('Failed to copy code:', error);
234
+ }
235
+ }
236
+
237
+ async copyToClipboard(text) {
238
+ if (navigator.clipboard && window.isSecureContext) {
239
+ await navigator.clipboard.writeText(text);
240
+ } else {
241
+ const textArea = document.createElement('textarea');
242
+ textArea.value = text;
243
+ textArea.style.position = 'fixed';
244
+ textArea.style.left = '-999999px';
245
+ document.body.appendChild(textArea);
246
+ textArea.select();
247
+ document.execCommand('copy');
248
+ document.body.removeChild(textArea);
249
+ }
250
+ }
251
+
252
+ showCopySuccess() {
253
+ if (!this.copyButton) return;
254
+
255
+ const iconEl = this.copyButton.querySelector('.docyard-code-group__copy-icon');
256
+ const textEl = this.copyButton.querySelector('.docyard-code-group__copy-text');
257
+
258
+ if (!iconEl || !textEl) return;
259
+
260
+ const originalIcon = iconEl.innerHTML;
261
+ const originalText = textEl.textContent;
262
+
263
+ iconEl.innerHTML = this.checkIcon;
264
+ textEl.textContent = 'Copied';
265
+ this.copyButton.classList.add('is-success');
266
+
267
+ setTimeout(() => {
268
+ iconEl.innerHTML = originalIcon;
269
+ textEl.textContent = originalText;
270
+ this.copyButton.classList.remove('is-success');
271
+ }, 2000);
272
+ }
273
+ }
274
+
275
+ function initializeCodeGroups() {
276
+ new CodeGroupManager();
277
+ }
278
+
279
+ if (document.readyState === 'loading') {
280
+ document.addEventListener('DOMContentLoaded', initializeCodeGroups);
281
+ } else {
282
+ initializeCodeGroups();
283
+ }
@@ -0,0 +1,39 @@
1
+ function initializeFileTrees() {
2
+ const fileTrees = document.querySelectorAll('.docyard-filetree');
3
+
4
+ fileTrees.forEach(tree => {
5
+ const folders = tree.querySelectorAll('.docyard-filetree__item--folder');
6
+
7
+ folders.forEach(folder => {
8
+ const entry = folder.querySelector(':scope > .docyard-filetree__entry');
9
+ const childList = folder.querySelector(':scope > .docyard-filetree__list');
10
+
11
+ if (!entry || !childList || childList.children.length === 0) return;
12
+
13
+ folder.classList.add('docyard-filetree__item--has-children');
14
+
15
+ entry.addEventListener('click', () => {
16
+ const isCollapsed = folder.classList.contains('docyard-filetree__item--collapsed');
17
+
18
+ folder.classList.toggle('docyard-filetree__item--collapsed');
19
+
20
+ const icon = entry.querySelector('.docyard-icon');
21
+ if (icon) {
22
+ if (isCollapsed) {
23
+ icon.classList.remove('docyard-icon-folder');
24
+ icon.classList.add('docyard-icon-folder-open');
25
+ } else {
26
+ icon.classList.remove('docyard-icon-folder-open');
27
+ icon.classList.add('docyard-icon-folder');
28
+ }
29
+ }
30
+ });
31
+ });
32
+ });
33
+ }
34
+
35
+ if (document.readyState === 'loading') {
36
+ document.addEventListener('DOMContentLoaded', initializeFileTrees);
37
+ } else {
38
+ initializeFileTrees();
39
+ }
@@ -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');
@@ -0,0 +1,72 @@
1
+ (function() {
2
+ 'use strict';
3
+
4
+ let lightbox = null;
5
+ let lightboxImg = null;
6
+
7
+ function createLightbox() {
8
+ if (lightbox) return;
9
+
10
+ lightbox = document.createElement('div');
11
+ lightbox.className = 'docyard-lightbox';
12
+ lightbox.innerHTML = `
13
+ <button class="docyard-lightbox-close" aria-label="Close">
14
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor">
15
+ <path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"/>
16
+ </svg>
17
+ </button>
18
+ <img src="" alt="">
19
+ `;
20
+
21
+ document.body.appendChild(lightbox);
22
+ lightboxImg = lightbox.querySelector('img');
23
+
24
+ lightbox.addEventListener('click', closeLightbox);
25
+ lightbox.querySelector('.docyard-lightbox-close').addEventListener('click', closeLightbox);
26
+
27
+ document.addEventListener('keydown', function(e) {
28
+ if (e.key === 'Escape' && lightbox.classList.contains('active')) {
29
+ closeLightbox();
30
+ }
31
+ });
32
+ }
33
+
34
+ function openLightbox(src, alt) {
35
+ createLightbox();
36
+ lightboxImg.src = src;
37
+ lightboxImg.alt = alt || '';
38
+ requestAnimationFrame(function() {
39
+ lightbox.classList.add('active');
40
+ document.body.style.overflow = 'hidden';
41
+ });
42
+ }
43
+
44
+ function closeLightbox(e) {
45
+ if (e && e.target === lightboxImg) return;
46
+ if (lightbox) {
47
+ lightbox.classList.remove('active');
48
+ document.body.style.overflow = '';
49
+ }
50
+ }
51
+
52
+ function initLightbox() {
53
+ var contentImages = document.querySelectorAll('.content img');
54
+
55
+ contentImages.forEach(function(img) {
56
+ if (img.hasAttribute('data-no-zoom')) {
57
+ img.style.cursor = 'default';
58
+ return;
59
+ }
60
+
61
+ img.addEventListener('click', function() {
62
+ openLightbox(this.src, this.alt);
63
+ });
64
+ });
65
+ }
66
+
67
+ if (document.readyState === 'loading') {
68
+ document.addEventListener('DOMContentLoaded', initLightbox);
69
+ } else {
70
+ initLightbox();
71
+ }
72
+ })();