docyard 0.2.0 → 0.4.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -1
  3. data/LICENSE.vscode-icons +42 -0
  4. data/README.md +86 -23
  5. data/lib/docyard/asset_handler.rb +33 -0
  6. data/lib/docyard/build/asset_bundler.rb +139 -0
  7. data/lib/docyard/build/file_copier.rb +105 -0
  8. data/lib/docyard/build/sitemap_generator.rb +57 -0
  9. data/lib/docyard/build/static_generator.rb +141 -0
  10. data/lib/docyard/builder.rb +104 -0
  11. data/lib/docyard/cli.rb +19 -0
  12. data/lib/docyard/components/base_processor.rb +24 -0
  13. data/lib/docyard/components/callout_processor.rb +121 -0
  14. data/lib/docyard/components/code_block_processor.rb +55 -0
  15. data/lib/docyard/components/code_detector.rb +59 -0
  16. data/lib/docyard/components/icon_detector.rb +57 -0
  17. data/lib/docyard/components/icon_processor.rb +51 -0
  18. data/lib/docyard/components/registry.rb +34 -0
  19. data/lib/docyard/components/table_wrapper_processor.rb +18 -0
  20. data/lib/docyard/components/tabs_parser.rb +60 -0
  21. data/lib/docyard/components/tabs_processor.rb +44 -0
  22. data/lib/docyard/config/validator.rb +171 -0
  23. data/lib/docyard/config.rb +135 -0
  24. data/lib/docyard/constants.rb +5 -0
  25. data/lib/docyard/icons/LICENSE.phosphor +21 -0
  26. data/lib/docyard/icons/file_types.rb +92 -0
  27. data/lib/docyard/icons/phosphor.rb +64 -0
  28. data/lib/docyard/icons.rb +40 -0
  29. data/lib/docyard/initializer.rb +93 -9
  30. data/lib/docyard/language_mapping.rb +52 -0
  31. data/lib/docyard/markdown.rb +27 -3
  32. data/lib/docyard/preview_server.rb +72 -0
  33. data/lib/docyard/rack_application.rb +77 -8
  34. data/lib/docyard/renderer.rb +56 -9
  35. data/lib/docyard/server.rb +5 -2
  36. data/lib/docyard/sidebar/config_parser.rb +180 -0
  37. data/lib/docyard/sidebar/item.rb +58 -0
  38. data/lib/docyard/sidebar/renderer.rb +33 -6
  39. data/lib/docyard/sidebar_builder.rb +54 -2
  40. data/lib/docyard/templates/assets/css/code.css +150 -2
  41. data/lib/docyard/templates/assets/css/components/callout.css +169 -0
  42. data/lib/docyard/templates/assets/css/components/code-block.css +196 -0
  43. data/lib/docyard/templates/assets/css/components/icon.css +16 -0
  44. data/lib/docyard/templates/assets/css/components/logo.css +44 -0
  45. data/lib/docyard/templates/assets/css/{components.css → components/navigation.css} +111 -53
  46. data/lib/docyard/templates/assets/css/components/tabs.css +299 -0
  47. data/lib/docyard/templates/assets/css/components/theme-toggle.css +69 -0
  48. data/lib/docyard/templates/assets/css/layout.css +14 -4
  49. data/lib/docyard/templates/assets/css/markdown.css +27 -17
  50. data/lib/docyard/templates/assets/css/reset.css +4 -0
  51. data/lib/docyard/templates/assets/css/variables.css +94 -3
  52. data/lib/docyard/templates/assets/favicon.svg +16 -0
  53. data/lib/docyard/templates/assets/js/components/code-block.js +162 -0
  54. data/lib/docyard/templates/assets/js/components/navigation.js +221 -0
  55. data/lib/docyard/templates/assets/js/components/tabs.js +338 -0
  56. data/lib/docyard/templates/assets/js/theme.js +12 -179
  57. data/lib/docyard/templates/assets/logo-dark.svg +4 -0
  58. data/lib/docyard/templates/assets/logo.svg +12 -0
  59. data/lib/docyard/templates/config/docyard.yml.erb +42 -0
  60. data/lib/docyard/templates/layouts/default.html.erb +32 -4
  61. data/lib/docyard/templates/markdown/getting-started/installation.md.erb +46 -12
  62. data/lib/docyard/templates/markdown/guides/configuration.md.erb +202 -0
  63. data/lib/docyard/templates/markdown/guides/markdown-features.md.erb +247 -0
  64. data/lib/docyard/templates/markdown/index.md.erb +55 -59
  65. data/lib/docyard/templates/partials/_callout.html.erb +11 -0
  66. data/lib/docyard/templates/partials/_code_block.html.erb +6 -0
  67. data/lib/docyard/templates/partials/_icon.html.erb +1 -0
  68. data/lib/docyard/templates/partials/_icon_file_extension.html.erb +1 -0
  69. data/lib/docyard/templates/partials/_nav_group.html.erb +10 -4
  70. data/lib/docyard/templates/partials/_nav_leaf.html.erb +9 -1
  71. data/lib/docyard/templates/partials/_tabs.html.erb +40 -0
  72. data/lib/docyard/templates/partials/_theme_toggle.html.erb +13 -0
  73. data/lib/docyard/version.rb +1 -1
  74. data/lib/docyard.rb +8 -0
  75. metadata +91 -7
  76. data/lib/docyard/templates/markdown/core-concepts/file-structure.md.erb +0 -61
  77. data/lib/docyard/templates/markdown/core-concepts/markdown.md.erb +0 -90
  78. data/lib/docyard/templates/markdown/getting-started/introduction.md.erb +0 -30
  79. data/lib/docyard/templates/markdown/getting-started/quick-start.md.erb +0 -56
  80. data/lib/docyard/templates/partials/_icons.html.erb +0 -11
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Layout/LineLength
4
+ module Docyard
5
+ module Icons
6
+ # Phosphor Icons - https://phosphoricons.com
7
+ # Copyright (c) 2020-2023 Phosphor Icons
8
+ # Licensed under MIT License (see LICENSE.phosphor)
9
+ PHOSPHOR = {
10
+ "regular" => {
11
+ "heart" => '<path d="M178,40c-20.65,0-38.73,8.88-50,23.89C116.73,48.88,98.65,40,78,40a62.07,62.07,0,0,0-62,62c0,70,103.79,126.66,108.21,129a8,8,0,0,0,7.58,0C136.21,228.66,240,172,240,102A62.07,62.07,0,0,0,178,40ZM128,214.8C109.74,204.16,32,155.69,32,102A46.06,46.06,0,0,1,78,56c19.45,0,35.78,10.36,42.6,27a8,8,0,0,0,14.8,0c6.82-16.67,23.15-27,42.6-27a46.06,46.06,0,0,1,46,46C224,155.61,146.24,204.15,128,214.8Z"/>',
12
+ "arrow-right" => '<path d="M221.66,133.66l-72,72a8,8,0,0,1-11.32-11.32L196.69,136H40a8,8,0,0,1,0-16H196.69L138.34,61.66a8,8,0,0,1,11.32-11.32l72,72A8,8,0,0,1,221.66,133.66Z"/>',
13
+ "arrow-left" => '<path d="M224,128a8,8,0,0,1-8,8H59.31l58.35,58.34a8,8,0,0,1-11.32,11.32l-72-72a8,8,0,0,1,0-11.32l72-72a8,8,0,0,1,11.32,11.32L59.31,120H216A8,8,0,0,1,224,128Z"/>',
14
+ "arrow-up" => '<path d="M205.66,117.66a8,8,0,0,1-11.32,0L136,59.31V216a8,8,0,0,1-16,0V59.31L61.66,117.66a8,8,0,0,1-11.32-11.32l72-72a8,8,0,0,1,11.32,0l72,72A8,8,0,0,1,205.66,117.66Z"/>',
15
+ "arrow-down" => '<path d="M205.66,149.66l-72,72a8,8,0,0,1-11.32,0l-72-72a8,8,0,0,1,11.32-11.32L120,196.69V40a8,8,0,0,1,16,0V196.69l58.34-58.35a8,8,0,0,1,11.32,11.32Z"/>',
16
+ "check" => '<path d="M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z"/>',
17
+ "x" => '<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"/>',
18
+ "warning" => '<path d="M236.8,188.09,149.35,36.22h0a24.76,24.76,0,0,0-42.7,0L19.2,188.09a23.51,23.51,0,0,0,0,23.72A24.35,24.35,0,0,0,40.55,224h174.9a24.35,24.35,0,0,0,21.33-12.19A23.51,23.51,0,0,0,236.8,188.09ZM222.93,203.8a8.5,8.5,0,0,1-7.48,4.2H40.55a8.5,8.5,0,0,1-7.48-4.2,7.59,7.59,0,0,1,0-7.72L120.52,44.21a8.75,8.75,0,0,1,15,0l87.45,151.87A7.59,7.59,0,0,1,222.93,203.8ZM120,144V104a8,8,0,0,1,16,0v40a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,180Z"/>',
19
+ "info" => '<path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"/>',
20
+ "code" => '<path d="M69.12,94.15,28.5,128l40.62,33.85a8,8,0,1,1-10.24,12.29l-48-40a8,8,0,0,1,0-12.29l48-40a8,8,0,0,1,10.24,12.3Zm176,27.7-48-40a8,8,0,1,0-10.24,12.3L227.5,128l-40.62,33.85a8,8,0,1,0,10.24,12.29l48-40a8,8,0,0,0,0-12.29ZM162.73,32.48a8,8,0,0,0-10.25,4.79l-64,176a8,8,0,0,0,4.79,10.26A8.14,8.14,0,0,0,96,224a8,8,0,0,0,7.52-5.27l64-176A8,8,0,0,0,162.73,32.48Z"/>',
21
+ "terminal" => '<path d="M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40Zm0,160H40V56H216V200ZM117.31,134.17,93.65,152l23.66,17.82a8,8,0,1,1-9.63,12.79l-32-24a8,8,0,0,1,0-12.8l32-24a8,8,0,0,1,9.63,12.79ZM160,168H136a8,8,0,0,1,0-16h24a8,8,0,0,1,0,16Z"/>',
22
+ "package" => '<path d="M223.68,66.15,135.68,18a15.88,15.88,0,0,0-15.36,0l-88,48.17a16,16,0,0,0-8.32,14v95.64a16,16,0,0,0,8.32,14l88,48.17a15.88,15.88,0,0,0,15.36,0l88-48.17a16,16,0,0,0,8.32-14V80.18A16,16,0,0,0,223.68,66.15ZM128,32l80.34,44-29.77,16.3-80.35-44ZM128,120,47.66,76l33.9-18.56,80.34,44ZM40,90l80,43.78v85.79L40,175.82Zm176,85.78h0l-80,43.79V133.82l32-17.51V152a8,8,0,0,0,16,0V107.55L216,90v85.77Z"/>',
23
+ "rocket-launch" => '<path d="M223.85,47.12a16,16,0,0,0-15-15c-12.58-.75-44.73.4-71.41,27.07L132.69,64H74.36A15.91,15.91,0,0,0,63,68.68L28.7,103a16,16,0,0,0,9.07,27.16l38.47,5.37,44.21,44.21,5.37,38.49a15.94,15.94,0,0,0,10.78,12.92,16.11,16.11,0,0,0,5.1.83A15.91,15.91,0,0,0,153,227.3L187.32,193A15.91,15.91,0,0,0,192,181.64V123.31l4.77-4.77C223.45,91.86,224.6,59.71,223.85,47.12ZM74.36,80h42.33L77.16,119.52,40,114.34Zm74.41-9.45a76.65,76.65,0,0,1,59.11-22.47,76.46,76.46,0,0,1-22.42,59.16L128,164.68,91.32,128ZM176,181.64,141.67,216l-5.19-37.17L176,139.31Zm-74.16,9.5C97.34,201,82.29,224,40,224a8,8,0,0,1-8-8c0-42.29,23-57.34,32.86-61.85a8,8,0,0,1,6.64,14.56c-6.43,2.93-20.62,12.36-23.12,38.91,26.55-2.5,36-16.69,38.91-23.12a8,8,0,1,1,14.56,6.64Z"/>',
24
+ "star" => '<path d="M239.18,97.26A16.38,16.38,0,0,0,224.92,86l-59-4.76L143.14,26.15a16.36,16.36,0,0,0-30.27,0L90.11,81.23,31.08,86a16.46,16.46,0,0,0-9.37,28.86l45,38.83L53,211.75a16.38,16.38,0,0,0,24.5,17.82L128,198.49l50.53,31.08A16.4,16.4,0,0,0,203,211.75l-13.76-58.07,45-38.83A16.43,16.43,0,0,0,239.18,97.26Zm-15.34,5.47-48.7,42a8,8,0,0,0-2.56,7.91l14.88,62.8a.37.37,0,0,1-.17.48c-.18.14-.23.11-.38,0l-54.72-33.65a8,8,0,0,0-8.38,0L69.09,215.94c-.15.09-.19.12-.38,0a.37.37,0,0,1-.17-.48l14.88-62.8a8,8,0,0,0-2.56-7.91l-48.7-42c-.12-.1-.23-.19-.13-.5s.18-.27.33-.29l63.92-5.16A8,8,0,0,0,103,91.86l24.62-59.61c.08-.17.11-.25.35-.25s.27.08.35.25L153,91.86a8,8,0,0,0,6.75,4.92l63.92,5.16c.15,0,.24,0,.33.29S224,102.63,223.84,102.73Z"/>',
25
+ "lightning" => '<path d="M215.79,118.17a8,8,0,0,0-5-5.66L153.18,90.9l14.66-73.33a8,8,0,0,0-13.69-7l-112,120a8,8,0,0,0,3,13l57.63,21.61L88.16,238.43a8,8,0,0,0,13.69,7l112-120A8,8,0,0,0,215.79,118.17ZM109.37,214l10.47-52.38a8,8,0,0,0-5-9.06L62,132.71l84.62-90.66L136.16,94.43a8,8,0,0,0,5,9.06l52.8,19.8Z"/>',
26
+ "moon-stars" => '<path d="M240,96a8,8,0,0,1-8,8H216v16a8,8,0,0,1-16,0V104H184a8,8,0,0,1,0-16h16V72a8,8,0,0,1,16,0V88h16A8,8,0,0,1,240,96ZM144,56h8v8a8,8,0,0,0,16,0V56h8a8,8,0,0,0,0-16h-8V32a8,8,0,0,0-16,0v8h-8a8,8,0,0,0,0,16Zm72.77,97a8,8,0,0,1,1.43,8A96,96,0,1,1,95.07,37.8a8,8,0,0,1,10.6,9.06A88.07,88.07,0,0,0,209.14,150.33,8,8,0,0,1,216.77,153Zm-19.39,14.88c-1.79.09-3.59.14-5.38.14A104.11,104.11,0,0,1,88,64c0-1.79,0-3.59.14-5.38A80,80,0,1,0,197.38,167.86Z"/>',
27
+ "sun" => '<path d="M120,40V16a8,8,0,0,1,16,0V40a8,8,0,0,1-16,0Zm72,88a64,64,0,1,1-64-64A64.07,64.07,0,0,1,192,128Zm-16,0a48,48,0,1,0-48,48A48.05,48.05,0,0,0,176,128ZM58.34,69.66A8,8,0,0,0,69.66,58.34l-16-16A8,8,0,0,0,42.34,53.66Zm0,116.68-16,16a8,8,0,0,0,11.32,11.32l16-16a8,8,0,0,0-11.32-11.32ZM192,72a8,8,0,0,0,5.66-2.34l16-16a8,8,0,0,0-11.32-11.32l-16,16A8,8,0,0,0,192,72Zm5.66,114.34a8,8,0,0,0-11.32,11.32l16,16a8,8,0,0,0,11.32-11.32ZM48,128a8,8,0,0,0-8-8H16a8,8,0,0,0,0,16H40A8,8,0,0,0,48,128Zm80,80a8,8,0,0,0-8,8v24a8,8,0,0,0,16,0V216A8,8,0,0,0,128,208Zm112-88H216a8,8,0,0,0,0,16h24a8,8,0,0,0,0-16Z"/>',
28
+ "link-external" => '<path d="M224,104a8,8,0,0,1-16,0V59.32l-66.33,66.34a8,8,0,0,1-11.32-11.32L196.68,48H152a8,8,0,0,1,0-16h64a8,8,0,0,1,8,8Zm-40,24a8,8,0,0,0-8,8v72H48V80h72a8,8,0,0,0,0-16H48A16,16,0,0,0,32,80V208a16,16,0,0,0,16,16H176a16,16,0,0,0,16-16V136A8,8,0,0,0,184,128Z"/>',
29
+ "copy" => '<path d="M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z"/>',
30
+ "caret-right" => '<path d="M181.66,133.66l-80,80a8,8,0,0,1-11.32-11.32L164.69,128,90.34,53.66a8,8,0,0,1,11.32-11.32l80,80A8,8,0,0,1,181.66,133.66Z"/>',
31
+ "github" => '<path d="M208.31,75.68A59.78,59.78,0,0,0,202.93,28,8,8,0,0,0,196,24a59.75,59.75,0,0,0-48,24H124A59.75,59.75,0,0,0,76,24a8,8,0,0,0-6.93,4,59.78,59.78,0,0,0-5.38,47.68A58.14,58.14,0,0,0,56,104v8a56.06,56.06,0,0,0,48.44,55.47A39.8,39.8,0,0,0,96,192v8H72a24,24,0,0,1-24-24A40,40,0,0,0,8,136a8,8,0,0,0,0,16,24,24,0,0,1,24,24,40,40,0,0,0,40,40H96v16a8,8,0,0,0,16,0V192a24,24,0,0,1,48,0v40a8,8,0,0,0,16,0V192a39.8,39.8,0,0,0-8.44-24.53A56.06,56.06,0,0,0,216,112v-8A58.14,58.14,0,0,0,208.31,75.68ZM200,112a40,40,0,0,1-40,40H112a40,40,0,0,1-40-40v-8a41.74,41.74,0,0,1,6.9-22.48,8,8,0,0,0,1.1-7.69,43.81,43.81,0,0,1,.79-33.58,43.88,43.88,0,0,1,32.32,20.06,8,8,0,0,0,6.71,3.69h32.35a8,8,0,0,0,6.74-3.69,43.87,43.87,0,0,1,32.32-20.06,43.81,43.81,0,0,1,.77,33.58,8.09,8.09,0,0,0,1,7.65,41.76,41.76,0,0,1,7,22.52Z"/>',
32
+ "question" => '<path d="M140,180a12,12,0,1,1-12-12A12,12,0,0,1,140,180ZM128,72c-22.06,0-40,16.15-40,36v4a8,8,0,0,0,16,0v-4c0-11,10.77-20,24-20s24,9,24,20-10.77,20-24,20a8,8,0,0,0-8,8v8a8,8,0,0,0,16,0v-.72c18.24-3.35,32-17.9,32-35.28C168,88.15,150.06,72,128,72Zm104,56A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z"/>',
33
+ "lightbulb" => '<path d="M176,232a8,8,0,0,1-8,8H88a8,8,0,0,1,0-16h80A8,8,0,0,1,176,232Zm40-128a87.55,87.55,0,0,1-33.64,69.21A16.24,16.24,0,0,0,176,186v6a16,16,0,0,1-16,16H96a16,16,0,0,1-16-16v-6a16,16,0,0,0-6.23-12.66A87.59,87.59,0,0,1,40,104.49C39.74,56.83,78.26,17.14,125.88,16A88,88,0,0,1,216,104Zm-16,0a72,72,0,0,0-73.74-72c-39,.92-70.47,33.39-70.26,72.39a71.65,71.65,0,0,0,27.64,56.3A32,32,0,0,1,96,186v6h64v-6a32.15,32.15,0,0,1,12.47-25.35A71.65,71.65,0,0,0,200,104Zm-16.11-9.34a57.6,57.6,0,0,0-46.56-46.55,8,8,0,0,0-2.66,15.78c16.57,2.79,30.63,16.85,33.44,33.45A8,8,0,0,0,176,104a9,9,0,0,0,1.35-.11A8,8,0,0,0,183.89,94.66Z"/>',
34
+ "warning-circle" => '<path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm-8-80V80a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,172Z"/>',
35
+ "warning-octagon" => '<path d="M120,136V80a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0ZM232,91.55v72.9a15.86,15.86,0,0,1-4.69,11.31l-51.55,51.55A15.86,15.86,0,0,1,164.45,232H91.55a15.86,15.86,0,0,1-11.31-4.69L28.69,175.76A15.86,15.86,0,0,1,24,164.45V91.55a15.86,15.86,0,0,1,4.69-11.31L80.24,28.69A15.86,15.86,0,0,1,91.55,24h72.9a15.86,15.86,0,0,1,11.31,4.69l51.55,51.55A15.86,15.86,0,0,1,232,91.55Zm-16,0L164.45,40H91.55L40,91.55v72.9L91.55,216h72.9L216,164.45ZM128,160a12,12,0,1,0,12,12A12,12,0,0,0,128,160Z"/>',
36
+ "siren" => '<path d="M120,16V8a8,8,0,0,1,16,0v8a8,8,0,0,1-16,0Zm80,32a8,8,0,0,0,5.66-2.34l8-8a8,8,0,0,0-11.32-11.32l-8,8A8,8,0,0,0,200,48ZM50.34,45.66A8,8,0,0,0,61.66,34.34l-8-8A8,8,0,0,0,42.34,37.66Zm87,26.45a8,8,0,1,0-2.64,15.78C153.67,91.08,168,108.32,168,128a8,8,0,0,0,16,0C184,100.6,163.93,76.57,137.32,72.11ZM232,176v24a16,16,0,0,1-16,16H40a16,16,0,0,1-16-16V176a16,16,0,0,1,16-16V128a88,88,0,0,1,88.67-88c48.15.36,87.33,40.29,87.33,89v31A16,16,0,0,1,232,176ZM56,160H200V129c0-40-32.05-72.71-71.45-73H128a72,72,0,0,0-72,72Zm160,40V176H40v24H216Z"/>',
37
+ "file" => '<path d="M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34ZM160,51.31,188.69,80H160ZM200,216H56V40h88V88a8,8,0,0,0,8,8h48V216Z"/>',
38
+ "terminal-window" => '<path d="M128,128a8,8,0,0,1-3,6.25l-40,32a8,8,0,1,1-10-12.5L107.19,128,75,102.25a8,8,0,1,1,10-12.5l40,32A8,8,0,0,1,128,128Zm48,24H136a8,8,0,0,0,0,16h40a8,8,0,0,0,0-16Zm56-96V200a16,16,0,0,1-16,16H40a16,16,0,0,1-16-16V56A16,16,0,0,1,40,40H216A16,16,0,0,1,232,56ZM216,200V56H40V200H216Z"/>'
39
+ },
40
+ "bold" => {
41
+ "heart" => '<path d="M178,36c-20.09,0-37.92,7.93-50,21.56C115.92,43.93,98.09,36,78,36a66.08,66.08,0,0,0-66,66c0,72.34,105.81,130.14,110.31,132.57a12,12,0,0,0,11.38,0C138.19,232.14,244,174.34,244,102A66.08,66.08,0,0,0,178,36Zm-5.49,142.36A328.69,328.69,0,0,1,128,210.16a328.69,328.69,0,0,1-44.51-31.8C61.82,159.77,36,131.42,36,102A42,42,0,0,1,78,60c17.8,0,32.7,9.4,38.89,24.54a12,12,0,0,0,22.22,0C145.3,69.4,160.2,60,178,60a42,42,0,0,1,42,42C220,131.42,194.18,159.77,172.51,178.36Z"/>'
42
+ },
43
+ "fill" => {
44
+ "heart" => '<path d="M240,102c0,70-103.79,126.66-108.21,129a8,8,0,0,1-7.58,0C119.79,228.66,16,172,16,102A62.07,62.07,0,0,1,78,40c20.65,0,38.73,8.88,50,23.89C139.27,48.88,157.35,40,178,40A62.07,62.07,0,0,1,240,102Z"/>'
45
+ },
46
+ "light" => {
47
+ "heart" => '<path d="M178,42c-21,0-39.26,9.47-50,25.34C117.26,51.47,99,42,78,42a60.07,60.07,0,0,0-60,60c0,29.2,18.2,59.59,54.1,90.31a334.68,334.68,0,0,0,53.06,37,6,6,0,0,0,5.68,0,334.68,334.68,0,0,0,53.06-37C219.8,161.59,238,131.2,238,102A60.07,60.07,0,0,0,178,42ZM128,217.11C111.59,207.64,30,157.72,30,102A48.05,48.05,0,0,1,78,54c20.28,0,37.31,10.83,44.45,28.27a6,6,0,0,0,11.1,0C140.69,64.83,157.72,54,178,54a48.05,48.05,0,0,1,48,48C226,157.72,144.41,207.64,128,217.11Z"/>'
48
+ },
49
+ "thin" => {
50
+ "heart" => '<path d="M178,44c-21.44,0-39.92,10.19-50,27.07C117.92,54.19,99.44,44,78,44a58.07,58.07,0,0,0-58,58c0,28.59,18,58.47,53.4,88.79a333.81,333.81,0,0,0,52.7,36.73,4,4,0,0,0,3.8,0,333.81,333.81,0,0,0,52.7-36.73C218,160.47,236,130.59,236,102A58.07,58.07,0,0,0,178,44ZM128,219.42c-14-8-100-59.35-100-117.42A50.06,50.06,0,0,1,78,52c21.11,0,38.85,11.31,46.3,29.51a4,4,0,0,0,7.4,0C139.15,63.31,156.89,52,178,52a50.06,50.06,0,0,1,50,50C228,160,142,211.46,128,219.42Z"/>'
51
+ },
52
+ "duotone" => {
53
+ "heart" => '<path d="M232,102c0,66-104,122-104,122S24,168,24,102A54,54,0,0,1,78,48c22.59,0,41.94,12.31,50,32,8.06-19.69,27.41-32,50-32A54,54,0,0,1,232,102Z" opacity="0.2"/><path d="M178,40c-20.65,0-38.73,8.88-50,23.89C116.73,48.88,98.65,40,78,40a62.07,62.07,0,0,0-62,62c0,70,103.79,126.66,108.21,129a8,8,0,0,0,7.58,0C136.21,228.66,240,172,240,102A62.07,62.07,0,0,0,178,40ZM128,214.8C109.74,204.16,32,155.69,32,102A46.06,46.06,0,0,1,78,56c19.45,0,35.78,10.36,42.6,27a8,8,0,0,0,14.8,0c6.82-16.67,23.15-27,42.6-27a46.06,46.06,0,0,1,46,46C224,155.61,146.24,204.15,128,214.8Z"/>',
54
+ "info" => '<path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"/>',
55
+ "warning" => '<path d="M236.8,188.09,149.35,36.22h0a24.76,24.76,0,0,0-42.7,0L19.2,188.09a23.51,23.51,0,0,0,0,23.72A24.35,24.35,0,0,0,40.55,224h174.9a24.35,24.35,0,0,0,21.33-12.19A23.51,23.51,0,0,0,236.8,188.09ZM222.93,203.8a8.5,8.5,0,0,1-7.48,4.2H40.55a8.5,8.5,0,0,1-7.48-4.2,7.59,7.59,0,0,1,0-7.72L120.52,44.21a8.75,8.75,0,0,1,15,0l87.45,151.87A7.59,7.59,0,0,1,222.93,203.8ZM120,144V104a8,8,0,0,1,16,0v40a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,180Z"/>',
56
+ "lightbulb" => '<path d="M176,232a8,8,0,0,1-8,8H88a8,8,0,0,1,0-16h80A8,8,0,0,1,176,232Zm40-128a87.55,87.55,0,0,1-33.64,69.21A16.24,16.24,0,0,0,176,186v6a16,16,0,0,1-16,16H96a16,16,0,0,1-16-16v-6a16,16,0,0,0-6.23-12.66A87.59,87.59,0,0,1,40,104.49C39.74,56.83,78.26,17.14,125.88,16A88,88,0,0,1,216,104Zm-16,0a72,72,0,0,0-73.74-72c-39,.92-70.47,33.39-70.26,72.39a71.65,71.65,0,0,0,27.64,56.3A32,32,0,0,1,96,186v6h64v-6a32.15,32.15,0,0,1,12.47-25.35A71.65,71.65,0,0,0,200,104Zm-16.11-9.34a57.6,57.6,0,0,0-46.56-46.55,8,8,0,0,0-2.66,15.78c16.57,2.79,30.63,16.85,33.44,33.45A8,8,0,0,0,176,104a9,9,0,0,0,1.35-.11A8,8,0,0,0,183.89,94.66Z"/>',
57
+ "warning-circle" => '<path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm-8-80V80a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,172Z"/>',
58
+ "warning-octagon" => '<path d="M120,136V80a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0ZM232,91.55v72.9a15.86,15.86,0,0,1-4.69,11.31l-51.55,51.55A15.86,15.86,0,0,1,164.45,232H91.55a15.86,15.86,0,0,1-11.31-4.69L28.69,175.76A15.86,15.86,0,0,1,24,164.45V91.55a15.86,15.86,0,0,1,4.69-11.31L80.24,28.69A15.86,15.86,0,0,1,91.55,24h72.9a15.86,15.86,0,0,1,11.31,4.69l51.55,51.55A15.86,15.86,0,0,1,232,91.55Zm-16,0L164.45,40H91.55L40,91.55v72.9L91.55,216h72.9L216,164.45ZM128,160a12,12,0,1,0,12,12A12,12,0,0,0,128,160Z"/>',
59
+ "siren" => '<path d="M120,16V8a8,8,0,0,1,16,0v8a8,8,0,0,1-16,0Zm80,32a8,8,0,0,0,5.66-2.34l8-8a8,8,0,0,0-11.32-11.32l-8,8A8,8,0,0,0,200,48ZM50.34,45.66A8,8,0,0,0,61.66,34.34l-8-8A8,8,0,0,0,42.34,37.66Zm87,26.45a8,8,0,1,0-2.64,15.78C153.67,91.08,168,108.32,168,128a8,8,0,0,0,16,0C184,100.6,163.93,76.57,137.32,72.11ZM232,176v24a16,16,0,0,1-16,16H40a16,16,0,0,1-16-16V176a16,16,0,0,1,16-16V128a88,88,0,0,1,88.67-88c48.15.36,87.33,40.29,87.33,89v31A16,16,0,0,1,232,176ZM56,160H200V129c0-40-32.05-72.71-71.45-73H128a72,72,0,0,0-72,72Zm160,40V176H40v24H216Z"/>'
60
+ }
61
+ }.freeze
62
+ end
63
+ end
64
+ # rubocop:enable Layout/LineLength
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "icons/phosphor"
4
+ require_relative "icons/file_types"
5
+ require_relative "renderer"
6
+
7
+ module Docyard
8
+ module Icons
9
+ LIBRARIES = {
10
+ phosphor: PHOSPHOR
11
+ }.freeze
12
+
13
+ def self.render(name, weight = "regular")
14
+ icon_data = LIBRARIES.dig(:phosphor, weight, name)
15
+ return nil unless icon_data
16
+
17
+ Renderer.new.render_partial(
18
+ "_icon", {
19
+ name: name,
20
+ icon_data: icon_data
21
+ }
22
+ )
23
+ end
24
+
25
+ def self.render_file_extension(extension)
26
+ svg_content = FileTypes.svg(extension)
27
+
28
+ if svg_content
29
+ Renderer.new.render_partial(
30
+ "_icon_file_extension", {
31
+ extension: extension,
32
+ svg_content: svg_content
33
+ }
34
+ )
35
+ else
36
+ render("file")
37
+ end
38
+ end
39
+ end
40
+ end
@@ -6,14 +6,13 @@ module Docyard
6
6
  class Initializer
7
7
  DOCS_DIR = "docs"
8
8
  TEMPLATE_DIR = File.join(__dir__, "templates", "markdown")
9
+ CONFIG_TEMPLATE_DIR = File.join(__dir__, "templates", "config")
9
10
 
10
11
  TEMPLATES = {
11
12
  "index.md" => "index.md.erb",
12
- "getting-started/introduction.md" => "getting-started/introduction.md.erb",
13
13
  "getting-started/installation.md" => "getting-started/installation.md.erb",
14
- "getting-started/quick-start.md" => "getting-started/quick-start.md.erb",
15
- "core-concepts/file-structure.md" => "core-concepts/file-structure.md.erb",
16
- "core-concepts/markdown.md" => "core-concepts/markdown.md.erb"
14
+ "guides/markdown-features.md" => "guides/markdown-features.md.erb",
15
+ "guides/configuration.md" => "guides/configuration.md.erb"
17
16
  }.freeze
18
17
 
19
18
  def initialize(path = ".")
@@ -44,6 +43,8 @@ module Docyard
44
43
  TEMPLATES.each do |output_name, template_name|
45
44
  copy_template(template_name, output_name)
46
45
  end
46
+
47
+ create_example_config
47
48
  end
48
49
 
49
50
  def copy_template(template_name, output_name)
@@ -57,20 +58,103 @@ module Docyard
57
58
  File.write(output_path, content)
58
59
  end
59
60
 
61
+ def create_example_config
62
+ config_path = File.join(@path, "docyard.yml")
63
+ return if File.exist?(config_path)
64
+
65
+ template_path = File.join(CONFIG_TEMPLATE_DIR, "docyard.yml.erb")
66
+ config_content = File.read(template_path)
67
+
68
+ File.write(config_path, config_content)
69
+ end
70
+
60
71
  def print_already_exists_error
61
72
  puts "Error: #{DOCS_DIR}/ folder already exists"
62
73
  puts " Remove it first or run docyard in a different directory"
63
74
  end
64
75
 
65
76
  def print_success
66
- puts "Docyard initialized successfully!"
77
+ print_banner
78
+ print_created_files
79
+ print_next_steps
80
+ end
81
+
82
+ def print_banner
83
+ puts ""
84
+ puts "┌─────────────────────────────────────────────────────────────┐"
85
+ puts "│ ✓ Docyard initialized successfully │"
86
+ puts "└─────────────────────────────────────────────────────────────┘"
87
+ puts ""
88
+ end
89
+
90
+ def print_created_files
91
+ puts "Created files:"
67
92
  puts ""
68
- puts "Created:"
69
- TEMPLATES.each_key { |file| puts " #{DOCS_DIR}/#{file}" }
93
+ print_file_tree
70
94
  puts ""
95
+ end
96
+
97
+ def print_next_steps
71
98
  puts "Next steps:"
72
- puts " 1. Edit your markdown files in #{DOCS_DIR}/"
73
- puts " 2. Run 'docyard serve' to preview your documentation locally"
99
+ puts ""
100
+ puts " Start development server:"
101
+ puts " docyard serve"
102
+ puts " → http://localhost:4200"
103
+ puts ""
104
+ puts " Build for production:"
105
+ puts " docyard build"
106
+ puts ""
107
+ puts " Preview production build:"
108
+ puts " docyard preview"
109
+ puts ""
110
+ end
111
+
112
+ def print_file_tree
113
+ puts " ├── docs/"
114
+
115
+ grouped_files = TEMPLATES.keys.group_by { |file| File.dirname(file) }
116
+ sorted_dirs = grouped_files.keys.sort
117
+
118
+ sorted_dirs.each_with_index do |dir, dir_idx|
119
+ print_directory_group(dir, grouped_files[dir], dir_idx == sorted_dirs.length - 1)
120
+ end
121
+
122
+ puts " └── docyard.yml"
123
+ end
124
+
125
+ def print_directory_group(dir, files, is_last_dir)
126
+ sorted_files = files.sort
127
+
128
+ if dir == "."
129
+ print_root_files(sorted_files, is_last_dir)
130
+ else
131
+ print_subdirectory(dir, sorted_files, is_last_dir)
132
+ end
133
+ end
134
+
135
+ def print_root_files(files, is_last_dir)
136
+ files.each_with_index do |file, idx|
137
+ is_last = idx == files.length - 1 && is_last_dir
138
+ prefix = is_last ? " │ └──" : " │ ├──"
139
+ puts "#{prefix} #{file}"
140
+ end
141
+ end
142
+
143
+ def print_subdirectory(dir, files, is_last_dir)
144
+ dir_prefix = is_last_dir ? " │ └──" : " │ ├──"
145
+ puts "#{dir_prefix} #{dir}/"
146
+
147
+ files.each_with_index do |file, idx|
148
+ print_subdirectory_file(file, idx, files.length, is_last_dir)
149
+ end
150
+ end
151
+
152
+ def print_subdirectory_file(file, idx, total, is_last_dir)
153
+ is_last_file = idx == total - 1
154
+ file_prefix = is_last_dir ? " │ " : " │ │ "
155
+ file_prefix += is_last_file ? "└──" : "├──"
156
+ basename = File.basename(file)
157
+ puts "#{file_prefix} #{basename}"
74
158
  end
75
159
  end
76
160
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docyard
4
+ module LanguageMapping
5
+ TERMINAL_LANGUAGES = %w[bash sh shell powershell].freeze
6
+
7
+ LANGUAGE_TO_EXTENSION = {
8
+ "js" => "js",
9
+ "javascript" => "js",
10
+ "ts" => "ts",
11
+ "typescript" => "ts",
12
+ "jsx" => "jsx",
13
+ "tsx" => "tsx",
14
+ "py" => "py",
15
+ "python" => "py",
16
+ "rb" => "rb",
17
+ "ruby" => "rb",
18
+ "go" => "go",
19
+ "golang" => "go",
20
+ "rs" => "rs",
21
+ "rust" => "rs",
22
+ "php" => "php",
23
+ "html" => "html",
24
+ "htm" => "html",
25
+ "html5" => "html",
26
+ "css" => "css",
27
+ "json" => "json",
28
+ "yaml" => "yaml",
29
+ "yml" => "yaml",
30
+ "toml" => "toml",
31
+ "sql" => "sql",
32
+ "mysql" => "mysql",
33
+ "postgresql" => "pgsql",
34
+ "postgres" => "pgsql",
35
+ "pgsql" => "pgsql",
36
+ "graphql" => "graphql",
37
+ "gql" => "graphql",
38
+ "vue" => "vue",
39
+ "svelte" => "svelte",
40
+ "proto" => "proto",
41
+ "protobuf" => "proto"
42
+ }.freeze
43
+
44
+ def self.extension_for(language)
45
+ LANGUAGE_TO_EXTENSION[language.to_s.downcase]
46
+ end
47
+
48
+ def self.terminal_language?(language)
49
+ TERMINAL_LANGUAGES.include?(language.to_s.downcase)
50
+ end
51
+ end
52
+ end
@@ -3,6 +3,13 @@
3
3
  require "kramdown"
4
4
  require "kramdown-parser-gfm"
5
5
  require "yaml"
6
+ require_relative "components/registry"
7
+ require_relative "components/base_processor"
8
+ require_relative "components/callout_processor"
9
+ require_relative "components/tabs_processor"
10
+ require_relative "components/icon_processor"
11
+ require_relative "components/code_block_processor"
12
+ require_relative "components/table_wrapper_processor"
6
13
 
7
14
  module Docyard
8
15
  class Markdown
@@ -34,6 +41,18 @@ module Docyard
34
41
  frontmatter["description"]
35
42
  end
36
43
 
44
+ def sidebar_icon
45
+ frontmatter.dig("sidebar", "icon")
46
+ end
47
+
48
+ def sidebar_text
49
+ frontmatter.dig("sidebar", "text")
50
+ end
51
+
52
+ def sidebar_collapsed
53
+ frontmatter.dig("sidebar", "collapsed")
54
+ end
55
+
37
56
  private
38
57
 
39
58
  def parse_frontmatter
@@ -50,12 +69,17 @@ module Docyard
50
69
  end
51
70
 
52
71
  def render_html
53
- Kramdown::Document.new(
54
- content,
72
+ preprocessed_content = Components::Registry.run_preprocessors(content)
73
+
74
+ raw_html = Kramdown::Document.new(
75
+ preprocessed_content,
55
76
  input: "GFM",
56
77
  hard_wrap: false,
57
- syntax_highlighter: "rouge"
78
+ syntax_highlighter: "rouge",
79
+ parse_block_html: true
58
80
  ).to_html
81
+
82
+ Components::Registry.run_postprocessors(raw_html)
59
83
  end
60
84
  end
61
85
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webrick"
4
+ require_relative "config"
5
+
6
+ module Docyard
7
+ class PreviewServer
8
+ DEFAULT_PORT = 4000
9
+
10
+ attr_reader :port, :output_dir
11
+
12
+ def initialize(port: DEFAULT_PORT)
13
+ @port = port
14
+ @config = Config.load
15
+ @output_dir = File.expand_path(@config.build.output_dir)
16
+ end
17
+
18
+ def start
19
+ validate_output_directory!
20
+ print_server_info
21
+
22
+ server = create_server
23
+ trap("INT") { shutdown_server(server) }
24
+
25
+ server.start
26
+ end
27
+
28
+ private
29
+
30
+ def validate_output_directory!
31
+ return if File.directory?(output_dir)
32
+
33
+ abort "Error: #{output_dir}/ directory not found.\n" \
34
+ "Run `docyard build` first to build the site."
35
+ end
36
+
37
+ def print_server_info
38
+ puts "Preview server starting..."
39
+ puts "=> Serving from: #{output_dir}/"
40
+ puts "=> Running at: http://localhost:#{port}"
41
+ puts "=> Press Ctrl+C to stop\n"
42
+ end
43
+
44
+ def create_server
45
+ WEBrick::HTTPServer.new(
46
+ Port: port,
47
+ DocumentRoot: output_dir,
48
+ AccessLog: [],
49
+ Logger: WEBrick::Log.new(File::NULL),
50
+ MimeTypes: mime_types
51
+ )
52
+ end
53
+
54
+ def mime_types
55
+ WEBrick::HTTPUtils::DefaultMimeTypes.merge(
56
+ {
57
+ "css" => "text/css",
58
+ "js" => "application/javascript",
59
+ "json" => "application/json",
60
+ "svg" => "image/svg+xml",
61
+ "woff" => "font/woff",
62
+ "woff2" => "font/woff2"
63
+ }
64
+ )
65
+ end
66
+
67
+ def shutdown_server(server)
68
+ puts "\nShutting down preview server..."
69
+ server.shutdown
70
+ end
71
+ end
72
+ end
@@ -3,14 +3,16 @@
3
3
  require "json"
4
4
  require "rack"
5
5
  require_relative "sidebar_builder"
6
+ require_relative "constants"
6
7
 
7
8
  module Docyard
8
9
  class RackApplication
9
- def initialize(docs_path:, file_watcher:)
10
+ def initialize(docs_path:, file_watcher:, config: nil)
10
11
  @docs_path = docs_path
11
12
  @file_watcher = file_watcher
13
+ @config = config
12
14
  @router = Router.new(docs_path: docs_path)
13
- @renderer = Renderer.new
15
+ @renderer = Renderer.new(base_url: config&.build&.base_url || "/")
14
16
  @asset_handler = AssetHandler.new
15
17
  end
16
18
 
@@ -20,7 +22,7 @@ module Docyard
20
22
 
21
23
  private
22
24
 
23
- attr_reader :docs_path, :file_watcher, :router, :renderer, :asset_handler
25
+ attr_reader :docs_path, :file_watcher, :config, :router, :renderer, :asset_handler
24
26
 
25
27
  def handle_request(env)
26
28
  path = env["PATH_INFO"]
@@ -37,15 +39,82 @@ module Docyard
37
39
  result = router.resolve(path)
38
40
 
39
41
  if result.found?
40
- sidebar = SidebarBuilder.new(docs_path: docs_path, current_path: path)
41
- html = renderer.render_file(result.file_path, sidebar_html: sidebar.to_html)
42
- [Constants::STATUS_OK, { "Content-Type" => Constants::CONTENT_TYPE_HTML }, [html]]
42
+ render_documentation_page(result.file_path, path)
43
43
  else
44
- html = renderer.render_not_found
45
- [Constants::STATUS_NOT_FOUND, { "Content-Type" => Constants::CONTENT_TYPE_HTML }, [html]]
44
+ render_not_found_page
46
45
  end
47
46
  end
48
47
 
48
+ def render_documentation_page(file_path, current_path)
49
+ html = renderer.render_file(
50
+ file_path,
51
+ sidebar_html: build_sidebar(current_path),
52
+ branding: branding_options
53
+ )
54
+
55
+ [Constants::STATUS_OK, { "Content-Type" => Constants::CONTENT_TYPE_HTML }, [html]]
56
+ end
57
+
58
+ def render_not_found_page
59
+ html = renderer.render_not_found
60
+ [Constants::STATUS_NOT_FOUND, { "Content-Type" => Constants::CONTENT_TYPE_HTML }, [html]]
61
+ end
62
+
63
+ def build_sidebar(current_path)
64
+ SidebarBuilder.new(
65
+ docs_path: docs_path,
66
+ current_path: current_path,
67
+ config: config
68
+ ).to_html
69
+ end
70
+
71
+ def branding_options
72
+ return default_branding unless config
73
+
74
+ default_branding.merge(config_branding_options)
75
+ end
76
+
77
+ def default_branding
78
+ {
79
+ site_title: Constants::DEFAULT_SITE_TITLE,
80
+ site_description: "",
81
+ logo: Constants::DEFAULT_LOGO_PATH,
82
+ logo_dark: Constants::DEFAULT_LOGO_DARK_PATH,
83
+ favicon: nil,
84
+ display_logo: true,
85
+ display_title: true
86
+ }
87
+ end
88
+
89
+ def config_branding_options
90
+ site = config.site
91
+ branding = config.branding
92
+
93
+ {
94
+ site_title: site.title || Constants::DEFAULT_SITE_TITLE,
95
+ site_description: site.description || "",
96
+ logo: resolve_logo(branding.logo, branding.logo_dark),
97
+ logo_dark: resolve_logo_dark(branding.logo, branding.logo_dark),
98
+ favicon: branding.favicon
99
+ }.merge(appearance_options(branding.appearance))
100
+ end
101
+
102
+ def appearance_options(appearance)
103
+ appearance ||= {}
104
+ {
105
+ display_logo: appearance["logo"] != false,
106
+ display_title: appearance["title"] != false
107
+ }
108
+ end
109
+
110
+ def resolve_logo(logo, logo_dark)
111
+ logo || logo_dark || Constants::DEFAULT_LOGO_PATH
112
+ end
113
+
114
+ def resolve_logo_dark(logo, logo_dark)
115
+ logo_dark || logo || Constants::DEFAULT_LOGO_DARK_PATH
116
+ end
117
+
49
118
  def handle_reload_check(env)
50
119
  since = parse_since_timestamp(env)
51
120
  reload_needed = file_watcher.changed_since?(since)
@@ -1,19 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "erb"
4
+ require_relative "constants"
4
5
 
5
6
  module Docyard
6
7
  class Renderer
7
8
  LAYOUTS_PATH = File.join(__dir__, "templates", "layouts")
8
9
  ERRORS_PATH = File.join(__dir__, "templates", "errors")
10
+ PARTIALS_PATH = File.join(__dir__, "templates", "partials")
9
11
 
10
- attr_reader :layout_path
12
+ attr_reader :layout_path, :base_url
11
13
 
12
- def initialize(layout: "default")
14
+ def initialize(layout: "default", base_url: "/")
13
15
  @layout_path = File.join(LAYOUTS_PATH, "#{layout}.html.erb")
16
+ @base_url = normalize_base_url(base_url)
14
17
  end
15
18
 
16
- def render_file(file_path, sidebar_html: "")
19
+ def render_file(file_path, sidebar_html: "", branding: {})
17
20
  markdown_content = File.read(file_path)
18
21
  markdown = Markdown.new(markdown_content)
19
22
 
@@ -21,17 +24,17 @@ module Docyard
21
24
 
22
25
  render(
23
26
  content: html_content,
24
- page_title: markdown.title || "Documentation",
25
- sidebar_html: sidebar_html
27
+ page_title: markdown.title || Constants::DEFAULT_SITE_TITLE,
28
+ sidebar_html: sidebar_html,
29
+ branding: branding
26
30
  )
27
31
  end
28
32
 
29
- def render(content:, page_title: "Documentation", sidebar_html: "")
33
+ def render(content:, page_title: Constants::DEFAULT_SITE_TITLE, sidebar_html: "", branding: {})
30
34
  template = File.read(layout_path)
31
35
 
32
- @content = content
33
- @page_title = page_title
34
- @sidebar_html = sidebar_html
36
+ assign_content_variables(content, page_title, sidebar_html)
37
+ assign_branding_variables(branding)
35
38
 
36
39
  ERB.new(template).result(binding)
37
40
  end
@@ -52,8 +55,52 @@ module Docyard
52
55
  ERB.new(template).result(binding)
53
56
  end
54
57
 
58
+ def render_partial(name, locals = {})
59
+ partial_path = File.join(PARTIALS_PATH, "#{name}.html.erb")
60
+ template = File.read(partial_path)
61
+
62
+ locals.each { |key, value| instance_variable_set("@#{key}", value) }
63
+
64
+ ERB.new(template).result(binding)
65
+ end
66
+
67
+ def asset_path(path)
68
+ return path if path.nil? || path.start_with?("http://", "https://")
69
+
70
+ "#{base_url}#{path}"
71
+ end
72
+
73
+ def link_path(path)
74
+ return path if path.nil? || path.start_with?("http://", "https://")
75
+
76
+ "#{base_url.chomp('/')}#{path}"
77
+ end
78
+
55
79
  private
56
80
 
81
+ def normalize_base_url(url)
82
+ return "/" if url.nil? || url.empty?
83
+
84
+ url = "/#{url}" unless url.start_with?("/")
85
+ url.end_with?("/") ? url : "#{url}/"
86
+ end
87
+
88
+ def assign_content_variables(content, page_title, sidebar_html)
89
+ @content = content
90
+ @page_title = page_title
91
+ @sidebar_html = sidebar_html
92
+ end
93
+
94
+ def assign_branding_variables(branding)
95
+ @site_title = branding[:site_title] || Constants::DEFAULT_SITE_TITLE
96
+ @site_description = branding[:site_description] || ""
97
+ @logo = branding[:logo] || Constants::DEFAULT_LOGO_PATH
98
+ @logo_dark = branding[:logo_dark]
99
+ @favicon = branding[:favicon] || Constants::DEFAULT_FAVICON_PATH
100
+ @display_logo = branding[:display_logo].nil? || branding[:display_logo]
101
+ @display_title = branding[:display_title].nil? || branding[:display_title]
102
+ end
103
+
57
104
  def strip_md_from_links(html)
58
105
  html.gsub(/href="([^"]+)\.md"/, 'href="\1"')
59
106
  end