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
@@ -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('i[class*="ph-"]');
21
+ if (icon) {
22
+ if (isCollapsed) {
23
+ icon.classList.remove('ph-folder');
24
+ icon.classList.add('ph-folder-open');
25
+ } else {
26
+ icon.classList.remove('ph-folder-open');
27
+ icon.classList.add('ph-folder');
28
+ }
29
+ }
30
+ });
31
+ });
32
+ });
33
+ }
34
+
35
+ if (document.readyState === 'loading') {
36
+ document.addEventListener('DOMContentLoaded', initializeFileTrees);
37
+ } else {
38
+ initializeFileTrees();
39
+ }
@@ -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
+ })();
@@ -267,7 +267,7 @@
267
267
  scrollTimeout = setTimeout(function() {
268
268
  sessionStorage.setItem(STORAGE_KEY, scrollContainer.scrollTop);
269
269
  }, 150);
270
- });
270
+ }, { passive: true });
271
271
 
272
272
  const logo = document.querySelector('.header-logo');
273
273
  if (logo) {
@@ -304,9 +304,9 @@
304
304
 
305
305
  updateFadeIndicators();
306
306
 
307
- scrollContainer.addEventListener('scroll', updateFadeIndicators);
307
+ scrollContainer.addEventListener('scroll', updateFadeIndicators, { passive: true });
308
308
 
309
- window.addEventListener('resize', updateFadeIndicators);
309
+ window.addEventListener('resize', updateFadeIndicators, { passive: true });
310
310
  }
311
311
 
312
312
  function initScrollBehavior() {
@@ -220,11 +220,11 @@ class SearchManager {
220
220
 
221
221
  async initPagefind() {
222
222
  try {
223
- this.pagefind = await import('/pagefind/pagefind.js');
224
- await this.pagefind.init();
223
+ this.pagefind = await import('/_docyard/pagefind/pagefind.js');
224
+ await this.pagefind.options({ baseUrl: '/' });
225
225
  } catch (error) {
226
226
  console.warn('Pagefind not available:', error);
227
- this.showErrorState('Search is not available. Run "docyard build" to generate the search index.');
227
+ this.showErrorState('Search is not available. Run "docyard serve -s" to enable search.');
228
228
  }
229
229
  }
230
230
 
@@ -308,7 +308,7 @@ class TableOfContentsManager {
308
308
  };
309
309
 
310
310
  checkOverflow();
311
- window.addEventListener('resize', checkOverflow);
311
+ window.addEventListener('resize', checkOverflow, { passive: true });
312
312
  }
313
313
 
314
314
  setupScrollFadeIndicators() {
@@ -343,11 +343,11 @@ class TableOfContentsManager {
343
343
  scrollContainer.addEventListener('scroll', () => {
344
344
  updateFadeIndicators();
345
345
  this.updateIndicator();
346
- });
346
+ }, { passive: true });
347
347
  window.addEventListener('resize', () => {
348
348
  updateFadeIndicators();
349
349
  this.updateIndicator();
350
- });
350
+ }, { passive: true });
351
351
  }
352
352
 
353
353
 
@@ -375,10 +375,16 @@ class TableOfContentsManager {
375
375
  }
376
376
  }
377
377
 
378
+ function initializeTableOfContents() {
379
+ window.tocManager = new TableOfContentsManager();
380
+ }
381
+
378
382
  if (typeof window !== 'undefined') {
379
- document.addEventListener('DOMContentLoaded', () => {
380
- window.tocManager = new TableOfContentsManager();
381
- });
383
+ if (document.readyState === 'loading') {
384
+ document.addEventListener('DOMContentLoaded', initializeTableOfContents);
385
+ } else {
386
+ initializeTableOfContents();
387
+ }
382
388
 
383
389
  window.addEventListener('beforeunload', () => {
384
390
  if (window.tocManager) {
@@ -16,6 +16,7 @@ class TabsManager {
16
16
  this.handleKeyDown = this.handleKeyDown.bind(this);
17
17
  this.handleResize = this.handleResize.bind(this);
18
18
  this.handleScroll = this.handleScroll.bind(this);
19
+ this.handleTabSync = this.handleTabSync.bind(this);
19
20
 
20
21
  this.init();
21
22
  }
@@ -30,9 +31,12 @@ class TabsManager {
30
31
  this.loadPreference();
31
32
  this.attachEventListeners();
32
33
  this.activateTab(this.activeIndex, false);
33
- this.updateIndicator();
34
+ this.updateIndicator(false);
34
35
 
35
36
  requestAnimationFrame(() => {
37
+ if (this.indicator) {
38
+ this.indicator.classList.add('is-ready');
39
+ }
36
40
  requestAnimationFrame(() => {
37
41
  this.updateScrollIndicators();
38
42
  });
@@ -63,6 +67,8 @@ class TabsManager {
63
67
  this.tabList.addEventListener('scroll', this.handleScroll);
64
68
 
65
69
  window.addEventListener('resize', this.handleResize);
70
+
71
+ window.addEventListener('docyard-tab-change', this.handleTabSync);
66
72
  }
67
73
 
68
74
 
@@ -70,7 +76,36 @@ class TabsManager {
70
76
  if (index === this.activeIndex) return;
71
77
 
72
78
  this.activateTab(index, true);
73
- this.savePreference(index);
79
+ this.broadcastTabChange(index);
80
+ }
81
+
82
+ handleTabSync(event) {
83
+ if (event.detail.sourceId === this.groupId) return;
84
+
85
+ const tabName = event.detail.tabName;
86
+ const index = this.tabs.findIndex(tab =>
87
+ tab.getAttribute('data-tab-name') === tabName
88
+ );
89
+
90
+ if (index !== -1 && index !== this.activeIndex) {
91
+ this.activateTab(index, true);
92
+ }
93
+ }
94
+
95
+ broadcastTabChange(index) {
96
+ if (index < 0 || index >= this.tabs.length) return;
97
+
98
+ const tabName = this.tabs[index].getAttribute('data-tab-name');
99
+
100
+ try {
101
+ localStorage.setItem('docyard-preferred-pm', tabName);
102
+ } catch (error) {
103
+ // Silently fail if localStorage is unavailable
104
+ }
105
+
106
+ window.dispatchEvent(new CustomEvent('docyard-tab-change', {
107
+ detail: { tabName, sourceId: this.groupId }
108
+ }));
74
109
  }
75
110
 
76
111
 
@@ -92,6 +127,7 @@ class TabsManager {
92
127
  if (key === 'Home') {
93
128
  event.preventDefault();
94
129
  this.activateTab(0, true);
130
+ this.broadcastTabChange(0);
95
131
  this.tabs[0].focus();
96
132
  }
97
133
 
@@ -99,6 +135,7 @@ class TabsManager {
99
135
  event.preventDefault();
100
136
  const lastIndex = this.tabs.length - 1;
101
137
  this.activateTab(lastIndex, true);
138
+ this.broadcastTabChange(lastIndex);
102
139
  this.tabs[lastIndex].focus();
103
140
  }
104
141
  }
@@ -153,22 +190,20 @@ class TabsManager {
153
190
  });
154
191
 
155
192
  this.updateIndicator(animate);
156
-
157
- if (previousIndex !== index) {
158
- this.savePreference(index);
159
- }
160
193
  }
161
194
 
162
195
 
163
196
  activateNextTab() {
164
197
  const nextIndex = (this.activeIndex + 1) % this.tabs.length;
165
198
  this.activateTab(nextIndex, true);
199
+ this.broadcastTabChange(nextIndex);
166
200
  }
167
201
 
168
-
202
+
169
203
  activatePreviousTab() {
170
204
  const prevIndex = (this.activeIndex - 1 + this.tabs.length) % this.tabs.length;
171
205
  this.activateTab(prevIndex, true);
206
+ this.broadcastTabChange(prevIndex);
172
207
  }
173
208
 
174
209
 
@@ -227,7 +262,7 @@ class TabsManager {
227
262
  if (!preferredTab) return;
228
263
 
229
264
  const index = this.tabs.findIndex(tab =>
230
- tab.textContent.trim().toLowerCase() === preferredTab.toLowerCase()
265
+ tab.getAttribute('data-tab-name') === preferredTab
231
266
  );
232
267
 
233
268
  if (index !== -1) {
@@ -238,22 +273,9 @@ class TabsManager {
238
273
  }
239
274
  }
240
275
 
241
-
242
- savePreference(index) {
243
- if (index < 0 || index >= this.tabs.length) return;
244
-
245
- try {
246
- const tabName = this.tabs[index].textContent.trim().toLowerCase();
247
- localStorage.setItem('docyard-preferred-pm', tabName);
248
- } catch (error) {
249
- console.warn('Could not save tab preference:', error);
250
- }
251
- }
252
-
253
-
254
276
  activateTabByName(name) {
255
277
  const index = this.tabs.findIndex(tab =>
256
- tab.textContent.trim().toLowerCase() === name.toLowerCase()
278
+ tab.getAttribute('data-tab-name') === name.toLowerCase()
257
279
  );
258
280
 
259
281
  if (index !== -1) {
@@ -270,6 +292,7 @@ class TabsManager {
270
292
  this.tabList.removeEventListener('keydown', this.handleKeyDown);
271
293
  this.tabList.removeEventListener('scroll', this.handleScroll);
272
294
  window.removeEventListener('resize', this.handleResize);
295
+ window.removeEventListener('docyard-tab-change', this.handleTabSync);
273
296
 
274
297
  if (this.resizeTimeout) {
275
298
  cancelAnimationFrame(this.resizeTimeout);
@@ -0,0 +1,118 @@
1
+ function initializeTooltips() {
2
+ const tooltips = document.querySelectorAll('.docyard-tooltip');
3
+ if (tooltips.length === 0) return;
4
+
5
+ const popover = createTooltipPopover();
6
+ document.body.appendChild(popover);
7
+
8
+ let hideTimeout;
9
+ let isHoveringPopover = false;
10
+
11
+ popover.addEventListener('mouseenter', () => {
12
+ isHoveringPopover = true;
13
+ clearTimeout(hideTimeout);
14
+ }, { passive: true });
15
+
16
+ popover.addEventListener('mouseleave', () => {
17
+ isHoveringPopover = false;
18
+ hideTimeout = setTimeout(() => {
19
+ hideTooltipPopover(popover);
20
+ }, 100);
21
+ }, { passive: true });
22
+
23
+ tooltips.forEach(tooltip => {
24
+ tooltip.addEventListener('mouseenter', () => {
25
+ clearTimeout(hideTimeout);
26
+ showTooltipPopover(popover, tooltip);
27
+ }, { passive: true });
28
+
29
+ tooltip.addEventListener('mouseleave', () => {
30
+ hideTimeout = setTimeout(() => {
31
+ if (!isHoveringPopover) {
32
+ hideTooltipPopover(popover);
33
+ }
34
+ }, 100);
35
+ }, { passive: true });
36
+ });
37
+ }
38
+
39
+ function createTooltipPopover() {
40
+ const popover = document.createElement('div');
41
+ popover.className = 'docyard-tooltip-popover';
42
+ popover.innerHTML = `
43
+ <span class="docyard-tooltip-popover__term"></span>
44
+ <span class="docyard-tooltip-popover__description"></span>
45
+ <a class="docyard-tooltip-popover__link" style="display: none;">
46
+ <span class="docyard-tooltip-popover__link-text"></span>
47
+ <svg class="docyard-tooltip-popover__link-icon" viewBox="0 0 256 256" fill="currentColor">
48
+ <path d="M221.66,133.66l-72,72a8,8,0,0,1-11.32-11.32L196.69,136H40a8,8,0,0,1,0-16H196.69l-58.35-58.34a8,8,0,0,1,11.32-11.32l72,72A8,8,0,0,1,221.66,133.66Z"></path>
49
+ </svg>
50
+ </a>
51
+ `;
52
+ return popover;
53
+ }
54
+
55
+ function showTooltipPopover(popover, tooltip) {
56
+ const term = tooltip.textContent;
57
+ const description = tooltip.dataset.description;
58
+ const link = tooltip.dataset.link;
59
+ const linkText = tooltip.dataset.linkText;
60
+
61
+ popover.querySelector('.docyard-tooltip-popover__term').textContent = term;
62
+ popover.querySelector('.docyard-tooltip-popover__description').textContent = description;
63
+
64
+ const linkEl = popover.querySelector('.docyard-tooltip-popover__link');
65
+ if (link) {
66
+ linkEl.href = link;
67
+ linkEl.querySelector('.docyard-tooltip-popover__link-text').textContent = linkText;
68
+ linkEl.style.display = 'inline-flex';
69
+ } else {
70
+ linkEl.style.display = 'none';
71
+ }
72
+
73
+ const rect = tooltip.getBoundingClientRect();
74
+ const scrollX = window.scrollX;
75
+ const scrollY = window.scrollY;
76
+
77
+ popover.style.visibility = 'hidden';
78
+ popover.classList.add('is-visible');
79
+
80
+ requestAnimationFrame(() => {
81
+ const popoverRect = popover.getBoundingClientRect();
82
+ let left = rect.left + scrollX + (rect.width / 2) - (popoverRect.width / 2);
83
+ let top = rect.top + scrollY - popoverRect.height - 8;
84
+
85
+ const viewportWidth = window.innerWidth;
86
+ const padding = 16;
87
+
88
+ if (left < padding) {
89
+ left = padding;
90
+ } else if (left + popoverRect.width > viewportWidth - padding) {
91
+ left = viewportWidth - popoverRect.width - padding;
92
+ }
93
+
94
+ if (top < scrollY + padding) {
95
+ top = rect.bottom + scrollY + 8;
96
+ popover.classList.add('is-below');
97
+ } else {
98
+ popover.classList.remove('is-below');
99
+ }
100
+
101
+ const arrowLeft = rect.left + scrollX + (rect.width / 2) - left;
102
+ popover.style.setProperty('--arrow-left', `${Math.max(12, Math.min(arrowLeft, popoverRect.width - 12))}px`);
103
+
104
+ popover.style.left = `${left}px`;
105
+ popover.style.top = `${top}px`;
106
+ popover.style.visibility = 'visible';
107
+ });
108
+ }
109
+
110
+ function hideTooltipPopover(popover) {
111
+ popover.classList.remove('is-visible');
112
+ }
113
+
114
+ if (document.readyState === 'loading') {
115
+ document.addEventListener('DOMContentLoaded', initializeTooltips);
116
+ } else {
117
+ initializeTooltips();
118
+ }
@@ -0,0 +1,44 @@
1
+ (function() {
2
+ var ssePort = window.__DOCYARD_SSE_PORT__;
3
+ if (!ssePort) return;
4
+
5
+ var url = 'http://127.0.0.1:' + ssePort + '/';
6
+ var eventSource = new EventSource(url);
7
+
8
+ eventSource.addEventListener('reload', function(event) {
9
+ var data = JSON.parse(event.data);
10
+ if (data.type === 'content') {
11
+ console.log('[Docyard] Content updated');
12
+ reloadContent();
13
+ } else {
14
+ console.log('[Docyard] Full reload');
15
+ location.reload();
16
+ }
17
+ });
18
+
19
+ eventSource.onerror = function() {
20
+ eventSource.close();
21
+ };
22
+
23
+ function reloadContent() {
24
+ fetch(location.href)
25
+ .then(function(response) { return response.text(); })
26
+ .then(function(html) {
27
+ var parser = new DOMParser();
28
+ var newDoc = parser.parseFromString(html, 'text/html');
29
+ var newContent = newDoc.querySelector('.content');
30
+ var currentContent = document.querySelector('.content');
31
+
32
+ if (newContent && currentContent) {
33
+ currentContent.innerHTML = newContent.innerHTML;
34
+ if (window.Prism) window.Prism.highlightAll();
35
+ if (window.docyardTOC) window.docyardTOC.init();
36
+ } else {
37
+ location.reload();
38
+ }
39
+ })
40
+ .catch(function() {
41
+ location.reload();
42
+ });
43
+ }
44
+ })();
@@ -4,13 +4,122 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>404 - Page Not Found</title>
7
- <link rel="stylesheet" href="/_docyard/css/main.css">
7
+ <style>
8
+ @font-face {
9
+ font-family: 'Inter';
10
+ src: url('/_docyard/fonts/Inter-Variable.ttf') format('truetype');
11
+ font-weight: 100 900;
12
+ font-style: normal;
13
+ }
14
+
15
+ :root {
16
+ --background: #ffffff;
17
+ --foreground: #0f0f10;
18
+ --muted-foreground: #71717a;
19
+ --border: #e4e4e7;
20
+ --primary: oklch(0.61 0.11 222);
21
+ --primary-foreground: oklch(0.98 0.02 201);
22
+ --radius: 0.5rem;
23
+ }
24
+
25
+ .dark {
26
+ --background: #09090b;
27
+ --foreground: #fafafa;
28
+ --muted-foreground: #a1a1aa;
29
+ --border: #27272a;
30
+ --primary: oklch(0.71 0.13 215);
31
+ --primary-foreground: oklch(0.30 0.05 230);
32
+ }
33
+
34
+ * {
35
+ margin: 0;
36
+ padding: 0;
37
+ box-sizing: border-box;
38
+ }
39
+
40
+ body {
41
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
42
+ background: var(--background);
43
+ color: var(--foreground);
44
+ min-height: 100vh;
45
+ display: flex;
46
+ align-items: center;
47
+ justify-content: center;
48
+ padding: 2rem;
49
+ }
50
+
51
+ .error-page {
52
+ text-align: center;
53
+ max-width: 450px;
54
+ }
55
+
56
+ .error-code {
57
+ font-size: 8rem;
58
+ font-weight: 700;
59
+ color: transparent;
60
+ -webkit-text-stroke: 2px var(--border);
61
+ line-height: 1;
62
+ }
63
+
64
+ .error-title {
65
+ font-size: 1.25rem;
66
+ font-weight: 600;
67
+ margin: 1rem 0 0.5rem;
68
+ }
69
+
70
+ .error-message {
71
+ color: var(--muted-foreground);
72
+ font-size: 0.9375rem;
73
+ margin-bottom: 1.5rem;
74
+ }
75
+
76
+ .btn {
77
+ display: inline-flex;
78
+ align-items: center;
79
+ gap: 0.5rem;
80
+ padding: 0.5rem 1rem;
81
+ font-size: 0.875rem;
82
+ font-weight: 500;
83
+ text-decoration: none;
84
+ border-radius: var(--radius);
85
+ background: var(--primary);
86
+ color: var(--primary-foreground);
87
+ transition: opacity 0.15s;
88
+ font-family: inherit;
89
+ }
90
+
91
+ .btn:hover {
92
+ opacity: 0.9;
93
+ }
94
+
95
+ .btn svg {
96
+ width: 16px;
97
+ height: 16px;
98
+ }
99
+
100
+ @media (max-width: 480px) {
101
+ .error-code {
102
+ font-size: 5rem;
103
+ }
104
+ }
105
+ </style>
106
+ <script>
107
+ (function() {
108
+ const theme = localStorage.getItem('theme') ||
109
+ (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
110
+ document.documentElement.classList.toggle('dark', theme === 'dark');
111
+ })();
112
+ </script>
8
113
  </head>
9
114
  <body>
10
- <main>
11
- <h1>404 - Page Not Found</h1>
12
- <p>The page you're looking for doesn't exist.</p>
13
- <p><a href="/">Go back home</a></p>
115
+ <main class="error-page">
116
+ <p class="error-code">404</p>
117
+ <h1 class="error-title">Page not found</h1>
118
+ <p class="error-message">This page doesn't exist or has been moved.</p>
119
+ <a href="/" class="btn">
120
+ <%= icon(:house_line) %>
121
+ Back to home
122
+ </a>
14
123
  </main>
15
124
  </body>
16
125
  </html>