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.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -1
- data/CHANGELOG.md +43 -1
- data/lib/docyard/build/asset_bundler.rb +22 -7
- data/lib/docyard/build/file_copier.rb +49 -27
- data/lib/docyard/build/sitemap_generator.rb +6 -6
- data/lib/docyard/build/static_generator.rb +85 -12
- data/lib/docyard/builder.rb +6 -6
- data/lib/docyard/components/aliases.rb +12 -0
- data/lib/docyard/components/processors/abbreviation_processor.rb +72 -0
- data/lib/docyard/components/processors/accordion_processor.rb +81 -0
- data/lib/docyard/components/processors/badge_processor.rb +72 -0
- data/lib/docyard/components/processors/callout_processor.rb +8 -2
- data/lib/docyard/components/processors/cards_processor.rb +100 -0
- data/lib/docyard/components/processors/code_block_options_preprocessor.rb +23 -2
- data/lib/docyard/components/processors/code_block_processor.rb +6 -0
- data/lib/docyard/components/processors/code_group_processor.rb +198 -0
- data/lib/docyard/components/processors/code_snippet_import_preprocessor.rb +6 -1
- data/lib/docyard/components/processors/custom_anchor_processor.rb +42 -0
- data/lib/docyard/components/processors/file_tree_processor.rb +151 -0
- data/lib/docyard/components/processors/image_caption_processor.rb +96 -0
- data/lib/docyard/components/processors/include_processor.rb +86 -0
- data/lib/docyard/components/processors/steps_processor.rb +89 -0
- data/lib/docyard/components/processors/tabs_processor.rb +9 -1
- data/lib/docyard/components/processors/tooltip_processor.rb +57 -0
- data/lib/docyard/components/processors/video_embed_processor.rb +196 -0
- data/lib/docyard/components/support/code_group/html_builder.rb +122 -0
- data/lib/docyard/components/support/markdown_code_block_helper.rb +56 -0
- data/lib/docyard/config/branding_resolver.rb +121 -17
- data/lib/docyard/config/constants.rb +6 -4
- data/lib/docyard/config/logo_detector.rb +39 -0
- data/lib/docyard/config/validator.rb +122 -99
- data/lib/docyard/config.rb +40 -42
- data/lib/docyard/initializer.rb +15 -76
- data/lib/docyard/navigation/breadcrumb_builder.rb +133 -0
- data/lib/docyard/navigation/prev_next_builder.rb +4 -1
- data/lib/docyard/navigation/sidebar/children_discoverer.rb +51 -0
- data/lib/docyard/navigation/sidebar/config_parser.rb +136 -108
- data/lib/docyard/navigation/sidebar/file_resolver.rb +90 -0
- data/lib/docyard/navigation/sidebar/file_system_scanner.rb +2 -1
- data/lib/docyard/navigation/sidebar/item.rb +50 -7
- data/lib/docyard/navigation/sidebar/local_config_loader.rb +51 -0
- data/lib/docyard/navigation/sidebar/metadata_extractor.rb +71 -0
- data/lib/docyard/navigation/sidebar/metadata_reader.rb +51 -0
- data/lib/docyard/navigation/sidebar/path_prefixer.rb +34 -0
- data/lib/docyard/navigation/sidebar/renderer.rb +60 -38
- data/lib/docyard/navigation/sidebar/sorter.rb +21 -0
- data/lib/docyard/navigation/sidebar/tree_builder.rb +100 -26
- data/lib/docyard/navigation/sidebar/tree_filter.rb +55 -0
- data/lib/docyard/navigation/sidebar_builder.rb +105 -36
- data/lib/docyard/rendering/icon_helpers.rb +13 -0
- data/lib/docyard/rendering/icons/phosphor.rb +26 -1
- data/lib/docyard/rendering/markdown.rb +29 -1
- data/lib/docyard/rendering/renderer.rb +75 -34
- data/lib/docyard/rendering/template_resolver.rb +172 -0
- data/lib/docyard/routing/fallback_resolver.rb +92 -0
- data/lib/docyard/search/build_indexer.rb +1 -1
- data/lib/docyard/search/dev_indexer.rb +51 -6
- data/lib/docyard/search/pagefind_support.rb +2 -0
- data/lib/docyard/server/asset_handler.rb +25 -19
- data/lib/docyard/server/pagefind_handler.rb +63 -0
- data/lib/docyard/server/preview_server.rb +1 -1
- data/lib/docyard/server/rack_application.rb +81 -64
- data/lib/docyard/templates/assets/css/code.css +18 -51
- data/lib/docyard/templates/assets/css/components/abbreviation.css +86 -0
- data/lib/docyard/templates/assets/css/components/accordion.css +138 -0
- data/lib/docyard/templates/assets/css/components/badges.css +47 -0
- data/lib/docyard/templates/assets/css/components/banner.css +202 -0
- data/lib/docyard/templates/assets/css/components/breadcrumbs.css +143 -0
- data/lib/docyard/templates/assets/css/components/callout.css +67 -67
- data/lib/docyard/templates/assets/css/components/cards.css +100 -0
- data/lib/docyard/templates/assets/css/components/code-block.css +190 -282
- data/lib/docyard/templates/assets/css/components/code-group.css +281 -0
- data/lib/docyard/templates/assets/css/components/figure.css +22 -0
- data/lib/docyard/templates/assets/css/components/file-tree.css +124 -0
- data/lib/docyard/templates/assets/css/components/heading-anchor.css +36 -15
- data/lib/docyard/templates/assets/css/components/icon.css +0 -1
- data/lib/docyard/templates/assets/css/components/lightbox.css +65 -0
- data/lib/docyard/templates/assets/css/components/logo.css +0 -2
- data/lib/docyard/templates/assets/css/components/nav-menu.css +237 -0
- data/lib/docyard/templates/assets/css/components/navigation.css +193 -167
- data/lib/docyard/templates/assets/css/components/prev-next.css +68 -48
- data/lib/docyard/templates/assets/css/components/search.css +186 -174
- data/lib/docyard/templates/assets/css/components/steps.css +122 -0
- data/lib/docyard/templates/assets/css/components/tab-bar.css +163 -0
- data/lib/docyard/templates/assets/css/components/table-of-contents.css +127 -114
- data/lib/docyard/templates/assets/css/components/tabs.css +119 -160
- data/lib/docyard/templates/assets/css/components/theme-toggle.css +48 -44
- data/lib/docyard/templates/assets/css/components/tooltip.css +113 -0
- data/lib/docyard/templates/assets/css/components/video.css +41 -0
- data/lib/docyard/templates/assets/css/landing.css +815 -0
- data/lib/docyard/templates/assets/css/layout.css +489 -87
- data/lib/docyard/templates/assets/css/main.css +1 -3
- data/lib/docyard/templates/assets/css/markdown.css +113 -93
- data/lib/docyard/templates/assets/css/reset.css +0 -3
- data/lib/docyard/templates/assets/css/typography.css +43 -41
- data/lib/docyard/templates/assets/css/variables.css +268 -208
- data/lib/docyard/templates/assets/favicon.svg +7 -8
- data/lib/docyard/templates/assets/fonts/Inter-Variable.ttf +0 -0
- data/lib/docyard/templates/assets/js/components/abbreviation.js +85 -0
- data/lib/docyard/templates/assets/js/components/banner.js +81 -0
- data/lib/docyard/templates/assets/js/components/code-block.js +24 -42
- data/lib/docyard/templates/assets/js/components/code-group.js +283 -0
- data/lib/docyard/templates/assets/js/components/file-tree.js +39 -0
- data/lib/docyard/templates/assets/js/components/heading-anchor.js +26 -24
- data/lib/docyard/templates/assets/js/components/lightbox.js +72 -0
- data/lib/docyard/templates/assets/js/components/navigation.js +181 -70
- data/lib/docyard/templates/assets/js/components/search.js +0 -75
- data/lib/docyard/templates/assets/js/components/sidebar-toggle.js +29 -0
- data/lib/docyard/templates/assets/js/components/tab-navigation.js +145 -0
- data/lib/docyard/templates/assets/js/components/table-of-contents.js +153 -66
- data/lib/docyard/templates/assets/js/components/tabs.js +31 -69
- data/lib/docyard/templates/assets/js/components/tooltip.js +118 -0
- data/lib/docyard/templates/assets/js/theme.js +0 -3
- data/lib/docyard/templates/assets/logo-dark.svg +8 -2
- data/lib/docyard/templates/assets/logo.svg +7 -4
- data/lib/docyard/templates/config/docyard.yml.erb +37 -34
- data/lib/docyard/templates/errors/404.html.erb +1 -1
- data/lib/docyard/templates/errors/500.html.erb +1 -1
- data/lib/docyard/templates/layouts/default.html.erb +19 -67
- data/lib/docyard/templates/layouts/splash.html.erb +177 -0
- data/lib/docyard/templates/partials/_accordion.html.erb +9 -0
- data/lib/docyard/templates/partials/_banner.html.erb +27 -0
- data/lib/docyard/templates/partials/_breadcrumbs.html.erb +24 -0
- data/lib/docyard/templates/partials/_card.html.erb +23 -0
- data/lib/docyard/templates/partials/_code_block.html.erb +5 -3
- data/lib/docyard/templates/partials/_doc_footer.html.erb +25 -0
- data/lib/docyard/templates/partials/_features.html.erb +15 -0
- data/lib/docyard/templates/partials/_footer.html.erb +42 -0
- data/lib/docyard/templates/partials/_head.html.erb +22 -0
- data/lib/docyard/templates/partials/_header.html.erb +49 -0
- data/lib/docyard/templates/partials/_heading_anchor.html.erb +3 -1
- data/lib/docyard/templates/partials/_hero.html.erb +27 -0
- data/lib/docyard/templates/partials/_nav_group.html.erb +31 -11
- data/lib/docyard/templates/partials/_nav_leaf.html.erb +4 -1
- data/lib/docyard/templates/partials/_nav_menu.html.erb +42 -0
- data/lib/docyard/templates/partials/_nav_nested_section.html.erb +11 -0
- data/lib/docyard/templates/partials/_nav_section.html.erb +1 -1
- data/lib/docyard/templates/partials/_prev_next.html.erb +8 -2
- data/lib/docyard/templates/partials/_scripts.html.erb +7 -0
- data/lib/docyard/templates/partials/_search_modal.html.erb +2 -6
- data/lib/docyard/templates/partials/_search_trigger.html.erb +2 -6
- data/lib/docyard/templates/partials/_sidebar.html.erb +21 -4
- data/lib/docyard/templates/partials/_step.html.erb +14 -0
- data/lib/docyard/templates/partials/_tab_bar.html.erb +25 -0
- data/lib/docyard/templates/partials/_table_of_contents.html.erb +12 -12
- data/lib/docyard/templates/partials/_table_of_contents_toggle.html.erb +1 -3
- data/lib/docyard/templates/partials/_tabs.html.erb +2 -2
- data/lib/docyard/templates/partials/_theme_toggle.html.erb +2 -11
- data/lib/docyard/version.rb +1 -1
- metadata +70 -5
- data/lib/docyard/templates/markdown/getting-started/installation.md.erb +0 -77
- data/lib/docyard/templates/markdown/guides/configuration.md.erb +0 -202
- data/lib/docyard/templates/markdown/guides/markdown-features.md.erb +0 -247
- data/lib/docyard/templates/markdown/index.md.erb +0 -82
|
@@ -3,21 +3,56 @@
|
|
|
3
3
|
module Docyard
|
|
4
4
|
module Sidebar
|
|
5
5
|
class Item
|
|
6
|
-
attr_reader :slug, :text, :icon, :link, :target, :collapsed, :items, :path, :active, :type
|
|
6
|
+
attr_reader :slug, :text, :icon, :link, :target, :collapsed, :items, :path, :active, :type, :has_index, :section,
|
|
7
|
+
:badge, :badge_type
|
|
8
|
+
|
|
9
|
+
DEFAULTS = {
|
|
10
|
+
target: "_self",
|
|
11
|
+
collapsed: false,
|
|
12
|
+
items: [],
|
|
13
|
+
active: false,
|
|
14
|
+
type: :file,
|
|
15
|
+
has_index: false,
|
|
16
|
+
section: true
|
|
17
|
+
}.freeze
|
|
7
18
|
|
|
8
19
|
def initialize(**options)
|
|
20
|
+
assign_required_attributes(options)
|
|
21
|
+
assign_optional_attributes(options)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def assign_required_attributes(options)
|
|
9
27
|
@slug = options[:slug]
|
|
10
28
|
@text = options[:text]
|
|
11
29
|
@icon = options[:icon]
|
|
12
30
|
@link = options[:link]
|
|
13
|
-
@
|
|
14
|
-
@
|
|
15
|
-
|
|
31
|
+
@badge = options[:badge]
|
|
32
|
+
@badge_type = options[:badge_type]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def assign_optional_attributes(options)
|
|
36
|
+
assign_navigation_attributes(options)
|
|
37
|
+
assign_state_attributes(options)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def assign_navigation_attributes(options)
|
|
41
|
+
@target = options.fetch(:target, DEFAULTS[:target])
|
|
16
42
|
@path = options[:path] || options[:link]
|
|
17
|
-
@active = options[:active]
|
|
18
|
-
@type = options[:type]
|
|
43
|
+
@active = options.fetch(:active, DEFAULTS[:active])
|
|
44
|
+
@type = options.fetch(:type, DEFAULTS[:type])
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def assign_state_attributes(options)
|
|
48
|
+
@collapsed = options.fetch(:collapsed, DEFAULTS[:collapsed])
|
|
49
|
+
@items = options.fetch(:items, DEFAULTS[:items])
|
|
50
|
+
@has_index = options.fetch(:has_index, DEFAULTS[:has_index])
|
|
51
|
+
@section = options.fetch(:section, DEFAULTS[:section])
|
|
19
52
|
end
|
|
20
53
|
|
|
54
|
+
public
|
|
55
|
+
|
|
21
56
|
def external?
|
|
22
57
|
return false if path.nil?
|
|
23
58
|
|
|
@@ -37,7 +72,11 @@ module Docyard
|
|
|
37
72
|
end
|
|
38
73
|
|
|
39
74
|
def collapsible?
|
|
40
|
-
children?
|
|
75
|
+
children? && !section
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def section?
|
|
79
|
+
section == true
|
|
41
80
|
end
|
|
42
81
|
|
|
43
82
|
def to_h
|
|
@@ -50,6 +89,10 @@ module Docyard
|
|
|
50
89
|
collapsed: collapsed,
|
|
51
90
|
collapsible: collapsible?,
|
|
52
91
|
target: target,
|
|
92
|
+
has_index: has_index,
|
|
93
|
+
section: section,
|
|
94
|
+
badge: badge,
|
|
95
|
+
badge_type: badge_type,
|
|
53
96
|
children: children.map(&:to_h)
|
|
54
97
|
}
|
|
55
98
|
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Docyard
|
|
6
|
+
module Sidebar
|
|
7
|
+
class LocalConfigLoader
|
|
8
|
+
SIDEBAR_CONFIG_FILE = "_sidebar.yml"
|
|
9
|
+
|
|
10
|
+
attr_reader :docs_path
|
|
11
|
+
|
|
12
|
+
def initialize(docs_path)
|
|
13
|
+
@docs_path = docs_path
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def load
|
|
17
|
+
return nil unless config_file_exists?
|
|
18
|
+
|
|
19
|
+
parse_config_file
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def config_file_exists?
|
|
23
|
+
File.file?(config_file_path)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def config_file_path
|
|
29
|
+
File.join(docs_path, SIDEBAR_CONFIG_FILE)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def parse_config_file
|
|
33
|
+
content = YAML.load_file(config_file_path)
|
|
34
|
+
normalize_config(content)
|
|
35
|
+
rescue Psych::SyntaxError => e
|
|
36
|
+
warn "Warning: Invalid YAML in #{config_file_path}: #{e.message}"
|
|
37
|
+
nil
|
|
38
|
+
rescue StandardError => e
|
|
39
|
+
warn "Warning: Error reading #{config_file_path}: #{e.message}"
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def normalize_config(content)
|
|
44
|
+
return nil if content.nil?
|
|
45
|
+
return content if content.is_a?(Array)
|
|
46
|
+
|
|
47
|
+
content["items"] if content.is_a?(Hash)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module Sidebar
|
|
5
|
+
class MetadataExtractor
|
|
6
|
+
attr_reader :docs_path, :title_extractor
|
|
7
|
+
|
|
8
|
+
def initialize(docs_path:, title_extractor:)
|
|
9
|
+
@docs_path = docs_path
|
|
10
|
+
@title_extractor = title_extractor
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def extract_index_metadata(file_path)
|
|
14
|
+
return { sidebar_text: nil, icon: nil } unless File.file?(file_path)
|
|
15
|
+
|
|
16
|
+
markdown = Markdown.new(File.read(file_path))
|
|
17
|
+
{
|
|
18
|
+
sidebar_text: markdown.sidebar_text,
|
|
19
|
+
icon: markdown.sidebar_icon
|
|
20
|
+
}
|
|
21
|
+
rescue StandardError
|
|
22
|
+
{ sidebar_text: nil, icon: nil }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def extract_frontmatter_metadata(file_path)
|
|
26
|
+
return { text: nil, icon: nil, badge: nil, badge_type: nil } unless File.exist?(file_path)
|
|
27
|
+
|
|
28
|
+
markdown = Markdown.new(File.read(file_path))
|
|
29
|
+
{
|
|
30
|
+
text: markdown.sidebar_text || markdown.title,
|
|
31
|
+
icon: markdown.sidebar_icon,
|
|
32
|
+
badge: markdown.sidebar_badge,
|
|
33
|
+
badge_type: markdown.sidebar_badge_type
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def extract_file_title(file_path, slug)
|
|
38
|
+
File.exist?(file_path) ? title_extractor.extract(file_path) : Utils::TextFormatter.titleize(slug)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def extract_common_options(options)
|
|
42
|
+
collapsed_value = options["collapsed"]
|
|
43
|
+
collapsed_value = options[:collapsed] if collapsed_value.nil?
|
|
44
|
+
collapsible_value = options["collapsible"]
|
|
45
|
+
collapsible_value = options[:collapsible] if collapsible_value.nil?
|
|
46
|
+
collapsible_value = true if !collapsed_value.nil? && collapsible_value.nil?
|
|
47
|
+
{
|
|
48
|
+
text: options["text"] || options[:text],
|
|
49
|
+
icon: options["icon"] || options[:icon],
|
|
50
|
+
collapsed: collapsed_value,
|
|
51
|
+
section: section_from_collapsible(collapsible_value)
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def section_from_collapsible(collapsible_value)
|
|
56
|
+
return nil if collapsible_value.nil?
|
|
57
|
+
|
|
58
|
+
collapsible_value != true
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def resolve_item_text(slug, file_path, options, frontmatter_text)
|
|
62
|
+
text = options["text"] || options[:text] || frontmatter_text
|
|
63
|
+
text || extract_file_title(file_path, slug)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def resolve_item_icon(options, frontmatter_icon)
|
|
67
|
+
options["icon"] || options[:icon] || frontmatter_icon
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module Sidebar
|
|
5
|
+
class MetadataReader
|
|
6
|
+
def extract_file_metadata(file_path)
|
|
7
|
+
return empty_file_metadata unless File.file?(file_path)
|
|
8
|
+
|
|
9
|
+
content = File.read(file_path)
|
|
10
|
+
markdown = Markdown.new(content)
|
|
11
|
+
{
|
|
12
|
+
title: markdown.sidebar_text || markdown.title,
|
|
13
|
+
icon: markdown.sidebar_icon,
|
|
14
|
+
collapsed: markdown.sidebar_collapsed,
|
|
15
|
+
order: markdown.sidebar_order,
|
|
16
|
+
badge: markdown.sidebar_badge,
|
|
17
|
+
badge_type: markdown.sidebar_badge_type
|
|
18
|
+
}
|
|
19
|
+
rescue StandardError
|
|
20
|
+
empty_file_metadata
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def extract_index_metadata(file_path)
|
|
24
|
+
return empty_index_metadata unless File.file?(file_path)
|
|
25
|
+
|
|
26
|
+
content = File.read(file_path)
|
|
27
|
+
markdown = Markdown.new(content)
|
|
28
|
+
{
|
|
29
|
+
sidebar_text: markdown.sidebar_text,
|
|
30
|
+
icon: markdown.sidebar_icon,
|
|
31
|
+
collapsed: markdown.sidebar_collapsed,
|
|
32
|
+
order: markdown.sidebar_order,
|
|
33
|
+
badge: markdown.sidebar_badge,
|
|
34
|
+
badge_type: markdown.sidebar_badge_type
|
|
35
|
+
}
|
|
36
|
+
rescue StandardError
|
|
37
|
+
empty_index_metadata
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def empty_file_metadata
|
|
43
|
+
{ title: nil, icon: nil, collapsed: nil, order: nil, badge: nil, badge_type: nil }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def empty_index_metadata
|
|
47
|
+
{ sidebar_text: nil, icon: nil, collapsed: nil, order: nil, badge: nil, badge_type: nil }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module Sidebar
|
|
5
|
+
class PathPrefixer
|
|
6
|
+
def initialize(tree, prefix)
|
|
7
|
+
@tree = tree
|
|
8
|
+
@prefix = prefix
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def prefix
|
|
12
|
+
return @tree if @prefix.empty?
|
|
13
|
+
|
|
14
|
+
@tree.map { |item| prefix_item(item) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def prefix_item(item)
|
|
20
|
+
prefixed = item.dup
|
|
21
|
+
prefixed[:path] = prefixed_path(prefixed[:path])
|
|
22
|
+
prefixed[:children] = self.class.new(prefixed[:children], @prefix).prefix if prefixed[:children]&.any?
|
|
23
|
+
prefixed
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def prefixed_path(path)
|
|
27
|
+
return path if path.nil? || path.start_with?("http")
|
|
28
|
+
|
|
29
|
+
path_without_slash = path.sub(%r{^/}, "")
|
|
30
|
+
path_without_slash.empty? ? @prefix : "#{@prefix}/#{path_without_slash}"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "erb"
|
|
4
|
+
require_relative "../../rendering/icon_helpers"
|
|
4
5
|
|
|
5
6
|
module Docyard
|
|
6
7
|
module Sidebar
|
|
7
8
|
class Renderer
|
|
8
9
|
include Utils::UrlHelpers
|
|
10
|
+
include IconHelpers
|
|
9
11
|
|
|
10
12
|
PARTIALS_PATH = File.join(__dir__, "../../templates/partials")
|
|
11
13
|
|
|
12
|
-
attr_reader :site_title, :base_url
|
|
14
|
+
attr_reader :site_title, :base_url, :header_ctas
|
|
13
15
|
|
|
14
|
-
def initialize(site_title: "Documentation", base_url: "/")
|
|
16
|
+
def initialize(site_title: "Documentation", base_url: "/", header_ctas: [])
|
|
15
17
|
@site_title = site_title
|
|
16
18
|
@base_url = normalize_base_url(base_url)
|
|
19
|
+
@header_ctas = header_ctas
|
|
17
20
|
end
|
|
18
21
|
|
|
19
22
|
def render(tree)
|
|
20
23
|
return "" if tree.empty?
|
|
21
24
|
|
|
22
25
|
nav_content = render_tree_with_sections(tree)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
render_partial(:sidebar, nav_content: nav_content, footer_html: footer_html)
|
|
26
|
+
render_partial(:sidebar, nav_content: nav_content, header_ctas: header_ctas)
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
private
|
|
@@ -37,50 +38,52 @@ module Docyard
|
|
|
37
38
|
ERB.new(template).result(erb_binding)
|
|
38
39
|
end
|
|
39
40
|
|
|
40
|
-
def icon(name, weight = "regular")
|
|
41
|
-
Icons.render(name.to_s.tr("_", "-"), weight) || ""
|
|
42
|
-
end
|
|
43
|
-
|
|
44
41
|
def render_tree_with_sections(items)
|
|
45
42
|
filtered_items = items.reject { |item| item[:title]&.downcase == site_title.downcase }
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
50
51
|
end.join
|
|
51
52
|
end
|
|
52
53
|
|
|
53
|
-
def
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def group_by_section(items)
|
|
59
|
-
sections = {}
|
|
60
|
-
root_items = []
|
|
54
|
+
def group_items_by_section(items)
|
|
55
|
+
groups = []
|
|
56
|
+
current_non_section_items = []
|
|
61
57
|
|
|
62
58
|
items.each do |item|
|
|
63
|
-
|
|
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
|
|
64
68
|
end
|
|
65
69
|
|
|
66
|
-
|
|
70
|
+
groups << { section: false, items: current_non_section_items } if current_non_section_items.any?
|
|
71
|
+
groups
|
|
67
72
|
end
|
|
68
73
|
|
|
69
|
-
def
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
else
|
|
76
|
-
root_items << item
|
|
77
|
-
end
|
|
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)
|
|
78
80
|
end
|
|
79
81
|
|
|
80
|
-
def
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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))
|
|
84
87
|
end
|
|
85
88
|
|
|
86
89
|
def render_tree(items)
|
|
@@ -93,6 +96,8 @@ module Docyard
|
|
|
93
96
|
def render_item(item)
|
|
94
97
|
item_content = if item[:children].empty?
|
|
95
98
|
render_leaf_item(item)
|
|
99
|
+
elsif item[:section]
|
|
100
|
+
render_nested_section(item)
|
|
96
101
|
else
|
|
97
102
|
render_group_item(item)
|
|
98
103
|
end
|
|
@@ -107,7 +112,19 @@ module Docyard
|
|
|
107
112
|
title: item[:title],
|
|
108
113
|
active: item[:active],
|
|
109
114
|
icon: item[:icon],
|
|
110
|
-
target: item[:target]
|
|
115
|
+
target: item[:target],
|
|
116
|
+
badge: item[:badge],
|
|
117
|
+
badge_type: item[:badge_type]
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def render_nested_section(item)
|
|
122
|
+
children_html = render_tree(item[:children])
|
|
123
|
+
render_partial(
|
|
124
|
+
:nav_nested_section,
|
|
125
|
+
title: item[:title],
|
|
126
|
+
icon: item[:icon],
|
|
127
|
+
children_html: children_html
|
|
111
128
|
)
|
|
112
129
|
end
|
|
113
130
|
|
|
@@ -116,9 +133,14 @@ module Docyard
|
|
|
116
133
|
render_partial(
|
|
117
134
|
:nav_group,
|
|
118
135
|
title: item[:title],
|
|
136
|
+
path: item[:path],
|
|
137
|
+
active: item[:active],
|
|
119
138
|
children_html: children_html,
|
|
120
139
|
icon: item[:icon],
|
|
121
|
-
collapsed: item[:collapsed]
|
|
140
|
+
collapsed: item[:collapsed],
|
|
141
|
+
has_index: item[:has_index],
|
|
142
|
+
badge: item[:badge],
|
|
143
|
+
badge_type: item[:badge_type]
|
|
122
144
|
)
|
|
123
145
|
end
|
|
124
146
|
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
|
|
@@ -1,65 +1,139 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "sorter"
|
|
4
|
+
require_relative "local_config_loader"
|
|
5
|
+
require_relative "config_parser"
|
|
6
|
+
require_relative "metadata_reader"
|
|
7
|
+
|
|
3
8
|
module Docyard
|
|
4
9
|
module Sidebar
|
|
5
10
|
class TreeBuilder
|
|
6
|
-
attr_reader :docs_path, :current_path, :title_extractor
|
|
11
|
+
attr_reader :docs_path, :current_path, :title_extractor, :metadata_reader
|
|
7
12
|
|
|
8
13
|
def initialize(docs_path:, current_path:, title_extractor: TitleExtractor.new)
|
|
9
14
|
@docs_path = docs_path
|
|
10
15
|
@current_path = Utils::PathResolver.normalize(current_path)
|
|
11
16
|
@title_extractor = title_extractor
|
|
17
|
+
@metadata_reader = MetadataReader.new
|
|
12
18
|
end
|
|
13
19
|
|
|
14
20
|
def build(file_items)
|
|
15
|
-
transform_items(file_items, "")
|
|
21
|
+
transform_items(file_items, "", depth: 1)
|
|
16
22
|
end
|
|
17
23
|
|
|
18
24
|
private
|
|
19
25
|
|
|
20
|
-
def transform_items(items, relative_base)
|
|
21
|
-
items.map do |item|
|
|
26
|
+
def transform_items(items, relative_base, depth:)
|
|
27
|
+
transformed = items.map do |item|
|
|
22
28
|
if item[:type] == :directory
|
|
23
|
-
transform_directory(item, relative_base)
|
|
29
|
+
transform_directory(item, relative_base, depth: depth)
|
|
24
30
|
else
|
|
25
31
|
transform_file(item, relative_base)
|
|
26
32
|
end
|
|
27
33
|
end
|
|
34
|
+
Sorter.sort_by_order(transformed)
|
|
28
35
|
end
|
|
29
36
|
|
|
30
|
-
def transform_directory(item, relative_base)
|
|
37
|
+
def transform_directory(item, relative_base, depth:)
|
|
31
38
|
dir_path = File.join(relative_base, item[:name])
|
|
32
|
-
|
|
39
|
+
dir_context = build_directory_context(dir_path)
|
|
40
|
+
children = build_directory_children(item, dir_path, depth)
|
|
33
41
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
collapsible: true,
|
|
40
|
-
collapsed: !active_child?(children),
|
|
41
|
-
children: children
|
|
42
|
-
}
|
|
42
|
+
if depth == 1
|
|
43
|
+
build_section(item, children, dir_context)
|
|
44
|
+
else
|
|
45
|
+
build_collapsible_group(item, children, dir_context)
|
|
46
|
+
end
|
|
43
47
|
end
|
|
44
48
|
|
|
45
|
-
def
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
48
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] || []) }
|
|
49
125
|
end
|
|
50
126
|
|
|
51
127
|
def transform_file(item, relative_base)
|
|
52
128
|
file_path = File.join(relative_base, "#{item[:name]}#{Constants::MARKDOWN_EXTENSION}")
|
|
53
129
|
full_file_path = File.join(docs_path, file_path)
|
|
54
130
|
url_path = Utils::PathResolver.to_url(file_path.delete_suffix(Constants::MARKDOWN_EXTENSION))
|
|
131
|
+
metadata = metadata_reader.extract_file_metadata(full_file_path)
|
|
55
132
|
|
|
56
|
-
{
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
type: :file,
|
|
61
|
-
children: []
|
|
62
|
-
}
|
|
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], badge: metadata[:badge],
|
|
136
|
+
badge_type: metadata[:badge_type], children: [] }
|
|
63
137
|
end
|
|
64
138
|
end
|
|
65
139
|
end
|