docyard 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +5 -1
  3. data/CHANGELOG.md +34 -1
  4. data/lib/docyard/build/asset_bundler.rb +22 -7
  5. data/lib/docyard/build/file_copier.rb +49 -27
  6. data/lib/docyard/build/sitemap_generator.rb +6 -6
  7. data/lib/docyard/build/static_generator.rb +82 -50
  8. data/lib/docyard/builder.rb +20 -10
  9. data/lib/docyard/cli.rb +6 -3
  10. data/lib/docyard/components/aliases.rb +29 -0
  11. data/lib/docyard/components/processors/callout_processor.rb +124 -0
  12. data/lib/docyard/components/processors/code_block_diff_preprocessor.rb +106 -0
  13. data/lib/docyard/components/processors/code_block_focus_preprocessor.rb +79 -0
  14. data/lib/docyard/components/processors/code_block_options_preprocessor.rb +78 -0
  15. data/lib/docyard/components/processors/code_block_processor.rb +175 -0
  16. data/lib/docyard/components/processors/code_snippet_import_preprocessor.rb +127 -0
  17. data/lib/docyard/components/processors/heading_anchor_processor.rb +39 -0
  18. data/lib/docyard/components/processors/icon_processor.rb +53 -0
  19. data/lib/docyard/components/processors/table_of_contents_processor.rb +68 -0
  20. data/lib/docyard/components/processors/table_wrapper_processor.rb +22 -0
  21. data/lib/docyard/components/processors/tabs_processor.rb +48 -0
  22. data/lib/docyard/components/support/code_block/feature_extractor.rb +117 -0
  23. data/lib/docyard/components/support/code_block/icon_detector.rb +44 -0
  24. data/lib/docyard/components/support/code_block/line_parser.rb +84 -0
  25. data/lib/docyard/components/support/code_block/line_wrapper.rb +50 -0
  26. data/lib/docyard/components/support/code_block/patterns.rb +55 -0
  27. data/lib/docyard/components/support/code_detector.rb +61 -0
  28. data/lib/docyard/components/support/tabs/icon_detector.rb +62 -0
  29. data/lib/docyard/components/support/tabs/parser.rb +195 -0
  30. data/lib/docyard/components/support/tabs/range_finder.rb +46 -0
  31. data/lib/docyard/config/branding_resolver.rb +183 -0
  32. data/lib/docyard/{constants.rb → config/constants.rb} +7 -4
  33. data/lib/docyard/config/validator.rb +122 -99
  34. data/lib/docyard/config.rb +38 -36
  35. data/lib/docyard/initializer.rb +15 -76
  36. data/lib/docyard/navigation/breadcrumb_builder.rb +133 -0
  37. data/lib/docyard/{prev_next_builder.rb → navigation/prev_next_builder.rb} +6 -3
  38. data/lib/docyard/navigation/sidebar/children_discoverer.rb +51 -0
  39. data/lib/docyard/navigation/sidebar/config_parser.rb +208 -0
  40. data/lib/docyard/navigation/sidebar/file_resolver.rb +78 -0
  41. data/lib/docyard/{sidebar → navigation/sidebar}/file_system_scanner.rb +2 -1
  42. data/lib/docyard/navigation/sidebar/item.rb +96 -0
  43. data/lib/docyard/navigation/sidebar/local_config_loader.rb +51 -0
  44. data/lib/docyard/navigation/sidebar/metadata_extractor.rb +69 -0
  45. data/lib/docyard/navigation/sidebar/metadata_reader.rb +47 -0
  46. data/lib/docyard/navigation/sidebar/path_prefixer.rb +34 -0
  47. data/lib/docyard/navigation/sidebar/renderer.rb +144 -0
  48. data/lib/docyard/navigation/sidebar/sorter.rb +21 -0
  49. data/lib/docyard/navigation/sidebar/tree_builder.rb +139 -0
  50. data/lib/docyard/navigation/sidebar/tree_filter.rb +55 -0
  51. data/lib/docyard/navigation/sidebar_builder.rb +159 -0
  52. data/lib/docyard/rendering/icon_helpers.rb +13 -0
  53. data/lib/docyard/{icons → rendering/icons}/phosphor.rb +26 -1
  54. data/lib/docyard/{markdown.rb → rendering/markdown.rb} +19 -13
  55. data/lib/docyard/rendering/renderer.rb +163 -0
  56. data/lib/docyard/rendering/template_resolver.rb +172 -0
  57. data/lib/docyard/routing/fallback_resolver.rb +92 -0
  58. data/lib/docyard/search/build_indexer.rb +74 -0
  59. data/lib/docyard/search/dev_indexer.rb +155 -0
  60. data/lib/docyard/search/pagefind_support.rb +33 -0
  61. data/lib/docyard/{asset_handler.rb → server/asset_handler.rb} +24 -19
  62. data/lib/docyard/{server.rb → server/dev_server.rb} +32 -9
  63. data/lib/docyard/server/pagefind_handler.rb +63 -0
  64. data/lib/docyard/{preview_server.rb → server/preview_server.rb} +2 -2
  65. data/lib/docyard/server/rack_application.rb +192 -0
  66. data/lib/docyard/server/resolution_result.rb +29 -0
  67. data/lib/docyard/{router.rb → server/router.rb} +4 -4
  68. data/lib/docyard/templates/assets/css/code.css +18 -51
  69. data/lib/docyard/templates/assets/css/components/breadcrumbs.css +143 -0
  70. data/lib/docyard/templates/assets/css/components/callout.css +67 -67
  71. data/lib/docyard/templates/assets/css/components/code-block.css +180 -282
  72. data/lib/docyard/templates/assets/css/components/heading-anchor.css +28 -15
  73. data/lib/docyard/templates/assets/css/components/icon.css +0 -1
  74. data/lib/docyard/templates/assets/css/components/logo.css +0 -2
  75. data/lib/docyard/templates/assets/css/components/nav-menu.css +237 -0
  76. data/lib/docyard/templates/assets/css/components/navigation.css +186 -167
  77. data/lib/docyard/templates/assets/css/components/prev-next.css +76 -47
  78. data/lib/docyard/templates/assets/css/components/search.css +561 -0
  79. data/lib/docyard/templates/assets/css/components/tab-bar.css +163 -0
  80. data/lib/docyard/templates/assets/css/components/table-of-contents.css +127 -114
  81. data/lib/docyard/templates/assets/css/components/tabs.css +119 -160
  82. data/lib/docyard/templates/assets/css/components/theme-toggle.css +48 -44
  83. data/lib/docyard/templates/assets/css/landing.css +815 -0
  84. data/lib/docyard/templates/assets/css/layout.css +503 -87
  85. data/lib/docyard/templates/assets/css/main.css +1 -3
  86. data/lib/docyard/templates/assets/css/markdown.css +111 -93
  87. data/lib/docyard/templates/assets/css/reset.css +0 -3
  88. data/lib/docyard/templates/assets/css/typography.css +43 -41
  89. data/lib/docyard/templates/assets/css/variables.css +268 -208
  90. data/lib/docyard/templates/assets/favicon.svg +7 -8
  91. data/lib/docyard/templates/assets/fonts/Inter-Variable.ttf +0 -0
  92. data/lib/docyard/templates/assets/js/components/code-block.js +24 -42
  93. data/lib/docyard/templates/assets/js/components/heading-anchor.js +26 -24
  94. data/lib/docyard/templates/assets/js/components/navigation.js +181 -70
  95. data/lib/docyard/templates/assets/js/components/search.js +610 -0
  96. data/lib/docyard/templates/assets/js/components/sidebar-toggle.js +29 -0
  97. data/lib/docyard/templates/assets/js/components/tab-navigation.js +145 -0
  98. data/lib/docyard/templates/assets/js/components/table-of-contents.js +153 -66
  99. data/lib/docyard/templates/assets/js/components/tabs.js +31 -69
  100. data/lib/docyard/templates/assets/js/theme.js +0 -3
  101. data/lib/docyard/templates/assets/logo-dark.svg +8 -2
  102. data/lib/docyard/templates/assets/logo.svg +7 -4
  103. data/lib/docyard/templates/config/docyard.yml.erb +37 -34
  104. data/lib/docyard/templates/errors/404.html.erb +1 -1
  105. data/lib/docyard/templates/errors/500.html.erb +1 -1
  106. data/lib/docyard/templates/layouts/default.html.erb +19 -56
  107. data/lib/docyard/templates/layouts/splash.html.erb +176 -0
  108. data/lib/docyard/templates/partials/_breadcrumbs.html.erb +24 -0
  109. data/lib/docyard/templates/partials/_code_block.html.erb +6 -4
  110. data/lib/docyard/templates/partials/_doc_footer.html.erb +25 -0
  111. data/lib/docyard/templates/partials/_features.html.erb +15 -0
  112. data/lib/docyard/templates/partials/_footer.html.erb +42 -0
  113. data/lib/docyard/templates/partials/_head.html.erb +22 -0
  114. data/lib/docyard/templates/partials/_header.html.erb +49 -0
  115. data/lib/docyard/templates/partials/_heading_anchor.html.erb +3 -1
  116. data/lib/docyard/templates/partials/_hero.html.erb +27 -0
  117. data/lib/docyard/templates/partials/_nav_group.html.erb +25 -11
  118. data/lib/docyard/templates/partials/_nav_leaf.html.erb +1 -1
  119. data/lib/docyard/templates/partials/_nav_menu.html.erb +42 -0
  120. data/lib/docyard/templates/partials/_nav_nested_section.html.erb +11 -0
  121. data/lib/docyard/templates/partials/_nav_section.html.erb +1 -1
  122. data/lib/docyard/templates/partials/_prev_next.html.erb +9 -3
  123. data/lib/docyard/templates/partials/_scripts.html.erb +7 -0
  124. data/lib/docyard/templates/partials/_search_modal.html.erb +41 -0
  125. data/lib/docyard/templates/partials/_search_trigger.html.erb +18 -0
  126. data/lib/docyard/templates/partials/_sidebar.html.erb +21 -4
  127. data/lib/docyard/templates/partials/_tab_bar.html.erb +25 -0
  128. data/lib/docyard/templates/partials/_table_of_contents.html.erb +12 -12
  129. data/lib/docyard/templates/partials/_table_of_contents_toggle.html.erb +1 -3
  130. data/lib/docyard/templates/partials/_tabs.html.erb +2 -2
  131. data/lib/docyard/templates/partials/_theme_toggle.html.erb +2 -11
  132. data/lib/docyard/utils/html_helpers.rb +14 -0
  133. data/lib/docyard/utils/path_resolver.rb +2 -1
  134. data/lib/docyard/utils/url_helpers.rb +20 -0
  135. data/lib/docyard/version.rb +1 -1
  136. data/lib/docyard.rb +22 -15
  137. metadata +89 -50
  138. data/lib/docyard/components/callout_processor.rb +0 -121
  139. data/lib/docyard/components/code_block_diff_preprocessor.rb +0 -104
  140. data/lib/docyard/components/code_block_feature_extractor.rb +0 -113
  141. data/lib/docyard/components/code_block_focus_preprocessor.rb +0 -77
  142. data/lib/docyard/components/code_block_icon_detector.rb +0 -40
  143. data/lib/docyard/components/code_block_line_wrapper.rb +0 -46
  144. data/lib/docyard/components/code_block_options_preprocessor.rb +0 -76
  145. data/lib/docyard/components/code_block_patterns.rb +0 -51
  146. data/lib/docyard/components/code_block_processor.rb +0 -176
  147. data/lib/docyard/components/code_detector.rb +0 -59
  148. data/lib/docyard/components/code_line_parser.rb +0 -80
  149. data/lib/docyard/components/code_snippet_import_preprocessor.rb +0 -125
  150. data/lib/docyard/components/heading_anchor_processor.rb +0 -34
  151. data/lib/docyard/components/icon_detector.rb +0 -57
  152. data/lib/docyard/components/icon_processor.rb +0 -51
  153. data/lib/docyard/components/table_of_contents_processor.rb +0 -64
  154. data/lib/docyard/components/table_wrapper_processor.rb +0 -18
  155. data/lib/docyard/components/tabs_parser.rb +0 -191
  156. data/lib/docyard/components/tabs_processor.rb +0 -44
  157. data/lib/docyard/components/tabs_range_finder.rb +0 -42
  158. data/lib/docyard/rack_application.rb +0 -172
  159. data/lib/docyard/renderer.rb +0 -120
  160. data/lib/docyard/routing/resolution_result.rb +0 -31
  161. data/lib/docyard/sidebar/config_parser.rb +0 -180
  162. data/lib/docyard/sidebar/item.rb +0 -58
  163. data/lib/docyard/sidebar/renderer.rb +0 -137
  164. data/lib/docyard/sidebar/tree_builder.rb +0 -59
  165. data/lib/docyard/sidebar_builder.rb +0 -102
  166. data/lib/docyard/templates/markdown/getting-started/installation.md.erb +0 -77
  167. data/lib/docyard/templates/markdown/guides/configuration.md.erb +0 -202
  168. data/lib/docyard/templates/markdown/guides/markdown-features.md.erb +0 -247
  169. data/lib/docyard/templates/markdown/index.md.erb +0 -82
  170. /data/lib/docyard/{sidebar → navigation/sidebar}/title_extractor.rb +0 -0
  171. /data/lib/docyard/{icons → rendering/icons}/LICENSE.phosphor +0 -0
  172. /data/lib/docyard/{icons → rendering/icons}/file_types.rb +0 -0
  173. /data/lib/docyard/{icons.rb → rendering/icons.rb} +0 -0
  174. /data/lib/docyard/{language_mapping.rb → rendering/language_mapping.rb} +0 -0
  175. /data/lib/docyard/{file_watcher.rb → server/file_watcher.rb} +0 -0
  176. /data/lib/docyard/{errors.rb → utils/errors.rb} +0 -0
  177. /data/lib/docyard/{logging.rb → utils/logging.rb} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34bf24256bf638697dbc86f9d146667a4f17594c365070313eb19e74f8ab5134
4
- data.tar.gz: 357ea04d8822f47c98e2aee4e09ca9e8cc69b7e4d11cbdce1491874aaa459b5c
3
+ metadata.gz: 77d317cd25d544eef47b5bdcecb0515b8937da93b347d39cac6fd1f87bb43b19
4
+ data.tar.gz: bf97d9449013ba8bf370682271990f64b2c2092bfdc9bf0deb99cf2b67056cc2
5
5
  SHA512:
6
- metadata.gz: bc5c55d5ad69b73ef286eec3b4e202c7312b3dfd7d67b79d491612357e1c4e6e03d255a78af97706bdd5fe19353b7dd96f2da046a4c17f2e363342fc265b5628
7
- data.tar.gz: 855fdcdcf312f3778fd204ca1fd36825c183b1abf0a5c938edff599693930d519668b8fdb0770f65ae00f9db43d1b794e31ca922b29dbde73143caf6abd2f64c
6
+ metadata.gz: 2e30597716798800240f4436c423284d1972fe5fa0abf1de8007b3279dde0a87ef32210cb830c84769c15db0c82e6c372337ae06baaecdb71fa866342c9697a3
7
+ data.tar.gz: f0ae1edf5937153c9ed3ccb1cc7c599c7f650e16470ad976ef0918c09bc030502ecf89dad6a8a7c79ee8055530b61a65d556bbfd147e08a86349a2475777ee54
data/.rubocop.yml CHANGED
@@ -22,7 +22,11 @@ Metrics/ClassLength:
22
22
  Max: 150
23
23
 
24
24
  Metrics/MethodLength:
25
- Max: 15
25
+ Max: 16
26
+
27
+ Metrics/ParameterLists:
28
+ MaxOptionalParameters: 6
29
+ CountKeywordArgs: false
26
30
 
27
31
  Style/Documentation:
28
32
  Enabled: false
data/CHANGELOG.md CHANGED
@@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.0] - 2026-01-13
11
+
12
+ ### Added
13
+ - **Landing Pages** - Hero sections, feature grids, and custom footer layouts for documentation homepages (#45)
14
+ - **Tab Navigation** - Top-level navigation tabs for organizing documentation into sections like Guide, API, Components (#52)
15
+ - **Header CTAs** - Configurable call-to-action buttons in the header with primary/secondary variants (#51)
16
+ - **Breadcrumbs** - Path navigation with auto-truncation for deep nesting and configurable via `navigation.breadcrumbs` (#54)
17
+ - **Doc Page Footer** - Social icons, "Built with Docyard" attribution, and copyright text in TOC column (#55)
18
+ - **Auto-detect Branding** - Automatic logo and favicon detection from `docs/public/` directory (#49)
19
+ - **Social Icon Mapping** - 16 social platform icons with automatic platform-to-icon mapping (#55)
20
+
21
+ ### Changed
22
+ - **Sidebar Overhaul** - Per-section `_sidebar.yml` files, improved collapsible behavior, and better active state handling (#50, #53)
23
+ - **Config Schema** - Reorganized configuration with `branding`, `navigation`, and `socials` sections (#48)
24
+ - **Sidebar Convention** - Section-based sidebar configuration in `docs/<section>/_sidebar.yml` (#47)
25
+ - **UI Refresh** - Updated typography, spacing, and visual consistency across components (#44)
26
+ - **Logo Update** - New logo with cyan accent and dark mode support (#46)
27
+
28
+ ## [0.7.0] - 2026-01-01
29
+
30
+ ### Added
31
+ - **Full-text Search** - Pagefind-powered search with Cmd/Ctrl+K modal, keyboard navigation, and highlighting (#41)
32
+ - **Search Configuration** - Customize placeholder text, enable/disable search, and exclude paths via `docyard.yml` (#41)
33
+ - **Dev Server Search** - Opt-in search indexing during development with `--search` flag (#41)
34
+
35
+ ### Changed
36
+ - Major codebase reorganization for improved maintainability (#42)
37
+ - Components reorganized into `processors/` and `support/` subdirectories (#42)
38
+ - Consolidated `server/`, `rendering/`, `navigation/`, `config/`, and `search/` modules (#42)
39
+ - Extracted shared utilities into `utils/` module (#42)
40
+
10
41
  ## [0.6.0] - 2025-12-25
11
42
 
12
43
  ### Added
@@ -109,7 +140,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
109
140
  - Initial gem structure
110
141
  - Project scaffolding
111
142
 
112
- [Unreleased]: https://github.com/sanifhimani/docyard/compare/v0.6.0...HEAD
143
+ [Unreleased]: https://github.com/sanifhimani/docyard/compare/v0.8.0...HEAD
144
+ [0.8.0]: https://github.com/sanifhimani/docyard/compare/v0.7.0...v0.8.0
145
+ [0.7.0]: https://github.com/sanifhimani/docyard/compare/v0.6.0...v0.7.0
113
146
  [0.6.0]: https://github.com/sanifhimani/docyard/compare/v0.5.0...v0.6.0
114
147
  [0.5.0]: https://github.com/sanifhimani/docyard/compare/v0.4.0...v0.5.0
115
148
  [0.4.0]: https://github.com/sanifhimani/docyard/compare/v0.3.0...v0.4.0
@@ -35,6 +35,8 @@ module Docyard
35
35
  main_css = File.read(File.join(ASSETS_PATH, "css", "main.css"))
36
36
  css_content = resolve_css_imports(main_css)
37
37
  minified = CSSminify.compress(css_content)
38
+ minified = fix_calc_whitespace(minified)
39
+ minified = fix_css_math_functions(minified)
38
40
  hash = generate_hash(minified)
39
41
 
40
42
  write_bundled_asset(minified, hash, "css")
@@ -43,6 +45,19 @@ module Docyard
43
45
  hash
44
46
  end
45
47
 
48
+ def fix_calc_whitespace(css)
49
+ css
50
+ .gsub(/\)\+(?!\s)/, ") + ")
51
+ .gsub(/\)-(?![-\s])/, ") - ")
52
+ .gsub(/(\d[a-z]*)\+(?=[\w(])/, '\1 + ')
53
+ .gsub(/([lch])\+(?=[\d.])/, '\1 + ')
54
+ .gsub(/([lch])-(?=[\d.])/, '\1 - ')
55
+ end
56
+
57
+ def fix_css_math_functions(css)
58
+ css.gsub(/\bmax\(0,/, "max(0px,").gsub(/\bmin\(0,/, "min(0px,)")
59
+ end
60
+
46
61
  def resolve_css_imports(css_content)
47
62
  css_content.gsub(/@import url\('([^']+)'\);/) do |match|
48
63
  import_file = Regexp.last_match(1)
@@ -92,8 +107,8 @@ module Docyard
92
107
  end
93
108
 
94
109
  def update_html_references(css_hash, js_hash)
95
- html_files = Dir.glob(File.join(config.build.output_dir, "**", "*.html"))
96
- base_url = normalize_base_url(config.build.base_url)
110
+ html_files = Dir.glob(File.join(config.build.output, "**", "*.html"))
111
+ base_url = normalize_base_url(config.build.base)
97
112
 
98
113
  html_files.each do |file|
99
114
  content = replace_asset_references(File.read(file), css_hash, js_hash, base_url)
@@ -104,15 +119,15 @@ module Docyard
104
119
  end
105
120
 
106
121
  def replace_asset_references(content, css_hash, js_hash, base_url)
107
- content.gsub(%r{/assets/css/main\.css}, "#{base_url}assets/bundle.#{css_hash}.css")
108
- .gsub(%r{/assets/js/theme\.js}, "#{base_url}assets/bundle.#{js_hash}.js")
109
- .gsub(%r{/assets/js/components\.js}, "")
110
- .gsub(%r{<script src="/assets/js/reload\.js"></script>}, "")
122
+ content.gsub(%r{/_docyard/css/main\.css}, "#{base_url}_docyard/bundle.#{css_hash}.css")
123
+ .gsub(%r{/_docyard/js/theme\.js}, "#{base_url}_docyard/bundle.#{js_hash}.js")
124
+ .gsub(%r{/_docyard/js/components\.js}, "")
125
+ .gsub(%r{<script src="/_docyard/js/reload\.js"></script>}, "")
111
126
  end
112
127
 
113
128
  def write_bundled_asset(content, hash, extension)
114
129
  filename = "bundle.#{hash}.#{extension}"
115
- output_path = File.join(config.build.output_dir, "assets", filename)
130
+ output_path = File.join(config.build.output, "_docyard", filename)
116
131
  FileUtils.mkdir_p(File.dirname(output_path))
117
132
  File.write(output_path, content)
118
133
  end
@@ -3,6 +3,8 @@
3
3
  module Docyard
4
4
  module Build
5
5
  class FileCopier
6
+ DOCYARD_OUTPUT_DIR = "_docyard"
7
+
6
8
  attr_reader :config, :verbose
7
9
 
8
10
  def initialize(config, verbose: false)
@@ -14,7 +16,7 @@ module Docyard
14
16
  puts "\nCopying static assets..."
15
17
 
16
18
  count = 0
17
- count += copy_user_assets
19
+ count += copy_public_files
18
20
  count += copy_branding_assets
19
21
 
20
22
  log "[✓] Copied #{count} static files"
@@ -23,25 +25,22 @@ module Docyard
23
25
 
24
26
  private
25
27
 
26
- def copy_user_assets
27
- user_assets_dir = "docs/assets"
28
- return 0 unless Dir.exist?(user_assets_dir)
29
-
30
- output_assets_dir = File.join(config.build.output_dir, "assets")
31
- FileUtils.mkdir_p(output_assets_dir)
28
+ def copy_public_files
29
+ public_dir = Constants::PUBLIC_DIR
30
+ return 0 unless Dir.exist?(public_dir)
32
31
 
33
- files = find_user_asset_files(user_assets_dir)
34
- files.each { |file| copy_single_asset(file, "docs/assets/", output_assets_dir) }
32
+ files = find_files_in_dir(public_dir)
33
+ files.each { |file| copy_single_file(file, "#{public_dir}/", config.build.output) }
35
34
 
36
- log "[✓] Copied #{files.size} user assets from docs/assets/" if files.any?
35
+ log "[✓] Copied #{files.size} public files from #{public_dir}/" if files.any?
37
36
  files.size
38
37
  end
39
38
 
40
- def find_user_asset_files(assets_dir)
41
- Dir.glob(File.join(assets_dir, "**", "*")).select { |f| File.file?(f) }
39
+ def find_files_in_dir(dir)
40
+ Dir.glob(File.join(dir, "**", "*")).select { |f| File.file?(f) }
42
41
  end
43
42
 
44
- def copy_single_asset(file, prefix, output_dir)
43
+ def copy_single_file(file, prefix, output_dir)
45
44
  relative_path = file.delete_prefix(prefix)
46
45
  dest_path = File.join(output_dir, relative_path)
47
46
 
@@ -60,26 +59,49 @@ module Docyard
60
59
  end
61
60
 
62
61
  def copy_default_branding_assets
63
- templates_assets = File.join(__dir__, "..", "templates", "assets")
64
- count = 0
62
+ templates_assets = templates_assets_path
63
+ count = copy_branding_files(templates_assets)
64
+ count + copy_fonts(templates_assets)
65
+ end
65
66
 
66
- ["logo.svg", "logo-dark.svg", "favicon.svg"].each do |asset_file|
67
- source_path = File.join(templates_assets, asset_file)
68
- next unless File.exist?(source_path)
67
+ def templates_assets_path
68
+ File.join(__dir__, "..", "templates", "assets")
69
+ end
69
70
 
70
- dest_path = File.join(config.build.output_dir, "assets", asset_file)
71
- FileUtils.mkdir_p(File.dirname(dest_path))
72
- FileUtils.cp(source_path, dest_path)
71
+ def copy_branding_files(templates_assets)
72
+ branding_files = %w[logo.svg logo-dark.svg favicon.svg]
73
+ branding_files.sum { |asset_file| copy_asset_to_docyard(templates_assets, asset_file, "default branding") }
74
+ end
73
75
 
74
- log " Copied default branding: #{asset_file}" if verbose
75
- count += 1
76
- end
76
+ def copy_asset_to_docyard(source_dir, filename, label)
77
+ source_path = File.join(source_dir, filename)
78
+ return 0 unless File.exist?(source_path)
77
79
 
78
- count
80
+ dest_path = File.join(config.build.output, DOCYARD_OUTPUT_DIR, filename)
81
+ FileUtils.mkdir_p(File.dirname(dest_path))
82
+ FileUtils.cp(source_path, dest_path)
83
+ log " Copied #{label}: #{filename}" if verbose
84
+ 1
85
+ end
86
+
87
+ def copy_fonts(templates_assets)
88
+ fonts_dir = File.join(templates_assets, "fonts")
89
+ return 0 unless Dir.exist?(fonts_dir)
90
+
91
+ font_files = Dir.glob(File.join(fonts_dir, "*")).select { |f| File.file?(f) }
92
+ font_files.sum { |font_file| copy_single_font(font_file) }
93
+ end
94
+
95
+ def copy_single_font(font_file)
96
+ dest_path = File.join(config.build.output, DOCYARD_OUTPUT_DIR, "fonts", File.basename(font_file))
97
+ FileUtils.mkdir_p(File.dirname(dest_path))
98
+ FileUtils.cp(font_file, dest_path)
99
+ log " Copied font: #{File.basename(font_file)}" if verbose
100
+ 1
79
101
  end
80
102
 
81
103
  def copy_user_branding_assets
82
- %w[logo logo_dark favicon].sum { |asset_key| copy_single_branding_asset(asset_key) }
104
+ %w[logo favicon].sum { |asset_key| copy_single_branding_asset(asset_key) }
83
105
  end
84
106
 
85
107
  def copy_single_branding_asset(asset_key)
@@ -89,7 +111,7 @@ module Docyard
89
111
  full_path = File.join("docs", asset_path)
90
112
  return 0 unless File.exist?(full_path)
91
113
 
92
- dest_path = File.join(config.build.output_dir, "assets", asset_path)
114
+ dest_path = File.join(config.build.output, asset_path)
93
115
  FileUtils.mkdir_p(File.dirname(dest_path))
94
116
  FileUtils.cp(full_path, dest_path)
95
117
 
@@ -15,7 +15,7 @@ module Docyard
15
15
  urls = collect_urls
16
16
  sitemap_content = build_sitemap(urls)
17
17
 
18
- output_path = File.join(config.build.output_dir, "sitemap.xml")
18
+ output_path = File.join(config.build.output, "sitemap.xml")
19
19
  File.write(output_path, sitemap_content)
20
20
 
21
21
  puts "[✓] Generated sitemap.xml (#{urls.size} URLs)"
@@ -24,10 +24,10 @@ module Docyard
24
24
  private
25
25
 
26
26
  def collect_urls
27
- html_files = Dir.glob(File.join(config.build.output_dir, "**", "index.html"))
27
+ html_files = Dir.glob(File.join(config.build.output, "**", "index.html"))
28
28
 
29
29
  html_files.map do |file|
30
- relative_path = file.delete_prefix(config.build.output_dir).delete_suffix("/index.html")
30
+ relative_path = file.delete_prefix(config.build.output).delete_suffix("/index.html")
31
31
  url_path = relative_path.empty? ? "/" : relative_path
32
32
  lastmod = File.mtime(file).utc.iso8601
33
33
 
@@ -36,15 +36,15 @@ module Docyard
36
36
  end
37
37
 
38
38
  def build_sitemap(urls)
39
- base_url = config.build.base_url
40
- base_url = base_url.chop if base_url.end_with?("/")
39
+ base = config.build.base
40
+ base = base.chop if base.end_with?("/")
41
41
 
42
42
  xml = ['<?xml version="1.0" encoding="UTF-8"?>']
43
43
  xml << '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'
44
44
 
45
45
  urls.each do |url|
46
46
  xml << " <url>"
47
- xml << " <loc>#{base_url}#{url[:loc]}</loc>"
47
+ xml << " <loc>#{base}#{url[:loc]}</loc>"
48
48
  xml << " <lastmod>#{url[:lastmod]}</lastmod>"
49
49
  xml << " </url>"
50
50
  end
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "tty-progressbar"
4
+ require_relative "../rendering/template_resolver"
5
+ require_relative "../navigation/prev_next_builder"
6
+ require_relative "../navigation/breadcrumb_builder"
4
7
 
5
8
  module Docyard
6
9
  module Build
@@ -10,15 +13,17 @@ module Docyard
10
13
  def initialize(config, verbose: false)
11
14
  @config = config
12
15
  @verbose = verbose
13
- @renderer = Renderer.new(base_url: config.build.base_url, config: config)
16
+ @renderer = Renderer.new(base_url: config.build.base, config: config)
14
17
  end
15
18
 
16
19
  def generate
20
+ copy_custom_landing_page if custom_landing_page?
21
+
17
22
  markdown_files = collect_markdown_files
18
23
  puts "\n[✓] Found #{markdown_files.size} markdown files"
19
24
 
20
25
  progress = TTY::ProgressBar.new(
21
- "Generating pages [:bar] :current/:total (:percent%)",
26
+ "Generating pages [:bar] :current/:total (:percent)",
22
27
  total: markdown_files.size,
23
28
  width: 50
24
29
  )
@@ -33,24 +38,72 @@ module Docyard
33
38
 
34
39
  private
35
40
 
41
+ def custom_landing_page?
42
+ File.file?("docs/index.html")
43
+ end
44
+
45
+ def copy_custom_landing_page
46
+ output_path = File.join(config.build.output, "index.html")
47
+ FileUtils.mkdir_p(File.dirname(output_path))
48
+ FileUtils.cp("docs/index.html", output_path)
49
+ log "[✓] Copied custom landing page (index.html)"
50
+ end
51
+
36
52
  def collect_markdown_files
37
- Dir.glob(File.join("docs", "**", "*.md"))
53
+ files = Dir.glob(File.join("docs", "**", "*.md"))
54
+ files.reject! { |f| f == "docs/index.md" } if custom_landing_page?
55
+ files
38
56
  end
39
57
 
40
58
  def generate_page(markdown_file_path)
41
59
  output_path = determine_output_path(markdown_file_path)
42
60
  current_path = determine_current_path(markdown_file_path)
43
61
 
44
- sidebar_html = build_sidebar(current_path)
45
- html_content = renderer.render_file(
46
- markdown_file_path,
47
- sidebar_html: sidebar_html,
48
- branding: branding_options
49
- )
62
+ html_content = render_markdown_file(markdown_file_path, current_path)
63
+ html_content = apply_search_exclusion(html_content, current_path)
64
+ write_output(output_path, html_content)
65
+ end
66
+
67
+ def apply_search_exclusion(html_content, current_path)
68
+ return html_content unless excluded_from_search?(current_path)
69
+
70
+ html_content.gsub("data-pagefind-body", "data-pagefind-ignore")
71
+ end
72
+
73
+ def excluded_from_search?(path)
74
+ exclude_patterns = config.search.exclude || []
75
+ exclude_patterns.any? do |pattern|
76
+ next false unless pattern.start_with?("/")
77
+
78
+ File.fnmatch(pattern, path, File::FNM_PATHNAME)
79
+ end
80
+ end
50
81
 
82
+ def render_markdown_file(markdown_file_path, current_path)
83
+ markdown = Markdown.new(File.read(markdown_file_path))
84
+ template_resolver = TemplateResolver.new(markdown.frontmatter, config.data)
85
+ branding = branding_options
86
+
87
+ navigation = build_navigation_html(template_resolver, current_path, markdown, branding[:header_ctas])
88
+ renderer.render_file(markdown_file_path, **navigation, branding: branding,
89
+ template_options: template_resolver.to_options,
90
+ current_path: current_path)
91
+ end
92
+
93
+ def build_navigation_html(template_resolver, current_path, markdown, header_ctas)
94
+ return { sidebar_html: "", prev_next_html: "", breadcrumbs: nil } unless template_resolver.show_sidebar?
95
+
96
+ sidebar_builder = build_sidebar_instance(current_path, header_ctas)
97
+ {
98
+ sidebar_html: sidebar_builder.to_html,
99
+ prev_next_html: build_prev_next(sidebar_builder, current_path, markdown),
100
+ breadcrumbs: build_breadcrumbs(sidebar_builder.tree, current_path)
101
+ }
102
+ end
103
+
104
+ def write_output(output_path, html_content)
51
105
  FileUtils.mkdir_p(File.dirname(output_path))
52
106
  File.write(output_path, html_content)
53
-
54
107
  log "Generated: #{output_path}" if verbose
55
108
  end
56
109
 
@@ -59,7 +112,7 @@ module Docyard
59
112
  base_name = File.basename(relative_path, ".md")
60
113
  dir_name = File.dirname(relative_path)
61
114
 
62
- output_dir = config.build.output_dir
115
+ output_dir = config.build.output
63
116
 
64
117
  if base_name == "index"
65
118
  File.join(output_dir, dir_name, "index.html")
@@ -80,57 +133,36 @@ module Docyard
80
133
  end
81
134
  end
82
135
 
83
- def build_sidebar(current_path)
136
+ def build_sidebar_instance(current_path, header_ctas = [])
84
137
  SidebarBuilder.new(
85
138
  docs_path: "docs",
86
139
  current_path: current_path,
87
- config: config
88
- ).to_html
89
- end
90
-
91
- def branding_options
92
- default_branding.merge(config_branding_options)
140
+ config: config,
141
+ header_ctas: header_ctas
142
+ )
93
143
  end
94
144
 
95
- def default_branding
96
- {
97
- site_title: Constants::DEFAULT_SITE_TITLE,
98
- site_description: "",
99
- logo: Constants::DEFAULT_LOGO_PATH,
100
- logo_dark: Constants::DEFAULT_LOGO_DARK_PATH,
101
- favicon: nil,
102
- display_logo: true,
103
- display_title: true
104
- }
145
+ def build_prev_next(sidebar_builder, current_path, markdown)
146
+ PrevNextBuilder.new(
147
+ sidebar_tree: sidebar_builder.tree,
148
+ current_path: current_path,
149
+ frontmatter: markdown.frontmatter,
150
+ config: {}
151
+ ).to_html
105
152
  end
106
153
 
107
- def config_branding_options
108
- site = config.site
109
- branding = config.branding
154
+ def build_breadcrumbs(sidebar_tree, current_path)
155
+ return nil unless breadcrumbs_enabled?
110
156
 
111
- {
112
- site_title: site.title || Constants::DEFAULT_SITE_TITLE,
113
- site_description: site.description || "",
114
- logo: resolve_logo(branding.logo, branding.logo_dark),
115
- logo_dark: resolve_logo_dark(branding.logo, branding.logo_dark),
116
- favicon: branding.favicon
117
- }.merge(appearance_options(branding.appearance))
157
+ BreadcrumbBuilder.new(sidebar_tree: sidebar_tree, current_path: current_path)
118
158
  end
119
159
 
120
- def appearance_options(appearance)
121
- appearance ||= {}
122
- {
123
- display_logo: appearance["logo"] != false,
124
- display_title: appearance["title"] != false
125
- }
126
- end
127
-
128
- def resolve_logo(logo, logo_dark)
129
- logo || logo_dark || Constants::DEFAULT_LOGO_PATH
160
+ def breadcrumbs_enabled?
161
+ config.navigation.breadcrumbs != false
130
162
  end
131
163
 
132
- def resolve_logo_dark(logo, logo_dark)
133
- logo_dark || logo || Constants::DEFAULT_LOGO_DARK_PATH
164
+ def branding_options
165
+ BrandingResolver.new(config).resolve
134
166
  end
135
167
 
136
168
  def log(message)
@@ -22,8 +22,9 @@ module Docyard
22
22
  bundles_created = bundle_assets
23
23
  assets_copied = copy_static_files
24
24
  generate_seo_files
25
+ pages_indexed = generate_search_index
25
26
 
26
- display_summary(pages_built, bundles_created, assets_copied)
27
+ display_summary(pages_built, bundles_created, assets_copied, pages_indexed)
27
28
  true
28
29
  rescue StandardError => e
29
30
  error "Build failed: #{e.message}"
@@ -34,7 +35,7 @@ module Docyard
34
35
  private
35
36
 
36
37
  def prepare_output_directory
37
- output_dir = config.build.output_dir
38
+ output_dir = config.build.output
38
39
 
39
40
  if clean && Dir.exist?(output_dir)
40
41
  log "[✓] Cleaning #{output_dir}/ directory"
@@ -67,29 +68,38 @@ module Docyard
67
68
  sitemap_gen = Build::SitemapGenerator.new(config)
68
69
  sitemap_gen.generate
69
70
 
70
- File.write(File.join(config.build.output_dir, "robots.txt"), robots_txt_content)
71
- log "[] Generated robots.txt"
71
+ File.write(File.join(config.build.output, "robots.txt"), robots_txt_content)
72
+ log "[+] Generated robots.txt"
73
+ end
74
+
75
+ def generate_search_index
76
+ indexer = Search::BuildIndexer.new(config, verbose: verbose)
77
+ indexer.index
72
78
  end
73
79
 
74
80
  def robots_txt_content
75
- base_url = config.build.base_url
76
- base_url = "#{base_url}/" unless base_url.end_with?("/")
81
+ base = config.build.base
82
+ base = "#{base}/" unless base.end_with?("/")
77
83
 
78
84
  <<~ROBOTS
79
85
  User-agent: *
80
86
  Allow: /
81
87
 
82
- Sitemap: #{base_url}sitemap.xml
88
+ Sitemap: #{base}sitemap.xml
83
89
  ROBOTS
84
90
  end
85
91
 
86
- def display_summary(pages, bundles, assets)
92
+ def display_summary(pages, bundles, assets, indexed = 0)
87
93
  elapsed = Time.now - start_time
88
94
 
89
95
  puts "\n#{'=' * 50}"
90
96
  puts "Build complete in #{format('%.2f', elapsed)}s"
91
- puts "Output: #{config.build.output_dir}/"
92
- puts "#{pages} pages, #{bundles} bundles, #{assets} static files"
97
+ puts "Output: #{config.build.output}/"
98
+
99
+ summary = "#{pages} pages, #{bundles} bundles, #{assets} static files"
100
+ summary += ", #{indexed} pages indexed" if indexed.positive?
101
+ puts summary
102
+
93
103
  puts "=" * 50
94
104
  end
95
105
 
data/lib/docyard/cli.rb CHANGED
@@ -34,18 +34,21 @@ module Docyard
34
34
  desc "preview", "Preview the built site locally"
35
35
  method_option :port, type: :numeric, default: 4000, aliases: "-p", desc: "Port to run preview server on"
36
36
  def preview
37
- require_relative "preview_server"
37
+ require_relative "server/preview_server"
38
38
  Docyard::PreviewServer.new(port: options[:port]).start
39
39
  end
40
40
 
41
41
  desc "serve", "Start the development server"
42
42
  method_option :port, type: :numeric, default: 4200, aliases: "-p", desc: "Port to run the server on"
43
43
  method_option :host, type: :string, default: "localhost", aliases: "-h", desc: "Host to bind the server to"
44
+ method_option :search, type: :boolean, default: false, aliases: "-s",
45
+ desc: "Enable search indexing (slower startup)"
44
46
  def serve
45
- require_relative "server"
47
+ require_relative "server/dev_server"
46
48
  server = Docyard::Server.new(
47
49
  port: options[:port],
48
- host: options[:host]
50
+ host: options[:host],
51
+ search: options[:search]
49
52
  )
50
53
  server.start
51
54
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docyard
4
+ module Components
5
+ CalloutProcessor = Processors::CalloutProcessor
6
+ CodeBlockProcessor = Processors::CodeBlockProcessor
7
+ CodeBlockDiffPreprocessor = Processors::CodeBlockDiffPreprocessor
8
+ CodeBlockFocusPreprocessor = Processors::CodeBlockFocusPreprocessor
9
+ CodeBlockOptionsPreprocessor = Processors::CodeBlockOptionsPreprocessor
10
+ CodeSnippetImportPreprocessor = Processors::CodeSnippetImportPreprocessor
11
+ HeadingAnchorProcessor = Processors::HeadingAnchorProcessor
12
+ IconProcessor = Processors::IconProcessor
13
+ TableOfContentsProcessor = Processors::TableOfContentsProcessor
14
+ TableWrapperProcessor = Processors::TableWrapperProcessor
15
+ TabsProcessor = Processors::TabsProcessor
16
+
17
+ CodeDetector = Support::CodeDetector
18
+ IconDetector = Support::Tabs::IconDetector
19
+
20
+ CodeBlockFeatureExtractor = Support::CodeBlock::FeatureExtractor
21
+ CodeBlockIconDetector = Support::CodeBlock::IconDetector
22
+ CodeBlockLineWrapper = Support::CodeBlock::LineWrapper
23
+ CodeBlockPatterns = Support::CodeBlock::Patterns
24
+ CodeLineParser = Support::CodeBlock::LineParser
25
+
26
+ TabsParser = Support::Tabs::Parser
27
+ TabsRangeFinder = Support::Tabs::RangeFinder
28
+ end
29
+ end