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
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+ require_relative "../../rendering/icon_helpers"
5
+
6
+ module Docyard
7
+ module Sidebar
8
+ class Renderer
9
+ include Utils::UrlHelpers
10
+ include IconHelpers
11
+
12
+ PARTIALS_PATH = File.join(__dir__, "../../templates/partials")
13
+
14
+ attr_reader :site_title, :base_url, :header_ctas
15
+
16
+ def initialize(site_title: "Documentation", base_url: "/", header_ctas: [])
17
+ @site_title = site_title
18
+ @base_url = normalize_base_url(base_url)
19
+ @header_ctas = header_ctas
20
+ end
21
+
22
+ def render(tree)
23
+ return "" if tree.empty?
24
+
25
+ nav_content = render_tree_with_sections(tree)
26
+ render_partial(:sidebar, nav_content: nav_content, header_ctas: header_ctas)
27
+ end
28
+
29
+ private
30
+
31
+ def render_partial(name, locals = {})
32
+ template_path = File.join(PARTIALS_PATH, "_#{name}.html.erb")
33
+ template = File.read(template_path)
34
+
35
+ locals.each { |key, value| instance_variable_set("@#{key}", value) }
36
+
37
+ erb_binding = binding
38
+ ERB.new(template).result(erb_binding)
39
+ end
40
+
41
+ def render_tree_with_sections(items)
42
+ filtered_items = items.reject { |item| item[:title]&.downcase == site_title.downcase }
43
+ grouped = group_items_by_section(filtered_items)
44
+
45
+ grouped.map do |group|
46
+ if group[:section]
47
+ render_section(group[:item])
48
+ else
49
+ render_item_group(group[:items])
50
+ end
51
+ end.join
52
+ end
53
+
54
+ def group_items_by_section(items)
55
+ groups = []
56
+ current_non_section_items = []
57
+
58
+ items.each do |item|
59
+ if item[:section]
60
+ if current_non_section_items.any?
61
+ groups << { section: false, items: current_non_section_items }
62
+ current_non_section_items = []
63
+ end
64
+ groups << { section: true, item: item }
65
+ else
66
+ current_non_section_items << item
67
+ end
68
+ end
69
+
70
+ groups << { section: false, items: current_non_section_items } if current_non_section_items.any?
71
+ groups
72
+ end
73
+
74
+ def render_section(item)
75
+ section_content = render_tree(item[:children])
76
+ render_partial(:nav_section,
77
+ section_name: item[:title],
78
+ section_icon: item[:icon],
79
+ section_content: section_content)
80
+ end
81
+
82
+ def render_item_group(items)
83
+ render_partial(:nav_section,
84
+ section_name: nil,
85
+ section_icon: nil,
86
+ section_content: render_tree(items))
87
+ end
88
+
89
+ def render_tree(items)
90
+ return "" if items.empty?
91
+
92
+ list_items = items.map { |item| render_item(item) }.join
93
+ render_partial(:nav_list, list_items: list_items)
94
+ end
95
+
96
+ def render_item(item)
97
+ item_content = if item[:children].empty?
98
+ render_leaf_item(item)
99
+ elsif item[:section]
100
+ render_nested_section(item)
101
+ else
102
+ render_group_item(item)
103
+ end
104
+
105
+ render_partial(:nav_item, item_content: item_content)
106
+ end
107
+
108
+ def render_leaf_item(item)
109
+ render_partial(
110
+ :nav_leaf,
111
+ path: item[:path],
112
+ title: item[:title],
113
+ active: item[:active],
114
+ icon: item[:icon],
115
+ target: item[:target]
116
+ )
117
+ end
118
+
119
+ def render_nested_section(item)
120
+ children_html = render_tree(item[:children])
121
+ render_partial(
122
+ :nav_nested_section,
123
+ title: item[:title],
124
+ icon: item[:icon],
125
+ children_html: children_html
126
+ )
127
+ end
128
+
129
+ def render_group_item(item)
130
+ children_html = render_tree(item[:children])
131
+ render_partial(
132
+ :nav_group,
133
+ title: item[:title],
134
+ path: item[:path],
135
+ active: item[:active],
136
+ children_html: children_html,
137
+ icon: item[:icon],
138
+ collapsed: item[:collapsed],
139
+ has_index: item[:has_index]
140
+ )
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docyard
4
+ module Sidebar
5
+ module Sorter
6
+ module_function
7
+
8
+ def sort_by_order(items)
9
+ items.sort_by do |item|
10
+ order = item[:order]
11
+ title = item[:title]&.downcase || ""
12
+ if order.nil?
13
+ [1, title]
14
+ else
15
+ [0, order, title]
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "sorter"
4
+ require_relative "local_config_loader"
5
+ require_relative "config_parser"
6
+ require_relative "metadata_reader"
7
+
8
+ module Docyard
9
+ module Sidebar
10
+ class TreeBuilder
11
+ attr_reader :docs_path, :current_path, :title_extractor, :metadata_reader
12
+
13
+ def initialize(docs_path:, current_path:, title_extractor: TitleExtractor.new)
14
+ @docs_path = docs_path
15
+ @current_path = Utils::PathResolver.normalize(current_path)
16
+ @title_extractor = title_extractor
17
+ @metadata_reader = MetadataReader.new
18
+ end
19
+
20
+ def build(file_items)
21
+ transform_items(file_items, "", depth: 1)
22
+ end
23
+
24
+ private
25
+
26
+ def transform_items(items, relative_base, depth:)
27
+ transformed = items.map do |item|
28
+ if item[:type] == :directory
29
+ transform_directory(item, relative_base, depth: depth)
30
+ else
31
+ transform_file(item, relative_base)
32
+ end
33
+ end
34
+ Sorter.sort_by_order(transformed)
35
+ end
36
+
37
+ def transform_directory(item, relative_base, depth:)
38
+ dir_path = File.join(relative_base, item[:name])
39
+ dir_context = build_directory_context(dir_path)
40
+ children = build_directory_children(item, dir_path, depth)
41
+
42
+ if depth == 1
43
+ build_section(item, children, dir_context)
44
+ else
45
+ build_collapsible_group(item, children, dir_context)
46
+ end
47
+ end
48
+
49
+ def build_directory_children(item, dir_path, depth)
50
+ full_dir_path = File.join(docs_path, dir_path)
51
+ local_config = LocalConfigLoader.new(full_dir_path).load
52
+
53
+ if local_config
54
+ build_children_from_config(local_config, dir_path)
55
+ else
56
+ transform_items(item[:children], dir_path, depth: depth + 1)
57
+ end
58
+ end
59
+
60
+ def build_children_from_config(config_items, base_path)
61
+ full_base_path = File.join(docs_path, base_path)
62
+ parser = ConfigParser.new(config_items, docs_path: full_base_path, current_path: current_path)
63
+ parser.parse.map(&:to_h)
64
+ end
65
+
66
+ def build_directory_context(dir_path)
67
+ index_file_path = File.join(docs_path, dir_path, "index.md")
68
+ has_index = File.file?(index_file_path)
69
+ { index_file_path: index_file_path, has_index: has_index,
70
+ url_path: has_index ? Utils::PathResolver.to_url(dir_path) : nil }
71
+ end
72
+
73
+ def build_section(item, children, context)
74
+ filtered_children = filter_index_from_children(children, context[:url_path])
75
+ metadata = context[:has_index] ? metadata_reader.extract_index_metadata(context[:index_file_path]) : {}
76
+
77
+ if context[:has_index]
78
+ overview = build_overview_item(metadata, context[:url_path])
79
+ filtered_children = [overview] + filtered_children
80
+ end
81
+
82
+ build_section_hash(item, filtered_children, metadata)
83
+ end
84
+
85
+ def build_section_hash(item, children, metadata)
86
+ { title: Utils::TextFormatter.titleize(item[:name]), path: nil, icon: metadata[:icon],
87
+ active: false, type: :directory, section: true,
88
+ collapsed: false, has_index: false, order: metadata[:order], children: children }
89
+ end
90
+
91
+ def build_collapsible_group(item, children, context)
92
+ filtered_children = filter_index_from_children(children, context[:url_path])
93
+ metadata = context[:has_index] ? metadata_reader.extract_index_metadata(context[:index_file_path]) : {}
94
+ is_active = context[:has_index] && current_path == context[:url_path]
95
+
96
+ build_collapsible_hash(item, filtered_children, context, metadata, is_active)
97
+ end
98
+
99
+ def build_collapsible_hash(item, children, context, metadata, is_active)
100
+ { title: Utils::TextFormatter.titleize(item[:name]), path: context[:url_path],
101
+ icon: metadata[:icon], active: is_active, type: :directory, section: false,
102
+ collapsed: collapsible_collapsed?(children, is_active), has_index: context[:has_index],
103
+ order: metadata[:order], children: children }
104
+ end
105
+
106
+ def collapsible_collapsed?(children, is_active)
107
+ return false if is_active || active_child?(children)
108
+
109
+ true
110
+ end
111
+
112
+ def build_overview_item(metadata, url_path)
113
+ { title: metadata[:sidebar_text] || "Overview", path: url_path,
114
+ icon: metadata[:icon], active: current_path == url_path, type: :file, children: [] }
115
+ end
116
+
117
+ def filter_index_from_children(children, index_url_path)
118
+ return children unless index_url_path
119
+
120
+ children.reject { |child| child[:path] == index_url_path }
121
+ end
122
+
123
+ def active_child?(children)
124
+ children.any? { |child| child[:active] || active_child?(child[:children] || []) }
125
+ end
126
+
127
+ def transform_file(item, relative_base)
128
+ file_path = File.join(relative_base, "#{item[:name]}#{Constants::MARKDOWN_EXTENSION}")
129
+ full_file_path = File.join(docs_path, file_path)
130
+ url_path = Utils::PathResolver.to_url(file_path.delete_suffix(Constants::MARKDOWN_EXTENSION))
131
+ metadata = metadata_reader.extract_file_metadata(full_file_path)
132
+
133
+ { title: metadata[:title] || title_extractor.extract(full_file_path),
134
+ path: url_path, icon: metadata[:icon], active: current_path == url_path,
135
+ type: :file, order: metadata[:order], children: [] }
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docyard
4
+ module Sidebar
5
+ class TreeFilter
6
+ def initialize(tree, tab_path)
7
+ @tree = tree
8
+ @tab_path = tab_path
9
+ end
10
+
11
+ def filter
12
+ @tree.filter_map { |item| filter_item(item) }
13
+ end
14
+
15
+ private
16
+
17
+ def filter_item(item)
18
+ children = item[:children] || []
19
+
20
+ if children.any?
21
+ filter_parent_item(item, children)
22
+ else
23
+ filter_leaf_item(item)
24
+ end
25
+ end
26
+
27
+ def filter_parent_item(item, children)
28
+ filtered_children = self.class.new(children, @tab_path).filter
29
+ has_matching_content = filtered_children.any? { |c| !external_item?(c) }
30
+
31
+ return nil if !has_matching_content && !item_matches_path?(item[:path])
32
+
33
+ item.merge(children: filtered_children)
34
+ end
35
+
36
+ def filter_leaf_item(item)
37
+ return item if external_item?(item)
38
+ return nil unless item_matches_path?(item[:path])
39
+
40
+ item
41
+ end
42
+
43
+ def external_item?(item)
44
+ item[:type] == :external || item[:path]&.start_with?("http")
45
+ end
46
+
47
+ def item_matches_path?(item_path)
48
+ return false if item_path.nil?
49
+
50
+ normalized_path = item_path.chomp("/")
51
+ normalized_path == @tab_path || normalized_path.start_with?("#{@tab_path}/")
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "sidebar/file_system_scanner"
4
+ require_relative "sidebar/title_extractor"
5
+ require_relative "sidebar/tree_builder"
6
+ require_relative "sidebar/renderer"
7
+ require_relative "sidebar/config_parser"
8
+ require_relative "sidebar/local_config_loader"
9
+ require_relative "sidebar/path_prefixer"
10
+ require_relative "sidebar/tree_filter"
11
+
12
+ module Docyard
13
+ class SidebarBuilder
14
+ attr_reader :docs_path, :current_path, :config, :header_ctas
15
+
16
+ def initialize(docs_path:, current_path: "/", config: nil, header_ctas: [])
17
+ @docs_path = docs_path
18
+ @current_path = current_path
19
+ @config = config
20
+ @header_ctas = header_ctas
21
+ end
22
+
23
+ def tree
24
+ @tree ||= build_scoped_tree
25
+ end
26
+
27
+ def to_html
28
+ renderer.render(tree)
29
+ end
30
+
31
+ private
32
+
33
+ def build_scoped_tree
34
+ active_tab = find_active_tab
35
+ return build_tree_for_path(docs_path) unless active_tab
36
+
37
+ build_tree_for_tab(active_tab)
38
+ end
39
+
40
+ def build_tree_for_tab(tab)
41
+ tab_path = tab["href"]&.chomp("/")
42
+ return build_tree_for_path(docs_path) if empty_tab_path?(tab_path)
43
+
44
+ scoped_docs_path = resolve_scoped_path(tab_path)
45
+ build_scoped_or_filtered_tree(scoped_docs_path, tab_path)
46
+ end
47
+
48
+ def empty_tab_path?(tab_path)
49
+ tab_path.nil? || tab_path.empty? || tab_path == "/"
50
+ end
51
+
52
+ def resolve_scoped_path(tab_path)
53
+ tab_folder = tab_path.sub(%r{^/}, "")
54
+ File.join(docs_path, tab_folder)
55
+ end
56
+
57
+ def build_scoped_or_filtered_tree(scoped_docs_path, tab_path)
58
+ if scoped_sidebar_available?(scoped_docs_path)
59
+ build_tree_for_path(scoped_docs_path, base_url_prefix: tab_path)
60
+ else
61
+ Sidebar::TreeFilter.new(build_tree_for_path(docs_path), tab_path).filter
62
+ end
63
+ end
64
+
65
+ def scoped_sidebar_available?(path)
66
+ File.directory?(path) && Sidebar::LocalConfigLoader.new(path).config_file_exists?
67
+ end
68
+
69
+ def build_tree_for_path(path, base_url_prefix: "")
70
+ config_items = Sidebar::LocalConfigLoader.new(path).load
71
+ tree = build_tree(config_items, path, base_url_prefix)
72
+ maybe_prepend_overview(tree, path, base_url_prefix)
73
+ end
74
+
75
+ def build_tree(config_items, path, base_url_prefix)
76
+ if config_items&.any?
77
+ build_tree_from_config(config_items, path, base_url_prefix)
78
+ else
79
+ build_tree_from_filesystem(path, base_url_prefix)
80
+ end
81
+ end
82
+
83
+ def maybe_prepend_overview(tree, path, base_url_prefix)
84
+ return tree if skip_overview?(tree, path, base_url_prefix)
85
+
86
+ [build_overview_item(base_url_prefix)] + tree
87
+ end
88
+
89
+ def skip_overview?(tree, path, base_url_prefix)
90
+ base_url_prefix.empty? ||
91
+ tree.first&.dig(:section) ||
92
+ !File.file?(File.join(path, "index.md")) ||
93
+ tree.any? { |item| item[:path] == base_url_prefix }
94
+ end
95
+
96
+ def build_overview_item(base_url_prefix)
97
+ {
98
+ title: "Overview", path: base_url_prefix, icon: nil,
99
+ active: current_path == base_url_prefix, type: :file,
100
+ collapsed: false, collapsible: false, target: "_self",
101
+ has_index: false, section: false, children: []
102
+ }
103
+ end
104
+
105
+ def build_tree_from_config(items, path, base_url_prefix)
106
+ tree = Sidebar::ConfigParser.new(
107
+ items, docs_path: path, current_path: current_path_relative_to(base_url_prefix)
108
+ ).parse.map(&:to_h)
109
+
110
+ Sidebar::PathPrefixer.new(tree, base_url_prefix).prefix
111
+ end
112
+
113
+ def build_tree_from_filesystem(path, base_url_prefix)
114
+ file_items = Sidebar::FileSystemScanner.new(path).scan
115
+ tree = Sidebar::TreeBuilder.new(
116
+ docs_path: path, current_path: current_path_relative_to(base_url_prefix)
117
+ ).build(file_items)
118
+
119
+ Sidebar::PathPrefixer.new(tree, base_url_prefix).prefix
120
+ end
121
+
122
+ def current_path_relative_to(prefix)
123
+ return current_path if prefix.empty?
124
+ return current_path unless current_path.start_with?(prefix)
125
+
126
+ relative = current_path.sub(prefix, "")
127
+ relative.empty? ? "/" : relative
128
+ end
129
+
130
+ def renderer
131
+ @renderer ||= Sidebar::Renderer.new(
132
+ site_title: config&.title || "Documentation",
133
+ base_url: config&.build&.base || "/",
134
+ header_ctas: header_ctas
135
+ )
136
+ end
137
+
138
+ def tabs_configured?
139
+ tabs = config&.tabs
140
+ tabs.is_a?(Array) && tabs.any?
141
+ end
142
+
143
+ def find_active_tab
144
+ return nil unless tabs_configured?
145
+
146
+ normalized_current = current_path.chomp("/")
147
+ config.tabs.find { |tab| tab_matches_current?(tab, normalized_current) }
148
+ end
149
+
150
+ def tab_matches_current?(tab, normalized_current)
151
+ return false if tab["external"]
152
+
153
+ tab_href = tab["href"]&.chomp("/")
154
+ return false if tab_href.nil?
155
+
156
+ normalized_current == tab_href || normalized_current.start_with?("#{tab_href}/")
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docyard
4
+ module IconHelpers
5
+ def icon(name, weight = "regular")
6
+ Icons.render(name.to_s.tr("_", "-"), weight) || ""
7
+ end
8
+
9
+ def icon_file_extension(extension)
10
+ Icons.render_file_extension(extension) || ""
11
+ end
12
+ end
13
+ end
@@ -36,7 +36,32 @@ module Docyard
36
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
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
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
- "list-dashes" => '<path d="M88,64a8,8,0,0,1,8-8H216a8,8,0,0,1,0,16H96A8,8,0,0,1,88,64Zm128,56H96a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Zm0,64H96a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16ZM56,56H40a8,8,0,0,0,0,16H56a8,8,0,0,0,0-16Zm0,64H40a8,8,0,0,0,0,16H56a8,8,0,0,0,0-16Zm0,64H40a8,8,0,0,0,0,16H56a8,8,0,0,0,0-16Z"/>'
39
+ "list-dashes" => '<path d="M88,64a8,8,0,0,1,8-8H216a8,8,0,0,1,0,16H96A8,8,0,0,1,88,64Zm128,56H96a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Zm0,64H96a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16ZM56,56H40a8,8,0,0,0,0,16H56a8,8,0,0,0,0-16Zm0,64H40a8,8,0,0,0,0,16H56a8,8,0,0,0,0-16Zm0,64H40a8,8,0,0,0,0,16H56a8,8,0,0,0,0-16Z"/>',
40
+ "magnifying-glass" => '<path d="M229.66,218.34l-50.07-50.06a88.11,88.11,0,1,0-11.31,11.31l50.06,50.07a8,8,0,0,0,11.32-11.32ZM40,112a72,72,0,1,1,72,72A72.08,72.08,0,0,1,40,112Z"/>',
41
+ "command" => '<path d="M180,144H160V112h20a36,36,0,1,0-36-36V96H112V76a36,36,0,1,0-36,36H96v32H76a36,36,0,1,0,36,36V160h32v20a36,36,0,1,0,36-36ZM160,76a20,20,0,1,1,20,20H160ZM56,76a20,20,0,0,1,40,0V96H76A20,20,0,0,1,56,76ZM96,180a20,20,0,1,1-20-20H96Zm16-68h32v32H112Zm68,88a20,20,0,0,1-20-20V160h20a20,20,0,0,1,0,40Z"/>',
42
+ "hash" => '<path d="M224,88H175.4l8.47-46.57a8,8,0,0,0-15.74-2.86l-9,49.43H111.4l8.47-46.57a8,8,0,0,0-15.74-2.86L95.14,88H48a8,8,0,0,0,0,16H92.23L83.5,152H32a8,8,0,0,0,0,16H80.6l-8.47,46.57a8,8,0,0,0,6.44,9.3A7.79,7.79,0,0,0,80,224a8,8,0,0,0,7.86-6.57l9-49.43H144.6l-8.47,46.57a8,8,0,0,0,6.44,9.3,7.79,7.79,0,0,0,1.43.13,8,8,0,0,0,7.86-6.57l9-49.43H208a8,8,0,0,0,0-16H163.77l8.73-48H224a8,8,0,0,0,0-16Zm-68.5,64H107.77l8.73-48h47.73Z"/>',
43
+ "table" => '<path d="M224,48H32a8,8,0,0,0-8,8V192a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A8,8,0,0,0,224,48ZM40,112H80v32H40Zm56,0H216v32H96Zm120-48v32H40V64ZM40,160H80v32H40Zm176,32H96V160H216v32Z"/>',
44
+ "caret-down" => '<path d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,53.66,90.34L128,164.69l74.34-74.35a8,8,0,0,1,11.32,11.32Z"/>',
45
+ "sidebar" => '<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,40ZM40,56H80V200H40ZM216,200H96V56H216V200Z"/>',
46
+ "link-simple" => '<path d="M165.66,90.34a8,8,0,0,1,0,11.32l-64,64a8,8,0,0,1-11.32-11.32l64-64A8,8,0,0,1,165.66,90.34ZM215.6,40.4a56,56,0,0,0-79.2,0L106.34,70.45a8,8,0,0,0,11.32,11.32l30.06-30a40,40,0,0,1,56.57,56.56l-30.07,30.06a8,8,0,0,0,11.31,11.32L215.6,119.6a56,56,0,0,0,0-79.2ZM138.34,174.22l-30.06,30.06a40,40,0,1,1-56.56-56.56l30.05-30.06a8,8,0,0,0-11.32-11.32L40.4,136.4a56,56,0,0,0,79.2,79.2l30.06-30.07a8,8,0,0,0-11.32-11.31Z"/>',
47
+ "copyright" => '<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,216ZM96,128a32,32,0,0,0,57.6,19.2,8,8,0,0,1,12.8,9.61,48,48,0,1,1,0-57.62,8,8,0,0,1-12.8,9.61A32,32,0,0,0,96,128Z"/>',
48
+ "equals" => '<path d="M224,160a8,8,0,0,1-8,8H40a8,8,0,0,1,0-16H216A8,8,0,0,1,224,160ZM40,104H216a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16Z"/>',
49
+ "x-logo" => '<path d="M214.75,211.71l-62.6-98.38,61.77-67.95a8,8,0,0,0-11.84-10.76L143.24,99.34,102.75,35.71A8,8,0,0,0,96,32H48a8,8,0,0,0-6.75,12.3l62.6,98.37-61.77,68a8,8,0,1,0,11.84,10.76l58.84-64.72,40.49,63.63A8,8,0,0,0,160,224h48a8,8,0,0,0,6.75-12.29ZM164.39,208,62.57,48h29L193.43,208Z"/>',
50
+ "discord-logo" => '<path d="M104,140a12,12,0,1,1-12-12A12,12,0,0,1,104,140Zm60-12a12,12,0,1,0,12,12A12,12,0,0,0,164,128Zm74.45,64.9-67,29.71a16.17,16.17,0,0,1-21.71-9.1l-8.11-22q-6.72.45-13.63.46t-13.63-.46l-8.11,22a16.18,16.18,0,0,1-21.71,9.1l-67-29.71a15.93,15.93,0,0,1-9.06-18.51L38,58A16.07,16.07,0,0,1,51,46.14l36.06-5.93a16.22,16.22,0,0,1,18.26,11.88l3.26,12.84Q118.11,64,128,64t19.4.93l3.26-12.84a16.21,16.21,0,0,1,18.26-11.88L205,46.14A16.07,16.07,0,0,1,218,58l29.53,116.38A15.93,15.93,0,0,1,238.45,192.9ZM232,178.28,202.47,62s0,0-.08,0L166.33,56a.17.17,0,0,0-.17,0l-2.83,11.14c5,.94,10,2.06,14.83,3.42A8,8,0,0,1,176,86.31a8.09,8.09,0,0,1-2.16-.3A172.25,172.25,0,0,0,128,80a172.25,172.25,0,0,0-45.84,6,8,8,0,1,1-4.32-15.4c4.82-1.36,9.78-2.48,14.82-3.42L89.83,56s0,0-.12,0h0L53.61,61.93a.17.17,0,0,0-.09,0L24,178.33,91,208a.23.23,0,0,0,.22,0L98,189.72a173.2,173.2,0,0,1-20.14-4.32A8,8,0,0,1,82.16,170,171.85,171.85,0,0,0,128,176a171.85,171.85,0,0,0,45.84-6,8,8,0,0,1,4.32,15.41A173.2,173.2,0,0,1,158,189.72L164.75,208a.22.22,0,0,0,.21,0Z"/>',
51
+ "linkedin-logo" => '<path d="M216,24H40A16,16,0,0,0,24,40V216a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V40A16,16,0,0,0,216,24Zm0,192H40V40H216V216ZM96,112v64a8,8,0,0,1-16,0V112a8,8,0,0,1,16,0Zm88,28v36a8,8,0,0,1-16,0V140a20,20,0,0,0-40,0v36a8,8,0,0,1-16,0V112a8,8,0,0,1,15.79-1.78A36,36,0,0,1,184,140ZM100,84A12,12,0,1,1,88,72,12,12,0,0,1,100,84Z"/>',
52
+ "youtube-logo" => '<path d="M164.44,121.34l-48-32A8,8,0,0,0,104,96v64a8,8,0,0,0,12.44,6.66l48-32a8,8,0,0,0,0-13.32ZM120,145.05V111l25.58,17ZM234.33,69.52a24,24,0,0,0-14.49-16.4C185.56,39.88,131,40,128,40s-57.56-.12-91.84,13.12a24,24,0,0,0-14.49,16.4C19.08,79.5,16,97.74,16,128s3.08,48.5,5.67,58.48a24,24,0,0,0,14.49,16.41C69,215.56,120.4,216,127.34,216h1.32c6.94,0,58.37-.44,91.18-13.11a24,24,0,0,0,14.49-16.41c2.59-10,5.67-28.22,5.67-58.48S236.92,79.5,234.33,69.52Zm-15.49,113a8,8,0,0,1-4.77,5.49c-31.65,12.22-85.48,12-86,12H128c-.54,0-54.33.2-86-12a8,8,0,0,1-4.77-5.49C34.8,173.39,32,156.57,32,128s2.8-45.39,5.16-54.47A8,8,0,0,1,41.93,68c30.52-11.79,81.66-12,85.85-12h.27c.54,0,54.38-.18,86,12a8,8,0,0,1,4.77,5.49C221.2,82.61,224,99.43,224,128S221.2,173.39,218.84,182.47Z"/>',
53
+ "twitter-logo" => '<path d="M214.75,211.71l-62.6-98.38,61.77-67.95a8,8,0,0,0-11.84-10.76L143.24,99.34,102.75,35.71A8,8,0,0,0,96,32H48a8,8,0,0,0-6.75,12.3l62.6,98.37-61.77,68a8,8,0,1,0,11.84,10.76l58.84-64.72,40.49,63.63A8,8,0,0,0,160,224h48a8,8,0,0,0,6.75-12.29ZM164.39,208,62.57,48h29L193.43,208Z"/>',
54
+ "instagram-logo" => '<path d="M128,80a48,48,0,1,0,48,48A48.05,48.05,0,0,0,128,80Zm0,80a32,32,0,1,1,32-32A32,32,0,0,1,128,160ZM176,24H80A56.06,56.06,0,0,0,24,80v96a56.06,56.06,0,0,0,56,56h96a56.06,56.06,0,0,0,56-56V80A56.06,56.06,0,0,0,176,24Zm40,152a40,40,0,0,1-40,40H80a40,40,0,0,1-40-40V80A40,40,0,0,1,80,40h96a40,40,0,0,1,40,40ZM192,76a12,12,0,1,1-12-12A12,12,0,0,1,192,76Z"/>',
55
+ "facebook-logo" => '<path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm8,191.63V152h24a8,8,0,0,0,0-16H136V112a16,16,0,0,1,16-16h16a8,8,0,0,0,0-16H152a32,32,0,0,0-32,32v24H96a8,8,0,0,0,0,16h24v63.63a88,88,0,1,1,16,0Z"/>',
56
+ "tiktok-logo" => '<path d="M224,72a48.05,48.05,0,0,1-48-48,8,8,0,0,0-8-8H128a8,8,0,0,0-8,8V156a20,20,0,1,1-28.57-18.08A8,8,0,0,0,96,130.69V88a8,8,0,0,0-9.4-7.88C50.91,86.48,24,119.1,24,156a76,76,0,0,0,152,0V116.29A103.25,103.25,0,0,0,224,128a8,8,0,0,0,8-8V80A8,8,0,0,0,224,72Zm-8,39.64a87.19,87.19,0,0,1-43.33-16.15A8,8,0,0,0,160,102v54a60,60,0,0,1-120,0c0-25.9,16.64-49.13,40-57.6v27.67A36,36,0,1,0,136,156V32h24.5A64.14,64.14,0,0,0,216,87.5Z"/>',
57
+ "twitch-logo" => '<path d="M208,32H48A16,16,0,0,0,32,48V192a16,16,0,0,0,16,16H64v32a8,8,0,0,0,13.12,6.15L122.9,208h42.2a16,16,0,0,0,10.25-3.71l42.89-35.75A15.93,15.93,0,0,0,224,156.25V48A16,16,0,0,0,208,32Zm0,124.25L165.1,192H120a8,8,0,0,0-5.12,1.85L80,222.92V200a8,8,0,0,0-8-8H48V48H208ZM160,136V88a8,8,0,0,1,16,0v48a8,8,0,0,1-16,0Zm-48,0V88a8,8,0,0,1,16,0v48a8,8,0,0,1-16,0Z"/>',
58
+ "reddit-logo" => '<path d="M248,104a32,32,0,0,0-52.94-24.19c-16.75-8.9-36.76-14.28-57.66-15.53l5.19-31.17,17.72,2.72a24,24,0,1,0,2.87-15.74l-26-4a8,8,0,0,0-9.11,6.59L121.2,64.16c-21.84.94-42.82,6.38-60.26,15.65a32,32,0,0,0-42.59,47.74A59,59,0,0,0,16,144c0,21.93,12,42.35,33.91,57.49C70.88,216,98.61,224,128,224s57.12-8,78.09-22.51C228,186.35,240,165.93,240,144a59,59,0,0,0-2.35-16.45A32.16,32.16,0,0,0,248,104ZM184,24a8,8,0,1,1-8,8A8,8,0,0,1,184,24Zm40.13,93.78a8,8,0,0,0-3.29,10A43.58,43.58,0,0,1,224,144c0,16.53-9.59,32.27-27,44.33C178.67,201,154.17,208,128,208s-50.67-7-69-19.67C41.59,176.27,32,160.53,32,144a43.75,43.75,0,0,1,3.14-16.17,8,8,0,0,0-3.27-10A16,16,0,1,1,52.94,94.59a8,8,0,0,0,10.45,2.23l.36-.22C81.45,85.9,104.25,80,128,80h0c23.73,0,46.53,5.9,64.23,16.6l.42.25a8,8,0,0,0,10.39-2.26,16,16,0,1,1,21.07,23.19ZM88,144a16,16,0,1,1,16-16A16,16,0,0,1,88,144Zm96-16a16,16,0,1,1-16-16A16,16,0,0,1,184,128Zm-16.93,44.25a8,8,0,0,1-3.32,10.82,76.18,76.18,0,0,1-71.5,0,8,8,0,1,1,7.5-14.14,60.18,60.18,0,0,0,56.5,0A8,8,0,0,1,167.07,172.25Z"/>',
59
+ "mastodon-logo" => '<path d="M184,32H72A40,40,0,0,0,32,72V192a40,40,0,0,0,40,40h88a8,8,0,0,0,0-16H72a24,24,0,0,1-24-24v-8H184a40,40,0,0,0,40-40V72A40,40,0,0,0,184,32Zm24,112a24,24,0,0,1-24,24H48V72A24,24,0,0,1,72,48H184a24,24,0,0,1,24,24Zm-24-40v32a8,8,0,0,1-16,0V104a16,16,0,0,0-32,0v32a8,8,0,0,1-16,0V104a16,16,0,0,0-32,0v32a8,8,0,0,1-16,0V104a32,32,0,0,1,56-21.13A32,32,0,0,1,184,104Z"/>',
60
+ "threads-logo" => '<path d="M186.42,123.65a63.81,63.81,0,0,0-11.13-6.72c-4-29.89-24-39.31-33.1-42.07-19.78-6-42.51,1.19-52.85,16.7a8,8,0,0,0,13.32,8.88c6.37-9.56,22-14.16,34.89-10.27,9.95,3,16.82,10.3,20.15,21a81.05,81.05,0,0,0-15.29-1.43c-13.92,0-26.95,3.59-36.67,10.1C94.3,127.57,88,139,88,152c0,20.58,15.86,35.52,37.71,35.52a48,48,0,0,0,34.35-14.81c6.44-6.7,14-18.36,15.61-37.1.38.26.74.53,1.1.8C186.88,144.05,192,154.68,192,168c0,19.36-20.34,48-64,48-26.73,0-45.48-8.65-57.34-26.44C60.93,175,56,154.26,56,128s4.93-47,14.66-61.56C82.52,48.65,101.27,40,128,40c32.93,0,54,13.25,64.53,40.52a8,8,0,1,0,14.93-5.75C194.68,41.56,167.2,24,128,24,96,24,72.19,35.29,57.34,57.56,45.83,74.83,40,98.52,40,128s5.83,53.17,17.34,70.44C72.19,220.71,96,232,128,232c30.07,0,48.9-11.48,59.4-21.1C200.3,199.08,208,183,208,168,208,149.66,200.54,134.32,186.42,123.65Zm-37.89,38a31.94,31.94,0,0,1-22.82,9.9c-10.81,0-21.71-6-21.71-19.52,0-12.63,12-26.21,38.41-26.21A63.88,63.88,0,0,1,160,128.24C160,142.32,156,153.86,148.53,161.62Z"/>',
61
+ "pinterest-logo" => '<path d="M224,112c0,22.57-7.9,43.2-22.23,58.11C188.39,184,170.25,192,152,192c-17.88,0-29.82-5.86-37.43-12l-10.78,45.82A8,8,0,0,1,96,232a8.24,8.24,0,0,1-1.84-.21,8,8,0,0,1-6-9.62l32-136a8,8,0,0,1,15.58,3.66l-16.9,71.8C122,166,131.3,176,152,176c27.53,0,56-23.94,56-64A72,72,0,1,0,73.63,148a8,8,0,0,1-13.85,8A88,88,0,1,1,224,112Z"/>',
62
+ "medium-logo" => '<path d="M72,64a64,64,0,1,0,64,64A64.07,64.07,0,0,0,72,64Zm0,112a48,48,0,1,1,48-48A48.05,48.05,0,0,1,72,176ZM184,64c-5.68,0-16.4,2.76-24.32,21.25C154.73,96.8,152,112,152,128s2.73,31.2,7.68,42.75C167.6,189.24,178.32,192,184,192s16.4-2.76,24.32-21.25C213.27,159.2,216,144,216,128s-2.73-31.2-7.68-42.75C200.4,66.76,189.68,64,184,64Zm0,112c-5.64,0-16-18.22-16-48s10.36-48,16-48,16,18.22,16,48S189.64,176,184,176ZM248,72V184a8,8,0,0,1-16,0V72a8,8,0,0,1,16,0Z"/>',
63
+ "slack-logo" => '<path d="M221.13,128A32,32,0,0,0,184,76.31V56a32,32,0,0,0-56-21.13A32,32,0,0,0,76.31,72H56a32,32,0,0,0-21.13,56A32,32,0,0,0,72,179.69V200a32,32,0,0,0,56,21.13A32,32,0,0,0,179.69,184H200a32,32,0,0,0,21.13-56ZM72,152a16,16,0,1,1-16-16H72Zm48,48a16,16,0,0,1-32,0V152a16,16,0,0,1,16-16h16Zm0-80H56a16,16,0,0,1,0-32h48a16,16,0,0,1,16,16Zm0-48H104a16,16,0,1,1,16-16Zm16-16a16,16,0,0,1,32,0v48a16,16,0,0,1-16,16H136Zm16,160a16,16,0,0,1-16-16V184h16a16,16,0,0,1,0,32Zm48-48H152a16,16,0,0,1-16-16V136h64a16,16,0,0,1,0,32Zm0-48H184V104a16,16,0,1,1,16,16Z"/>',
64
+ "gitlab-logo" => '<path d="M230.15,117.1,210.25,41a11.94,11.94,0,0,0-22.79-1.11L169.78,88H86.22L68.54,39.87A11.94,11.94,0,0,0,45.75,41L25.85,117.1a57.19,57.19,0,0,0,22,61l73.27,51.76a11.91,11.91,0,0,0,13.74,0l73.27-51.76A57.19,57.19,0,0,0,230.15,117.1ZM58,57.5,73.13,98.76A8,8,0,0,0,80.64,104h94.72a8,8,0,0,0,7.51-5.24L198,57.5l13.07,50L128,166.21,44.9,107.5ZM40.68,124.11,114.13,176,93.41,190.65,57.09,165A41.06,41.06,0,0,1,40.68,124.11Zm87.32,91-20.73-14.65L128,185.8l20.73,14.64ZM198.91,165l-36.32,25.66L141.87,176l73.45-51.9A41.06,41.06,0,0,1,198.91,165Z"/>'
40
65
  },
41
66
  "bold" => {
42
67
  "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"/>'
@@ -3,19 +3,20 @@
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/code_snippet_import_preprocessor"
13
- require_relative "components/code_block_options_preprocessor"
14
- require_relative "components/code_block_diff_preprocessor"
15
- require_relative "components/code_block_focus_preprocessor"
16
- require_relative "components/table_wrapper_processor"
17
- require_relative "components/heading_anchor_processor"
18
- require_relative "components/table_of_contents_processor"
6
+ require_relative "../components/registry"
7
+ require_relative "../components/base_processor"
8
+ require_relative "../components/processors/callout_processor"
9
+ require_relative "../components/processors/tabs_processor"
10
+ require_relative "../components/processors/icon_processor"
11
+ require_relative "../components/processors/code_block_processor"
12
+ require_relative "../components/processors/code_snippet_import_preprocessor"
13
+ require_relative "../components/processors/code_block_options_preprocessor"
14
+ require_relative "../components/processors/code_block_diff_preprocessor"
15
+ require_relative "../components/processors/code_block_focus_preprocessor"
16
+ require_relative "../components/processors/table_wrapper_processor"
17
+ require_relative "../components/processors/heading_anchor_processor"
18
+ require_relative "../components/processors/table_of_contents_processor"
19
+ require_relative "../components/aliases"
19
20
 
20
21
  module Docyard
21
22
  class Markdown
@@ -61,6 +62,10 @@ module Docyard
61
62
  frontmatter.dig("sidebar", "collapsed")
62
63
  end
63
64
 
65
+ def sidebar_order
66
+ frontmatter.dig("sidebar", "order")
67
+ end
68
+
64
69
  def toc
65
70
  @context[:toc] || []
66
71
  end
@@ -90,6 +95,7 @@ module Docyard
90
95
  input: "GFM",
91
96
  hard_wrap: false,
92
97
  syntax_highlighter: "rouge",
98
+ syntax_highlighter_opts: { guess_lang: true },
93
99
  parse_block_html: true
94
100
  ).to_html
95
101