docyard 0.8.0 → 1.0.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 (189) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +67 -1
  3. data/README.md +8 -253
  4. data/exe/docyard +6 -0
  5. data/lib/docyard/build/asset_bundler.rb +2 -2
  6. data/lib/docyard/build/file_copier.rb +12 -5
  7. data/lib/docyard/build/llms_txt_generator.rb +103 -0
  8. data/lib/docyard/build/sitemap_generator.rb +1 -1
  9. data/lib/docyard/build/static_generator.rb +115 -79
  10. data/lib/docyard/builder.rb +6 -2
  11. data/lib/docyard/cli.rb +14 -4
  12. data/lib/docyard/components/aliases.rb +12 -0
  13. data/lib/docyard/components/processors/abbreviation_processor.rb +72 -0
  14. data/lib/docyard/components/processors/accordion_processor.rb +81 -0
  15. data/lib/docyard/components/processors/badge_processor.rb +72 -0
  16. data/lib/docyard/components/processors/callout_processor.rb +9 -3
  17. data/lib/docyard/components/processors/cards_processor.rb +100 -0
  18. data/lib/docyard/components/processors/code_block_extended_fence_postprocessor.rb +24 -0
  19. data/lib/docyard/components/processors/code_block_extended_fence_preprocessor.rb +44 -0
  20. data/lib/docyard/components/processors/code_block_options_preprocessor.rb +34 -3
  21. data/lib/docyard/components/processors/code_block_processor.rb +11 -24
  22. data/lib/docyard/components/processors/code_group_processor.rb +182 -0
  23. data/lib/docyard/components/processors/code_snippet_import_preprocessor.rb +7 -1
  24. data/lib/docyard/components/processors/custom_anchor_processor.rb +42 -0
  25. data/lib/docyard/components/processors/file_tree_processor.rb +150 -0
  26. data/lib/docyard/components/processors/icon_processor.rb +8 -2
  27. data/lib/docyard/components/processors/image_caption_processor.rb +96 -0
  28. data/lib/docyard/components/processors/include_processor.rb +86 -0
  29. data/lib/docyard/components/processors/steps_processor.rb +89 -0
  30. data/lib/docyard/components/processors/tabs_processor.rb +9 -1
  31. data/lib/docyard/components/processors/tooltip_processor.rb +57 -0
  32. data/lib/docyard/components/processors/video_embed_processor.rb +207 -0
  33. data/lib/docyard/components/support/code_block/feature_extractor.rb +3 -1
  34. data/lib/docyard/components/support/code_block/icon_detector.rb +5 -12
  35. data/lib/docyard/components/support/code_block/line_number_resolver.rb +30 -0
  36. data/lib/docyard/components/support/code_detector.rb +2 -12
  37. data/lib/docyard/components/support/code_group/html_builder.rb +118 -0
  38. data/lib/docyard/components/support/markdown_code_block_helper.rb +56 -0
  39. data/lib/docyard/components/support/tabs/icon_detector.rb +6 -2
  40. data/lib/docyard/components/support/tabs/parser.rb +6 -23
  41. data/lib/docyard/config/analytics_resolver.rb +24 -0
  42. data/lib/docyard/config/branding_resolver.rb +84 -58
  43. data/lib/docyard/config/key_validator.rb +30 -0
  44. data/lib/docyard/config/logo_detector.rb +39 -0
  45. data/lib/docyard/config/schema.rb +39 -0
  46. data/lib/docyard/config/section.rb +21 -0
  47. data/lib/docyard/config/validation_helpers.rb +83 -0
  48. data/lib/docyard/config/validator.rb +45 -144
  49. data/lib/docyard/config/validators/navigation.rb +43 -0
  50. data/lib/docyard/config/validators/section.rb +114 -0
  51. data/lib/docyard/config.rb +45 -96
  52. data/lib/docyard/constants.rb +59 -0
  53. data/lib/docyard/{utils/errors.rb → errors.rb} +6 -0
  54. data/lib/docyard/initializer.rb +100 -49
  55. data/lib/docyard/navigation/page_navigation_builder.rb +65 -0
  56. data/lib/docyard/navigation/sidebar/auto_builder.rb +107 -0
  57. data/lib/docyard/navigation/sidebar/cache.rb +96 -0
  58. data/lib/docyard/navigation/sidebar/config_builder.rb +179 -0
  59. data/lib/docyard/navigation/sidebar/distributed_builder.rb +145 -0
  60. data/lib/docyard/navigation/sidebar/item.rb +6 -1
  61. data/lib/docyard/navigation/sidebar/local_config_loader.rb +69 -3
  62. data/lib/docyard/navigation/sidebar/renderer.rb +18 -3
  63. data/lib/docyard/navigation/sidebar_builder.rb +43 -81
  64. data/lib/docyard/rendering/branding_variables.rb +65 -0
  65. data/lib/docyard/rendering/icon_helpers.rb +14 -1
  66. data/lib/docyard/rendering/icons/devicons.rb +63 -0
  67. data/lib/docyard/rendering/icons.rb +26 -27
  68. data/lib/docyard/rendering/markdown.rb +20 -15
  69. data/lib/docyard/rendering/og_helpers.rb +36 -0
  70. data/lib/docyard/rendering/renderer.rb +87 -58
  71. data/lib/docyard/rendering/template_resolver.rb +14 -0
  72. data/lib/docyard/routing/fallback_resolver.rb +3 -3
  73. data/lib/docyard/search/build_indexer.rb +2 -2
  74. data/lib/docyard/search/dev_indexer.rb +36 -28
  75. data/lib/docyard/search/pagefind_support.rb +1 -1
  76. data/lib/docyard/server/asset_handler.rb +40 -15
  77. data/lib/docyard/server/dev_server.rb +90 -55
  78. data/lib/docyard/server/file_watcher.rb +68 -18
  79. data/lib/docyard/server/pagefind_handler.rb +1 -1
  80. data/lib/docyard/server/preview_server.rb +29 -33
  81. data/lib/docyard/server/rack_application.rb +38 -70
  82. data/lib/docyard/server/router.rb +11 -7
  83. data/lib/docyard/server/sse_server.rb +157 -0
  84. data/lib/docyard/server/static_file_app.rb +42 -0
  85. data/lib/docyard/templates/assets/css/components/abbreviation.css +86 -0
  86. data/lib/docyard/templates/assets/css/components/accordion.css +138 -0
  87. data/lib/docyard/templates/assets/css/components/badges.css +47 -0
  88. data/lib/docyard/templates/assets/css/components/banner.css +233 -0
  89. data/lib/docyard/templates/assets/css/components/breadcrumbs.css +2 -1
  90. data/lib/docyard/templates/assets/css/components/callout.css +26 -6
  91. data/lib/docyard/templates/assets/css/components/cards.css +100 -0
  92. data/lib/docyard/templates/assets/css/components/code-block.css +14 -2
  93. data/lib/docyard/templates/assets/css/components/code-group.css +294 -0
  94. data/lib/docyard/templates/assets/css/components/feedback.css +126 -0
  95. data/lib/docyard/templates/assets/css/components/figure.css +22 -0
  96. data/lib/docyard/templates/assets/css/components/file-tree.css +125 -0
  97. data/lib/docyard/templates/assets/css/components/heading-anchor.css +21 -13
  98. data/lib/docyard/templates/assets/css/components/icon.css +5 -0
  99. data/lib/docyard/templates/assets/css/components/lightbox.css +65 -0
  100. data/lib/docyard/templates/assets/css/components/nav-menu.css +20 -4
  101. data/lib/docyard/templates/assets/css/components/navigation.css +32 -3
  102. data/lib/docyard/templates/assets/css/components/page-actions.css +131 -0
  103. data/lib/docyard/templates/assets/css/components/prev-next.css +20 -22
  104. data/lib/docyard/templates/assets/css/components/search.css +6 -10
  105. data/lib/docyard/templates/assets/css/components/steps.css +122 -0
  106. data/lib/docyard/templates/assets/css/components/tab-bar.css +7 -4
  107. data/lib/docyard/templates/assets/css/components/table-of-contents.css +57 -11
  108. data/lib/docyard/templates/assets/css/components/tabs.css +13 -5
  109. data/lib/docyard/templates/assets/css/components/theme-toggle.css +3 -1
  110. data/lib/docyard/templates/assets/css/components/tooltip.css +113 -0
  111. data/lib/docyard/templates/assets/css/components/video.css +41 -0
  112. data/lib/docyard/templates/assets/css/landing.css +82 -13
  113. data/lib/docyard/templates/assets/css/layout.css +17 -0
  114. data/lib/docyard/templates/assets/css/markdown.css +25 -3
  115. data/lib/docyard/templates/assets/css/variables.css +13 -1
  116. data/lib/docyard/templates/assets/js/components/abbreviation.js +85 -0
  117. data/lib/docyard/templates/assets/js/components/banner.js +81 -0
  118. data/lib/docyard/templates/assets/js/components/code-group.js +286 -0
  119. data/lib/docyard/templates/assets/js/components/copy-page.js +115 -0
  120. data/lib/docyard/templates/assets/js/components/feedback.js +66 -0
  121. data/lib/docyard/templates/assets/js/components/file-tree.js +39 -0
  122. data/lib/docyard/templates/assets/js/components/lightbox.js +72 -0
  123. data/lib/docyard/templates/assets/js/components/navigation.js +3 -3
  124. data/lib/docyard/templates/assets/js/components/search.js +3 -3
  125. data/lib/docyard/templates/assets/js/components/table-of-contents.js +12 -6
  126. data/lib/docyard/templates/assets/js/components/tabs.js +45 -22
  127. data/lib/docyard/templates/assets/js/components/tooltip.js +118 -0
  128. data/lib/docyard/templates/assets/js/hot-reload.js +44 -0
  129. data/lib/docyard/templates/errors/404.html.erb +114 -5
  130. data/lib/docyard/templates/errors/500.html.erb +173 -10
  131. data/lib/docyard/templates/init/_sidebar.yml +36 -0
  132. data/lib/docyard/templates/init/docyard.yml +36 -0
  133. data/lib/docyard/templates/init/pages/components.md +146 -0
  134. data/lib/docyard/templates/init/pages/getting-started.md +94 -0
  135. data/lib/docyard/templates/init/pages/index.md +22 -0
  136. data/lib/docyard/templates/layouts/default.html.erb +11 -0
  137. data/lib/docyard/templates/layouts/splash.html.erb +15 -1
  138. data/lib/docyard/templates/partials/_accordion.html.erb +9 -0
  139. data/lib/docyard/templates/partials/_analytics.html.erb +24 -0
  140. data/lib/docyard/templates/partials/_banner.html.erb +27 -0
  141. data/lib/docyard/templates/partials/_card.html.erb +23 -0
  142. data/lib/docyard/templates/partials/_code_block.html.erb +1 -1
  143. data/lib/docyard/templates/partials/_feedback.html.erb +14 -0
  144. data/lib/docyard/templates/partials/_footer.html.erb +1 -1
  145. data/lib/docyard/templates/partials/_head.html.erb +79 -4
  146. data/lib/docyard/templates/partials/_icon_library.html.erb +8 -0
  147. data/lib/docyard/templates/partials/_nav_group.html.erb +6 -0
  148. data/lib/docyard/templates/partials/_nav_leaf.html.erb +3 -0
  149. data/lib/docyard/templates/partials/_page_actions.html.erb +21 -0
  150. data/lib/docyard/templates/partials/_scripts.html.erb +6 -3
  151. data/lib/docyard/templates/partials/_step.html.erb +14 -0
  152. data/lib/docyard/templates/partials/_tabs.html.erb +4 -1
  153. data/lib/docyard/utils/git_info.rb +157 -0
  154. data/lib/docyard/utils/hash_utils.rb +31 -0
  155. data/lib/docyard/utils/html_helpers.rb +8 -0
  156. data/lib/docyard/utils/logging.rb +44 -3
  157. data/lib/docyard/utils/path_resolver.rb +0 -10
  158. data/lib/docyard/utils/path_utils.rb +73 -0
  159. data/lib/docyard/version.rb +1 -1
  160. data/lib/docyard.rb +2 -2
  161. metadata +114 -47
  162. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -31
  163. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
  164. data/.github/pull_request_template.md +0 -14
  165. data/.github/workflows/ci.yml +0 -49
  166. data/.rubocop.yml +0 -42
  167. data/CODE_OF_CONDUCT.md +0 -132
  168. data/CONTRIBUTING.md +0 -55
  169. data/LICENSE.vscode-icons +0 -42
  170. data/Rakefile +0 -8
  171. data/lib/docyard/config/constants.rb +0 -31
  172. data/lib/docyard/navigation/sidebar/children_discoverer.rb +0 -51
  173. data/lib/docyard/navigation/sidebar/config_parser.rb +0 -208
  174. data/lib/docyard/navigation/sidebar/file_resolver.rb +0 -78
  175. data/lib/docyard/navigation/sidebar/file_system_scanner.rb +0 -78
  176. data/lib/docyard/navigation/sidebar/metadata_extractor.rb +0 -69
  177. data/lib/docyard/navigation/sidebar/metadata_reader.rb +0 -47
  178. data/lib/docyard/navigation/sidebar/path_prefixer.rb +0 -34
  179. data/lib/docyard/navigation/sidebar/sorter.rb +0 -21
  180. data/lib/docyard/navigation/sidebar/title_extractor.rb +0 -25
  181. data/lib/docyard/navigation/sidebar/tree_builder.rb +0 -139
  182. data/lib/docyard/rendering/icons/LICENSE.phosphor +0 -21
  183. data/lib/docyard/rendering/icons/file_types.rb +0 -79
  184. data/lib/docyard/rendering/icons/phosphor.rb +0 -90
  185. data/lib/docyard/rendering/language_mapping.rb +0 -52
  186. data/lib/docyard/templates/assets/js/reload.js +0 -98
  187. data/lib/docyard/templates/partials/_icon.html.erb +0 -1
  188. data/lib/docyard/templates/partials/_icon_file_extension.html.erb +0 -1
  189. data/sig/docyard.rbs +0 -4
@@ -21,6 +21,26 @@
21
21
  font-variant-ligatures: none;
22
22
  }
23
23
 
24
+ .content .docyard-callout--note code:not(.highlight code) {
25
+ background-color: oklch(from var(--callout-note-border) l c h / 15%);
26
+ }
27
+
28
+ .content .docyard-callout--tip code:not(.highlight code) {
29
+ background-color: oklch(from var(--callout-tip-border) l c h / 15%);
30
+ }
31
+
32
+ .content .docyard-callout--important code:not(.highlight code) {
33
+ background-color: oklch(from var(--callout-important-border) l c h / 15%);
34
+ }
35
+
36
+ .content .docyard-callout--warning code:not(.highlight code) {
37
+ background-color: oklch(from var(--callout-warning-border) l c h / 15%);
38
+ }
39
+
40
+ .content .docyard-callout--danger code:not(.highlight code) {
41
+ background-color: oklch(from var(--callout-danger-border) l c h / 15%);
42
+ }
43
+
24
44
  .content ul,
25
45
  .content ol {
26
46
  margin: var(--spacing-5) 0;
@@ -145,14 +165,16 @@
145
165
  }
146
166
 
147
167
  .content img {
168
+ display: block;
148
169
  max-width: 100%;
149
170
  height: auto;
150
171
  border-radius: var(--radius-xl);
151
- margin: var(--spacing-4) 0;
172
+ margin: var(--spacing-4) auto;
152
173
  border: 1px solid var(--border);
174
+ cursor: zoom-in;
153
175
  }
154
176
 
155
- .content a:not(.heading-anchor):not(.pager-link):not(.breadcrumb-link):not(.site-footer__link):not(.site-footer__attribution) {
177
+ .content a:not(.heading-anchor):not(.pager-link):not(.breadcrumb-link):not(.site-footer__link):not(.site-footer__attribution):not(.docyard-card):not(.docyard-announcement__link):not(.docyard-announcement__button):not(.page-actions__edit-link) {
156
178
  color: var(--foreground);
157
179
  font-weight: var(--font-semibold);
158
180
  text-decoration: none;
@@ -160,7 +182,7 @@
160
182
  transition: border-bottom var(--transition-fast);
161
183
  }
162
184
 
163
- .content a:not(.heading-anchor):not(.pager-link):not(.breadcrumb-link):not(.site-footer__link):not(.site-footer__attribution):hover {
185
+ .content a:not(.heading-anchor):not(.pager-link):not(.breadcrumb-link):not(.site-footer__link):not(.site-footer__attribution):not(.docyard-card):not(.docyard-announcement__link):not(.docyard-announcement__button):not(.page-actions__edit-link):hover {
164
186
  border-bottom: 2px solid var(--primary);
165
187
  }
166
188
 
@@ -3,7 +3,7 @@
3
3
  src: url('/_docyard/fonts/Inter-Variable.ttf') format('truetype');
4
4
  font-weight: 100 900;
5
5
  font-style: normal;
6
- font-display: optional;
6
+ font-display: swap;
7
7
  }
8
8
 
9
9
  :root {
@@ -308,3 +308,15 @@
308
308
  --text-4xl: 1.875rem;
309
309
  }
310
310
  }
311
+
312
+ .no-transition,
313
+ .no-transition *,
314
+ .no-transition *::before,
315
+ .no-transition *::after {
316
+ transition: none !important;
317
+ animation: none !important;
318
+ }
319
+
320
+ .banner-dismissed .docyard-announcement {
321
+ display: none !important;
322
+ }
@@ -0,0 +1,85 @@
1
+ function initializeAbbreviations() {
2
+ const abbreviations = document.querySelectorAll('.docyard-abbr');
3
+ if (abbreviations.length === 0) return;
4
+
5
+ const popover = createPopover();
6
+ document.body.appendChild(popover);
7
+
8
+ let hideTimeout;
9
+
10
+ abbreviations.forEach(abbr => {
11
+ abbr.addEventListener('mouseenter', () => {
12
+ clearTimeout(hideTimeout);
13
+ showPopover(popover, abbr);
14
+ });
15
+
16
+ abbr.addEventListener('mouseleave', () => {
17
+ hideTimeout = setTimeout(() => {
18
+ hidePopover(popover);
19
+ }, 100);
20
+ });
21
+ });
22
+ }
23
+
24
+ function createPopover() {
25
+ const popover = document.createElement('div');
26
+ popover.className = 'docyard-abbr-popover';
27
+ popover.innerHTML = `
28
+ <span class="docyard-abbr-popover__term"></span>
29
+ <span class="docyard-abbr-popover__definition"></span>
30
+ `;
31
+ return popover;
32
+ }
33
+
34
+ function showPopover(popover, abbr) {
35
+ const term = abbr.textContent;
36
+ const definition = abbr.dataset.definition;
37
+
38
+ popover.querySelector('.docyard-abbr-popover__term').textContent = term;
39
+ popover.querySelector('.docyard-abbr-popover__definition').textContent = definition;
40
+
41
+ const rect = abbr.getBoundingClientRect();
42
+ const scrollX = window.scrollX;
43
+ const scrollY = window.scrollY;
44
+
45
+ popover.style.visibility = 'hidden';
46
+ popover.classList.add('is-visible');
47
+ popover.classList.remove('is-below');
48
+
49
+ requestAnimationFrame(() => {
50
+ const popoverRect = popover.getBoundingClientRect();
51
+ let left = rect.left + scrollX + (rect.width / 2) - (popoverRect.width / 2);
52
+ let top = rect.top + scrollY - popoverRect.height - 8;
53
+
54
+ const viewportWidth = window.innerWidth;
55
+ const padding = 16;
56
+
57
+ if (left < padding) {
58
+ left = padding;
59
+ } else if (left + popoverRect.width > viewportWidth - padding) {
60
+ left = viewportWidth - popoverRect.width - padding;
61
+ }
62
+
63
+ if (top < scrollY + padding) {
64
+ top = rect.bottom + scrollY + 8;
65
+ popover.classList.add('is-below');
66
+ }
67
+
68
+ const arrowLeft = rect.left + scrollX + (rect.width / 2) - left;
69
+ popover.style.setProperty('--arrow-left', `${Math.max(12, Math.min(arrowLeft, popoverRect.width - 12))}px`);
70
+
71
+ popover.style.left = `${left}px`;
72
+ popover.style.top = `${top}px`;
73
+ popover.style.visibility = 'visible';
74
+ });
75
+ }
76
+
77
+ function hidePopover(popover) {
78
+ popover.classList.remove('is-visible');
79
+ }
80
+
81
+ if (document.readyState === 'loading') {
82
+ document.addEventListener('DOMContentLoaded', initializeAbbreviations);
83
+ } else {
84
+ initializeAbbreviations();
85
+ }
@@ -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
+ })();
@@ -0,0 +1,286 @@
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(false);
63
+
64
+ requestAnimationFrame(() => {
65
+ if (this.indicator) {
66
+ this.indicator.classList.add('is-ready');
67
+ }
68
+ this.updateScrollIndicators();
69
+ });
70
+ }
71
+
72
+ attachEventListeners() {
73
+ this.tabs.forEach((tab, index) => {
74
+ tab.addEventListener('click', () => this.handleTabClick(index));
75
+ });
76
+
77
+ this.tabList.addEventListener('keydown', (e) => this.handleKeyDown(e));
78
+ this.tabList.addEventListener('scroll', () => this.handleScroll());
79
+ window.addEventListener('resize', () => this.handleResize());
80
+
81
+ if (this.copyButton) {
82
+ this.copyButton.addEventListener('click', () => this.handleCopy());
83
+ }
84
+ }
85
+
86
+ handleScroll() {
87
+ if (this.scrollTimeout) {
88
+ cancelAnimationFrame(this.scrollTimeout);
89
+ }
90
+
91
+ this.scrollTimeout = requestAnimationFrame(() => {
92
+ this.updateScrollIndicators();
93
+ });
94
+ }
95
+
96
+ handleTabClick(index) {
97
+ if (index === this.activeIndex) return;
98
+
99
+ const label = this.tabs[index].dataset.label;
100
+ this.manager.syncTabs(label);
101
+ }
102
+
103
+ handleKeyDown(event) {
104
+ const { key } = event;
105
+
106
+ if (key === 'ArrowLeft' || key === 'ArrowRight') {
107
+ event.preventDefault();
108
+
109
+ if (key === 'ArrowLeft') {
110
+ this.activatePreviousTab();
111
+ } else {
112
+ this.activateNextTab();
113
+ }
114
+
115
+ this.tabs[this.activeIndex].focus();
116
+ const label = this.tabs[this.activeIndex].dataset.label;
117
+ this.manager.syncTabs(label);
118
+ }
119
+
120
+ if (key === 'Home') {
121
+ event.preventDefault();
122
+ this.activateTab(0, true);
123
+ this.tabs[0].focus();
124
+ const label = this.tabs[0].dataset.label;
125
+ this.manager.syncTabs(label);
126
+ }
127
+
128
+ if (key === 'End') {
129
+ event.preventDefault();
130
+ const lastIndex = this.tabs.length - 1;
131
+ this.activateTab(lastIndex, true);
132
+ this.tabs[lastIndex].focus();
133
+ const label = this.tabs[lastIndex].dataset.label;
134
+ this.manager.syncTabs(label);
135
+ }
136
+ }
137
+
138
+ handleResize() {
139
+ if (this.resizeTimeout) {
140
+ cancelAnimationFrame(this.resizeTimeout);
141
+ }
142
+
143
+ this.resizeTimeout = requestAnimationFrame(() => {
144
+ this.updateIndicator(false);
145
+ this.updateScrollIndicators();
146
+ });
147
+ }
148
+
149
+ activateTab(index, animate = true) {
150
+ if (index < 0 || index >= this.tabs.length) return;
151
+
152
+ this.activeIndex = index;
153
+
154
+ this.tabs.forEach((tab, i) => {
155
+ const isActive = i === index;
156
+ tab.setAttribute('aria-selected', isActive ? 'true' : 'false');
157
+ tab.setAttribute('tabindex', isActive ? '0' : '-1');
158
+ });
159
+
160
+ this.panels.forEach((panel, i) => {
161
+ const isActive = i === index;
162
+ panel.setAttribute('aria-hidden', isActive ? 'false' : 'true');
163
+ });
164
+
165
+ this.updateIndicator(animate);
166
+ }
167
+
168
+ activateTabByLabel(label, animate = true) {
169
+ const index = this.tabs.findIndex(tab =>
170
+ tab.dataset.label.toLowerCase() === label.toLowerCase()
171
+ );
172
+
173
+ if (index !== -1 && index !== this.activeIndex) {
174
+ this.activateTab(index, animate);
175
+ }
176
+ }
177
+
178
+ activateNextTab() {
179
+ const nextIndex = (this.activeIndex + 1) % this.tabs.length;
180
+ this.activateTab(nextIndex, true);
181
+ }
182
+
183
+ activatePreviousTab() {
184
+ const prevIndex = (this.activeIndex - 1 + this.tabs.length) % this.tabs.length;
185
+ this.activateTab(prevIndex, true);
186
+ }
187
+
188
+ updateIndicator(animate = true) {
189
+ if (!this.indicator || !this.tabs[this.activeIndex]) return;
190
+
191
+ const activeTab = this.tabs[this.activeIndex];
192
+ const tabListRect = this.tabList.getBoundingClientRect();
193
+ const activeTabRect = activeTab.getBoundingClientRect();
194
+
195
+ const left = activeTabRect.left - tabListRect.left + this.tabList.scrollLeft;
196
+ const width = activeTabRect.width;
197
+
198
+ this.indicator.style.width = `${width}px`;
199
+ this.indicator.style.transform = `translateX(${left}px)`;
200
+
201
+ if (!animate) {
202
+ this.indicator.style.transition = 'none';
203
+ void this.indicator.offsetWidth;
204
+ this.indicator.style.transition = '';
205
+ }
206
+ }
207
+
208
+ updateScrollIndicators() {
209
+ if (!this.tabList || !this.scrollContainer) return;
210
+
211
+ const { scrollLeft, scrollWidth, clientWidth } = this.tabList;
212
+ const hasOverflow = scrollWidth > clientWidth;
213
+
214
+ if (!hasOverflow) {
215
+ this.scrollContainer.classList.remove('can-scroll-left', 'can-scroll-right');
216
+ return;
217
+ }
218
+
219
+ const canScrollLeft = scrollLeft > 5;
220
+ this.scrollContainer.classList.toggle('can-scroll-left', canScrollLeft);
221
+
222
+ const canScrollRight = scrollLeft < scrollWidth - clientWidth - 5;
223
+ this.scrollContainer.classList.toggle('can-scroll-right', canScrollRight);
224
+ }
225
+
226
+ async handleCopy() {
227
+ const activePanel = this.panels[this.activeIndex];
228
+ if (!activePanel) return;
229
+
230
+ const codeText = activePanel.dataset.code || '';
231
+
232
+ try {
233
+ await this.copyToClipboard(codeText);
234
+ this.showCopySuccess();
235
+ } catch (error) {
236
+ console.warn('Failed to copy code:', error);
237
+ }
238
+ }
239
+
240
+ async copyToClipboard(text) {
241
+ if (navigator.clipboard && window.isSecureContext) {
242
+ await navigator.clipboard.writeText(text);
243
+ } else {
244
+ const textArea = document.createElement('textarea');
245
+ textArea.value = text;
246
+ textArea.style.position = 'fixed';
247
+ textArea.style.left = '-999999px';
248
+ document.body.appendChild(textArea);
249
+ textArea.select();
250
+ document.execCommand('copy');
251
+ document.body.removeChild(textArea);
252
+ }
253
+ }
254
+
255
+ showCopySuccess() {
256
+ if (!this.copyButton) return;
257
+
258
+ const iconEl = this.copyButton.querySelector('.docyard-code-group__copy-icon');
259
+ const textEl = this.copyButton.querySelector('.docyard-code-group__copy-text');
260
+
261
+ if (!iconEl || !textEl) return;
262
+
263
+ const originalIcon = iconEl.innerHTML;
264
+ const originalText = textEl.textContent;
265
+
266
+ iconEl.innerHTML = this.checkIcon;
267
+ textEl.textContent = 'Copied';
268
+ this.copyButton.classList.add('is-success');
269
+
270
+ setTimeout(() => {
271
+ iconEl.innerHTML = originalIcon;
272
+ textEl.textContent = originalText;
273
+ this.copyButton.classList.remove('is-success');
274
+ }, 2000);
275
+ }
276
+ }
277
+
278
+ function initializeCodeGroups() {
279
+ new CodeGroupManager();
280
+ }
281
+
282
+ if (document.readyState === 'loading') {
283
+ document.addEventListener('DOMContentLoaded', initializeCodeGroups);
284
+ } else {
285
+ initializeCodeGroups();
286
+ }
@@ -0,0 +1,115 @@
1
+ class CopyPageManager {
2
+ constructor() {
3
+ this.buttons = document.querySelectorAll('[data-copy-page]');
4
+ this.markdownElement = document.getElementById('page-markdown');
5
+
6
+ this.handleCopy = this.handleCopy.bind(this);
7
+ this.init();
8
+ }
9
+
10
+ init() {
11
+ if (!this.markdownElement || this.buttons.length === 0) return;
12
+
13
+ this.buttons.forEach(button => {
14
+ button.addEventListener('click', this.handleCopy);
15
+ });
16
+ }
17
+
18
+ getMarkdownContent() {
19
+ if (!this.markdownElement) return null;
20
+
21
+ try {
22
+ return JSON.parse(this.markdownElement.textContent);
23
+ } catch (error) {
24
+ console.warn('Failed to parse markdown content:', error);
25
+ return null;
26
+ }
27
+ }
28
+
29
+ async handleCopy(event) {
30
+ const button = event.currentTarget;
31
+ const content = this.getMarkdownContent();
32
+
33
+ if (!content) {
34
+ console.warn('No markdown content available to copy');
35
+ return;
36
+ }
37
+
38
+ try {
39
+ await this.copyToClipboard(content);
40
+ this.showSuccess(button);
41
+ } catch (error) {
42
+ console.warn('Failed to copy page:', error);
43
+ }
44
+ }
45
+
46
+ async copyToClipboard(text) {
47
+ if (navigator.clipboard && window.isSecureContext) {
48
+ await navigator.clipboard.writeText(text);
49
+ } else {
50
+ this.fallbackCopy(text);
51
+ }
52
+ }
53
+
54
+ fallbackCopy(text) {
55
+ const textArea = document.createElement('textarea');
56
+ textArea.value = text;
57
+ textArea.style.position = 'fixed';
58
+ textArea.style.left = '-999999px';
59
+ textArea.style.top = '-999999px';
60
+ document.body.appendChild(textArea);
61
+ textArea.focus();
62
+ textArea.select();
63
+
64
+ try {
65
+ document.execCommand('copy');
66
+ textArea.remove();
67
+ } catch (error) {
68
+ textArea.remove();
69
+ throw error;
70
+ }
71
+ }
72
+
73
+ showSuccess(button) {
74
+ const iconElement = button.querySelector('i[class*="ph-"]');
75
+ const textElement = button.querySelector('.page-actions__copy-text');
76
+ const originalClasses = iconElement?.className;
77
+
78
+ button.classList.add('is-copied');
79
+
80
+ if (iconElement) {
81
+ iconElement.className = 'ph ph-check';
82
+ iconElement.classList.add('icon-animate-in');
83
+ }
84
+ if (textElement) {
85
+ textElement.textContent = 'Copied';
86
+ }
87
+
88
+ setTimeout(() => {
89
+ button.classList.remove('is-copied');
90
+ if (iconElement && originalClasses) {
91
+ iconElement.classList.add('icon-animate-out');
92
+ setTimeout(() => {
93
+ iconElement.className = originalClasses;
94
+ }, 150);
95
+ }
96
+ if (textElement) {
97
+ textElement.textContent = 'Copy page';
98
+ }
99
+ }, 2000);
100
+ }
101
+ }
102
+
103
+ function initializeCopyPage() {
104
+ new CopyPageManager();
105
+ }
106
+
107
+ if (document.readyState === 'loading') {
108
+ document.addEventListener('DOMContentLoaded', initializeCopyPage);
109
+ } else {
110
+ initializeCopyPage();
111
+ }
112
+
113
+ if (typeof module !== 'undefined' && module.exports) {
114
+ module.exports = { CopyPageManager };
115
+ }
@@ -0,0 +1,66 @@
1
+ class FeedbackManager {
2
+ constructor() {
3
+ this.container = document.querySelector('.feedback');
4
+ if (!this.container) return;
5
+
6
+ this.buttons = this.container.querySelectorAll('.feedback__btn');
7
+ this.thanks = this.container.querySelector('.feedback__thanks');
8
+ this.pagePath = window.location.pathname;
9
+
10
+ this.init();
11
+ }
12
+
13
+ init() {
14
+ this.buttons.forEach(button => {
15
+ button.addEventListener('click', (e) => this.handleFeedback(e));
16
+ });
17
+ }
18
+
19
+ handleFeedback(event) {
20
+ const button = event.currentTarget;
21
+ const value = button.dataset.feedback;
22
+ const isHelpful = value === 'yes';
23
+
24
+ this.updateUI(button);
25
+ this.sendAnalytics(isHelpful);
26
+ }
27
+
28
+ updateUI(selectedButton) {
29
+ this.buttons.forEach(button => {
30
+ if (button === selectedButton) {
31
+ button.classList.add('is-selected');
32
+ } else {
33
+ button.classList.add('is-not-selected');
34
+ }
35
+ });
36
+
37
+ setTimeout(() => {
38
+ this.container.classList.add('is-submitted');
39
+ this.thanks.hidden = false;
40
+ }, 600);
41
+ }
42
+
43
+ sendAnalytics(isHelpful) {
44
+ const helpful = isHelpful ? 'yes' : 'no';
45
+
46
+ if (typeof gtag === 'function') {
47
+ gtag('event', 'page_feedback', {
48
+ feedback_page: this.pagePath,
49
+ helpful: helpful,
50
+ value: isHelpful ? 1 : 0
51
+ });
52
+ }
53
+
54
+ if (typeof plausible === 'function') {
55
+ plausible('Feedback', { props: { helpful: helpful, page: this.pagePath } });
56
+ }
57
+
58
+ if (typeof fathom === 'object' && typeof fathom.trackEvent === 'function') {
59
+ fathom.trackEvent(`feedback_${helpful}`);
60
+ }
61
+ }
62
+ }
63
+
64
+ document.addEventListener('DOMContentLoaded', () => {
65
+ new FeedbackManager();
66
+ });