docyard 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +5 -1
  3. data/CHANGELOG.md +43 -1
  4. data/lib/docyard/build/asset_bundler.rb +22 -7
  5. data/lib/docyard/build/file_copier.rb +49 -27
  6. data/lib/docyard/build/sitemap_generator.rb +6 -6
  7. data/lib/docyard/build/static_generator.rb +85 -12
  8. data/lib/docyard/builder.rb +6 -6
  9. data/lib/docyard/components/aliases.rb +12 -0
  10. data/lib/docyard/components/processors/abbreviation_processor.rb +72 -0
  11. data/lib/docyard/components/processors/accordion_processor.rb +81 -0
  12. data/lib/docyard/components/processors/badge_processor.rb +72 -0
  13. data/lib/docyard/components/processors/callout_processor.rb +8 -2
  14. data/lib/docyard/components/processors/cards_processor.rb +100 -0
  15. data/lib/docyard/components/processors/code_block_options_preprocessor.rb +23 -2
  16. data/lib/docyard/components/processors/code_block_processor.rb +6 -0
  17. data/lib/docyard/components/processors/code_group_processor.rb +198 -0
  18. data/lib/docyard/components/processors/code_snippet_import_preprocessor.rb +6 -1
  19. data/lib/docyard/components/processors/custom_anchor_processor.rb +42 -0
  20. data/lib/docyard/components/processors/file_tree_processor.rb +151 -0
  21. data/lib/docyard/components/processors/image_caption_processor.rb +96 -0
  22. data/lib/docyard/components/processors/include_processor.rb +86 -0
  23. data/lib/docyard/components/processors/steps_processor.rb +89 -0
  24. data/lib/docyard/components/processors/tabs_processor.rb +9 -1
  25. data/lib/docyard/components/processors/tooltip_processor.rb +57 -0
  26. data/lib/docyard/components/processors/video_embed_processor.rb +196 -0
  27. data/lib/docyard/components/support/code_group/html_builder.rb +122 -0
  28. data/lib/docyard/components/support/markdown_code_block_helper.rb +56 -0
  29. data/lib/docyard/config/branding_resolver.rb +121 -17
  30. data/lib/docyard/config/constants.rb +6 -4
  31. data/lib/docyard/config/logo_detector.rb +39 -0
  32. data/lib/docyard/config/validator.rb +122 -99
  33. data/lib/docyard/config.rb +40 -42
  34. data/lib/docyard/initializer.rb +15 -76
  35. data/lib/docyard/navigation/breadcrumb_builder.rb +133 -0
  36. data/lib/docyard/navigation/prev_next_builder.rb +4 -1
  37. data/lib/docyard/navigation/sidebar/children_discoverer.rb +51 -0
  38. data/lib/docyard/navigation/sidebar/config_parser.rb +136 -108
  39. data/lib/docyard/navigation/sidebar/file_resolver.rb +90 -0
  40. data/lib/docyard/navigation/sidebar/file_system_scanner.rb +2 -1
  41. data/lib/docyard/navigation/sidebar/item.rb +50 -7
  42. data/lib/docyard/navigation/sidebar/local_config_loader.rb +51 -0
  43. data/lib/docyard/navigation/sidebar/metadata_extractor.rb +71 -0
  44. data/lib/docyard/navigation/sidebar/metadata_reader.rb +51 -0
  45. data/lib/docyard/navigation/sidebar/path_prefixer.rb +34 -0
  46. data/lib/docyard/navigation/sidebar/renderer.rb +60 -38
  47. data/lib/docyard/navigation/sidebar/sorter.rb +21 -0
  48. data/lib/docyard/navigation/sidebar/tree_builder.rb +100 -26
  49. data/lib/docyard/navigation/sidebar/tree_filter.rb +55 -0
  50. data/lib/docyard/navigation/sidebar_builder.rb +105 -36
  51. data/lib/docyard/rendering/icon_helpers.rb +13 -0
  52. data/lib/docyard/rendering/icons/phosphor.rb +26 -1
  53. data/lib/docyard/rendering/markdown.rb +29 -1
  54. data/lib/docyard/rendering/renderer.rb +75 -34
  55. data/lib/docyard/rendering/template_resolver.rb +172 -0
  56. data/lib/docyard/routing/fallback_resolver.rb +92 -0
  57. data/lib/docyard/search/build_indexer.rb +1 -1
  58. data/lib/docyard/search/dev_indexer.rb +51 -6
  59. data/lib/docyard/search/pagefind_support.rb +2 -0
  60. data/lib/docyard/server/asset_handler.rb +25 -19
  61. data/lib/docyard/server/pagefind_handler.rb +63 -0
  62. data/lib/docyard/server/preview_server.rb +1 -1
  63. data/lib/docyard/server/rack_application.rb +81 -64
  64. data/lib/docyard/templates/assets/css/code.css +18 -51
  65. data/lib/docyard/templates/assets/css/components/abbreviation.css +86 -0
  66. data/lib/docyard/templates/assets/css/components/accordion.css +138 -0
  67. data/lib/docyard/templates/assets/css/components/badges.css +47 -0
  68. data/lib/docyard/templates/assets/css/components/banner.css +202 -0
  69. data/lib/docyard/templates/assets/css/components/breadcrumbs.css +143 -0
  70. data/lib/docyard/templates/assets/css/components/callout.css +67 -67
  71. data/lib/docyard/templates/assets/css/components/cards.css +100 -0
  72. data/lib/docyard/templates/assets/css/components/code-block.css +190 -282
  73. data/lib/docyard/templates/assets/css/components/code-group.css +281 -0
  74. data/lib/docyard/templates/assets/css/components/figure.css +22 -0
  75. data/lib/docyard/templates/assets/css/components/file-tree.css +124 -0
  76. data/lib/docyard/templates/assets/css/components/heading-anchor.css +36 -15
  77. data/lib/docyard/templates/assets/css/components/icon.css +0 -1
  78. data/lib/docyard/templates/assets/css/components/lightbox.css +65 -0
  79. data/lib/docyard/templates/assets/css/components/logo.css +0 -2
  80. data/lib/docyard/templates/assets/css/components/nav-menu.css +237 -0
  81. data/lib/docyard/templates/assets/css/components/navigation.css +193 -167
  82. data/lib/docyard/templates/assets/css/components/prev-next.css +68 -48
  83. data/lib/docyard/templates/assets/css/components/search.css +186 -174
  84. data/lib/docyard/templates/assets/css/components/steps.css +122 -0
  85. data/lib/docyard/templates/assets/css/components/tab-bar.css +163 -0
  86. data/lib/docyard/templates/assets/css/components/table-of-contents.css +127 -114
  87. data/lib/docyard/templates/assets/css/components/tabs.css +119 -160
  88. data/lib/docyard/templates/assets/css/components/theme-toggle.css +48 -44
  89. data/lib/docyard/templates/assets/css/components/tooltip.css +113 -0
  90. data/lib/docyard/templates/assets/css/components/video.css +41 -0
  91. data/lib/docyard/templates/assets/css/landing.css +815 -0
  92. data/lib/docyard/templates/assets/css/layout.css +489 -87
  93. data/lib/docyard/templates/assets/css/main.css +1 -3
  94. data/lib/docyard/templates/assets/css/markdown.css +113 -93
  95. data/lib/docyard/templates/assets/css/reset.css +0 -3
  96. data/lib/docyard/templates/assets/css/typography.css +43 -41
  97. data/lib/docyard/templates/assets/css/variables.css +268 -208
  98. data/lib/docyard/templates/assets/favicon.svg +7 -8
  99. data/lib/docyard/templates/assets/fonts/Inter-Variable.ttf +0 -0
  100. data/lib/docyard/templates/assets/js/components/abbreviation.js +85 -0
  101. data/lib/docyard/templates/assets/js/components/banner.js +81 -0
  102. data/lib/docyard/templates/assets/js/components/code-block.js +24 -42
  103. data/lib/docyard/templates/assets/js/components/code-group.js +283 -0
  104. data/lib/docyard/templates/assets/js/components/file-tree.js +39 -0
  105. data/lib/docyard/templates/assets/js/components/heading-anchor.js +26 -24
  106. data/lib/docyard/templates/assets/js/components/lightbox.js +72 -0
  107. data/lib/docyard/templates/assets/js/components/navigation.js +181 -70
  108. data/lib/docyard/templates/assets/js/components/search.js +0 -75
  109. data/lib/docyard/templates/assets/js/components/sidebar-toggle.js +29 -0
  110. data/lib/docyard/templates/assets/js/components/tab-navigation.js +145 -0
  111. data/lib/docyard/templates/assets/js/components/table-of-contents.js +153 -66
  112. data/lib/docyard/templates/assets/js/components/tabs.js +31 -69
  113. data/lib/docyard/templates/assets/js/components/tooltip.js +118 -0
  114. data/lib/docyard/templates/assets/js/theme.js +0 -3
  115. data/lib/docyard/templates/assets/logo-dark.svg +8 -2
  116. data/lib/docyard/templates/assets/logo.svg +7 -4
  117. data/lib/docyard/templates/config/docyard.yml.erb +37 -34
  118. data/lib/docyard/templates/errors/404.html.erb +1 -1
  119. data/lib/docyard/templates/errors/500.html.erb +1 -1
  120. data/lib/docyard/templates/layouts/default.html.erb +19 -67
  121. data/lib/docyard/templates/layouts/splash.html.erb +177 -0
  122. data/lib/docyard/templates/partials/_accordion.html.erb +9 -0
  123. data/lib/docyard/templates/partials/_banner.html.erb +27 -0
  124. data/lib/docyard/templates/partials/_breadcrumbs.html.erb +24 -0
  125. data/lib/docyard/templates/partials/_card.html.erb +23 -0
  126. data/lib/docyard/templates/partials/_code_block.html.erb +5 -3
  127. data/lib/docyard/templates/partials/_doc_footer.html.erb +25 -0
  128. data/lib/docyard/templates/partials/_features.html.erb +15 -0
  129. data/lib/docyard/templates/partials/_footer.html.erb +42 -0
  130. data/lib/docyard/templates/partials/_head.html.erb +22 -0
  131. data/lib/docyard/templates/partials/_header.html.erb +49 -0
  132. data/lib/docyard/templates/partials/_heading_anchor.html.erb +3 -1
  133. data/lib/docyard/templates/partials/_hero.html.erb +27 -0
  134. data/lib/docyard/templates/partials/_nav_group.html.erb +31 -11
  135. data/lib/docyard/templates/partials/_nav_leaf.html.erb +4 -1
  136. data/lib/docyard/templates/partials/_nav_menu.html.erb +42 -0
  137. data/lib/docyard/templates/partials/_nav_nested_section.html.erb +11 -0
  138. data/lib/docyard/templates/partials/_nav_section.html.erb +1 -1
  139. data/lib/docyard/templates/partials/_prev_next.html.erb +8 -2
  140. data/lib/docyard/templates/partials/_scripts.html.erb +7 -0
  141. data/lib/docyard/templates/partials/_search_modal.html.erb +2 -6
  142. data/lib/docyard/templates/partials/_search_trigger.html.erb +2 -6
  143. data/lib/docyard/templates/partials/_sidebar.html.erb +21 -4
  144. data/lib/docyard/templates/partials/_step.html.erb +14 -0
  145. data/lib/docyard/templates/partials/_tab_bar.html.erb +25 -0
  146. data/lib/docyard/templates/partials/_table_of_contents.html.erb +12 -12
  147. data/lib/docyard/templates/partials/_table_of_contents_toggle.html.erb +1 -3
  148. data/lib/docyard/templates/partials/_tabs.html.erb +2 -2
  149. data/lib/docyard/templates/partials/_theme_toggle.html.erb +2 -11
  150. data/lib/docyard/version.rb +1 -1
  151. metadata +70 -5
  152. data/lib/docyard/templates/markdown/getting-started/installation.md.erb +0 -77
  153. data/lib/docyard/templates/markdown/guides/configuration.md.erb +0 -202
  154. data/lib/docyard/templates/markdown/guides/markdown-features.md.erb +0 -247
  155. data/lib/docyard/templates/markdown/index.md.erb +0 -82
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e127990af44c6db555b00cfd2c7a89be408e6e4bfddf2bed9a753387f708ee87
4
- data.tar.gz: ed54f92691fe29fc8dd83b8b797504162283bba12840c49c120a4341ac22a66c
3
+ metadata.gz: 51b2adfdb7f77c4366592cc3eea5a160fc42f85f17e44593e9bf192c3b470b48
4
+ data.tar.gz: 439d82b762c7bc6f485ab627365522b7e202833fc841c6b8655fb28e1e9b3e55
5
5
  SHA512:
6
- metadata.gz: 7ae09bfea3ce92e5b5b8cee3e7317d6d57b253e17314adcfbf9f468cc0635a54d3cbd55a90f28807514875359e4d93b4e1c68fee99b7ec65a9ab12d34a21c6c2
7
- data.tar.gz: 3c2148376ab27c804a148666795420afe24fe23cb890fa76fa2602af5434d9e60d589fcbb460454210d2dfe1cc38f1f8584db41b1209db14b8b21ec3d7abeb68
6
+ metadata.gz: a66355efb4615ae4552aa8656e63078989cdec734f41670dc172936060954a065fefb1a2733e51494747ef0ab9a1dbee399b3c8f37daf2718e92ba03c315df9f
7
+ data.tar.gz: be3cea82f91b60cc7e7c35be142b6c2ccd9be4323cbf1c5190987b6ba6894d4c8da38f55c141abda43211a5ac2b70d38283e1b340d62071ee5c22c87c9c35a51
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,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.9.0] - 2026-01-15
11
+
12
+ ### Added
13
+ - **Accordions** - Collapsible content sections with `:::details{title="..."}` syntax (#62)
14
+ - **Steps** - Numbered step-by-step instructions with `:::steps` syntax and vertical connector lines (#63)
15
+ - **Cards** - Grid of linked content blocks with `:::cards` and `::card{title="" icon="" href=""}` syntax (#64)
16
+ - **Badges** - Inline status indicators with `:badge[text]{type="success|warning|danger"}` syntax (#65)
17
+ - **Sidebar Badges** - Navigation labels via frontmatter `sidebar.badge` and `sidebar.badge_type` (#66)
18
+ - **Announcement Banner** - Dismissible top banner with optional action button via config (#56)
19
+ - **Markdown Inclusion** - Include content from other files with `<!--@include: ./file.md-->` syntax (#57)
20
+ - **Custom Anchor IDs** - Override auto-generated heading IDs with `## Heading {#custom-id}` syntax (#58)
21
+ - **Image Captions** - Figure elements with captions using `![](image.png){caption="..."}` syntax (#59)
22
+ - **Video Embeds** - YouTube and Vimeo embedding with `::youtube[ID]` and `::vimeo[ID]` syntax (#60)
23
+ - **File Tree** - Display directory structures with icons using `filetree` code blocks (#67)
24
+ - **Tooltips** - Inline hover definitions with `:tooltip[term]{description="..."}` syntax (#68)
25
+ - **Abbreviations** - Auto-expanding terms with `*[TERM]: Definition` syntax (#68)
26
+ - **Code Groups** - Tabbed code blocks with `:::code-group` syntax, syncs selection across page (#70)
27
+
28
+ ### Fixed
29
+ - **Copy Button Overlap** - Repositioned copy button to prevent overlapping code content in non-titled blocks (#71)
30
+ - **Code Fence Protection** - Preprocessors now skip content inside fenced code blocks, allowing documentation to show raw syntax examples (#72)
31
+
32
+ ## [0.8.0] - 2026-01-13
33
+
34
+ ### Added
35
+ - **Landing Pages** - Hero sections, feature grids, and custom footer layouts for documentation homepages (#45)
36
+ - **Tab Navigation** - Top-level navigation tabs for organizing documentation into sections like Guide, API, Components (#52)
37
+ - **Header CTAs** - Configurable call-to-action buttons in the header with primary/secondary variants (#51)
38
+ - **Breadcrumbs** - Path navigation with auto-truncation for deep nesting and configurable via `navigation.breadcrumbs` (#54)
39
+ - **Doc Page Footer** - Social icons, "Built with Docyard" attribution, and copyright text in TOC column (#55)
40
+ - **Auto-detect Branding** - Automatic logo and favicon detection from `docs/public/` directory (#49)
41
+ - **Social Icon Mapping** - 16 social platform icons with automatic platform-to-icon mapping (#55)
42
+
43
+ ### Changed
44
+ - **Sidebar Overhaul** - Per-section `_sidebar.yml` files, improved collapsible behavior, and better active state handling (#50, #53)
45
+ - **Config Schema** - Reorganized configuration with `branding`, `navigation`, and `socials` sections (#48)
46
+ - **Sidebar Convention** - Section-based sidebar configuration in `docs/<section>/_sidebar.yml` (#47)
47
+ - **UI Refresh** - Updated typography, spacing, and visual consistency across components (#44)
48
+ - **Logo Update** - New logo with cyan accent and dark mode support (#46)
49
+
10
50
  ## [0.7.0] - 2026-01-01
11
51
 
12
52
  ### Added
@@ -122,7 +162,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
122
162
  - Initial gem structure
123
163
  - Project scaffolding
124
164
 
125
- [Unreleased]: https://github.com/sanifhimani/docyard/compare/v0.7.0...HEAD
165
+ [Unreleased]: https://github.com/sanifhimani/docyard/compare/v0.9.0...HEAD
166
+ [0.9.0]: https://github.com/sanifhimani/docyard/compare/v0.8.0...v0.9.0
167
+ [0.8.0]: https://github.com/sanifhimani/docyard/compare/v0.7.0...v0.8.0
126
168
  [0.7.0]: https://github.com/sanifhimani/docyard/compare/v0.6.0...v0.7.0
127
169
  [0.6.0]: https://github.com/sanifhimani/docyard/compare/v0.5.0...v0.6.0
128
170
  [0.5.0]: https://github.com/sanifhimani/docyard/compare/v0.4.0...v0.5.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,10 +13,12 @@ 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
 
@@ -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
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
50
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,14 +133,34 @@ 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
140
+ config: config,
141
+ header_ctas: header_ctas
142
+ )
143
+ end
144
+
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: {}
88
151
  ).to_html
89
152
  end
90
153
 
154
+ def build_breadcrumbs(sidebar_tree, current_path)
155
+ return nil unless breadcrumbs_enabled?
156
+
157
+ BreadcrumbBuilder.new(sidebar_tree: sidebar_tree, current_path: current_path)
158
+ end
159
+
160
+ def breadcrumbs_enabled?
161
+ config.navigation.breadcrumbs != false
162
+ end
163
+
91
164
  def branding_options
92
165
  BrandingResolver.new(config).resolve
93
166
  end
@@ -35,7 +35,7 @@ module Docyard
35
35
  private
36
36
 
37
37
  def prepare_output_directory
38
- output_dir = config.build.output_dir
38
+ output_dir = config.build.output
39
39
 
40
40
  if clean && Dir.exist?(output_dir)
41
41
  log "[✓] Cleaning #{output_dir}/ directory"
@@ -68,7 +68,7 @@ module Docyard
68
68
  sitemap_gen = Build::SitemapGenerator.new(config)
69
69
  sitemap_gen.generate
70
70
 
71
- File.write(File.join(config.build.output_dir, "robots.txt"), robots_txt_content)
71
+ File.write(File.join(config.build.output, "robots.txt"), robots_txt_content)
72
72
  log "[+] Generated robots.txt"
73
73
  end
74
74
 
@@ -78,14 +78,14 @@ module Docyard
78
78
  end
79
79
 
80
80
  def robots_txt_content
81
- base_url = config.build.base_url
82
- base_url = "#{base_url}/" unless base_url.end_with?("/")
81
+ base = config.build.base
82
+ base = "#{base}/" unless base.end_with?("/")
83
83
 
84
84
  <<~ROBOTS
85
85
  User-agent: *
86
86
  Allow: /
87
87
 
88
- Sitemap: #{base_url}sitemap.xml
88
+ Sitemap: #{base}sitemap.xml
89
89
  ROBOTS
90
90
  end
91
91
 
@@ -94,7 +94,7 @@ module Docyard
94
94
 
95
95
  puts "\n#{'=' * 50}"
96
96
  puts "Build complete in #{format('%.2f', elapsed)}s"
97
- puts "Output: #{config.build.output_dir}/"
97
+ puts "Output: #{config.build.output}/"
98
98
 
99
99
  summary = "#{pages} pages, #{bundles} bundles, #{assets} static files"
100
100
  summary += ", #{indexed} pages indexed" if indexed.positive?
@@ -2,17 +2,29 @@
2
2
 
3
3
  module Docyard
4
4
  module Components
5
+ AbbreviationProcessor = Processors::AbbreviationProcessor
6
+ AccordionProcessor = Processors::AccordionProcessor
7
+ BadgeProcessor = Processors::BadgeProcessor
8
+ StepsProcessor = Processors::StepsProcessor
9
+ CardsProcessor = Processors::CardsProcessor
5
10
  CalloutProcessor = Processors::CalloutProcessor
6
11
  CodeBlockProcessor = Processors::CodeBlockProcessor
12
+ CodeGroupProcessor = Processors::CodeGroupProcessor
7
13
  CodeBlockDiffPreprocessor = Processors::CodeBlockDiffPreprocessor
8
14
  CodeBlockFocusPreprocessor = Processors::CodeBlockFocusPreprocessor
9
15
  CodeBlockOptionsPreprocessor = Processors::CodeBlockOptionsPreprocessor
10
16
  CodeSnippetImportPreprocessor = Processors::CodeSnippetImportPreprocessor
17
+ CustomAnchorProcessor = Processors::CustomAnchorProcessor
18
+ ImageCaptionProcessor = Processors::ImageCaptionProcessor
19
+ IncludeProcessor = Processors::IncludeProcessor
20
+ VideoEmbedProcessor = Processors::VideoEmbedProcessor
21
+ FileTreeProcessor = Processors::FileTreeProcessor
11
22
  HeadingAnchorProcessor = Processors::HeadingAnchorProcessor
12
23
  IconProcessor = Processors::IconProcessor
13
24
  TableOfContentsProcessor = Processors::TableOfContentsProcessor
14
25
  TableWrapperProcessor = Processors::TableWrapperProcessor
15
26
  TabsProcessor = Processors::TabsProcessor
27
+ TooltipProcessor = Processors::TooltipProcessor
16
28
 
17
29
  CodeDetector = Support::CodeDetector
18
30
  IconDetector = Support::Tabs::IconDetector
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../base_processor"
4
+ require_relative "../support/markdown_code_block_helper"
5
+
6
+ module Docyard
7
+ module Components
8
+ module Processors
9
+ class AbbreviationProcessor < BaseProcessor
10
+ include Support::MarkdownCodeBlockHelper
11
+
12
+ DEFINITION_PATTERN = /^\*\[([^\]]+)\]:\s*(.+)$/
13
+ self.priority = 5
14
+
15
+ def preprocess(content)
16
+ abbreviations = extract_abbreviations_outside_code_blocks(content)
17
+ return content if abbreviations.empty?
18
+
19
+ process_outside_code_blocks(content) do |segment|
20
+ segment = remove_definitions(segment)
21
+ apply_abbreviations(segment, abbreviations)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def extract_abbreviations_outside_code_blocks(content)
28
+ abbreviations = {}
29
+ process_outside_code_blocks(content) do |segment|
30
+ segment.scan(DEFINITION_PATTERN) do |term, definition|
31
+ abbreviations[term] = definition.strip
32
+ end
33
+ segment
34
+ end
35
+ abbreviations
36
+ end
37
+
38
+ def remove_definitions(content)
39
+ content.gsub(/^[ \t]*\*\[([^\]]+)\]:\s*.+$\n?/, "")
40
+ end
41
+
42
+ def apply_abbreviations(content, abbreviations)
43
+ abbreviations.each do |term, definition|
44
+ pattern = build_term_pattern(term)
45
+ content = content.gsub(pattern) do |match|
46
+ build_abbr_tag(match, definition)
47
+ end
48
+ end
49
+ content
50
+ end
51
+
52
+ def build_term_pattern(term)
53
+ escaped = Regexp.escape(term)
54
+ /(?<![<\w])#{escaped}(?![>\w])/
55
+ end
56
+
57
+ def build_abbr_tag(term, definition)
58
+ escaped_definition = escape_html(definition)
59
+ %(<abbr class="docyard-abbr" data-definition="#{escaped_definition}">#{term}</abbr>)
60
+ end
61
+
62
+ def escape_html(text)
63
+ text.to_s
64
+ .gsub("&", "&amp;")
65
+ .gsub("<", "&lt;")
66
+ .gsub(">", "&gt;")
67
+ .gsub('"', "&quot;")
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end