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
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
class BreadcrumbBuilder
|
|
5
|
+
MAX_VISIBLE_ITEMS = 3
|
|
6
|
+
|
|
7
|
+
Item = Struct.new(:title, :href, :current, keyword_init: true)
|
|
8
|
+
|
|
9
|
+
attr_reader :sidebar_tree, :current_path
|
|
10
|
+
|
|
11
|
+
def initialize(sidebar_tree:, current_path:)
|
|
12
|
+
@sidebar_tree = sidebar_tree || []
|
|
13
|
+
@current_path = normalize_path(current_path)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def items
|
|
17
|
+
@items ||= build_items
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def truncated?
|
|
21
|
+
full_path_items.length > MAX_VISIBLE_ITEMS
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def should_show?
|
|
25
|
+
items.any? && !root_page?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def build_items
|
|
31
|
+
return [] if full_path_items.empty?
|
|
32
|
+
|
|
33
|
+
if truncated?
|
|
34
|
+
truncated_items
|
|
35
|
+
else
|
|
36
|
+
full_path_items
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def truncated_items
|
|
41
|
+
path = full_path_items
|
|
42
|
+
[
|
|
43
|
+
Item.new(title: "...", href: nil, current: false),
|
|
44
|
+
path[-2],
|
|
45
|
+
path[-1]
|
|
46
|
+
].compact
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def full_path_items
|
|
50
|
+
@full_path_items ||= find_breadcrumb_path(sidebar_tree, [])
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def find_breadcrumb_path(nodes, path)
|
|
54
|
+
nodes.each do |node|
|
|
55
|
+
result = process_node(node, path)
|
|
56
|
+
return result if result
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
[]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def process_node(node, path)
|
|
63
|
+
node_path = normalize_path(node[:path])
|
|
64
|
+
node_title = truncate_title(node[:title] || "")
|
|
65
|
+
|
|
66
|
+
return build_current_item(path, node_title, node_path) if exact_match?(node_path)
|
|
67
|
+
|
|
68
|
+
search_in_ancestors(node, path, node_title, node_path) ||
|
|
69
|
+
search_in_children(node, path)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def build_current_item(path, title, href)
|
|
73
|
+
path + [Item.new(title: title, href: href, current: true)]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def search_in_ancestors(node, path, title, href)
|
|
77
|
+
return unless node[:children]&.any?
|
|
78
|
+
|
|
79
|
+
effective_href = href == "/" ? derive_section_path(node) : href
|
|
80
|
+
return unless path_is_ancestor?(effective_href)
|
|
81
|
+
|
|
82
|
+
result = find_breadcrumb_path(
|
|
83
|
+
node[:children],
|
|
84
|
+
path + [Item.new(title: title, href: effective_href, current: false)]
|
|
85
|
+
)
|
|
86
|
+
result.any? ? result : nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def derive_section_path(node)
|
|
90
|
+
first_child = node[:children]&.first
|
|
91
|
+
return nil unless first_child
|
|
92
|
+
|
|
93
|
+
child_path = first_child[:path]
|
|
94
|
+
return nil if child_path.nil? || child_path.empty?
|
|
95
|
+
|
|
96
|
+
File.dirname(child_path)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def search_in_children(node, path)
|
|
100
|
+
return unless node[:children]&.any?
|
|
101
|
+
|
|
102
|
+
result = find_breadcrumb_path(node[:children], path)
|
|
103
|
+
result.any? ? result : nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def exact_match?(node_path)
|
|
107
|
+
normalize_path(node_path) == current_path
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def path_is_ancestor?(node_path)
|
|
111
|
+
return false if node_path.nil? || node_path.empty? || node_path == "/"
|
|
112
|
+
|
|
113
|
+
normalized = normalize_path(node_path)
|
|
114
|
+
current_path.start_with?("#{normalized}/") || current_path == normalized
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def normalize_path(path)
|
|
118
|
+
return "/" if path.nil? || path.empty?
|
|
119
|
+
|
|
120
|
+
path.chomp("/")
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def truncate_title(title)
|
|
124
|
+
return title if title.length <= 30
|
|
125
|
+
|
|
126
|
+
"#{title[0, 27]}..."
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def root_page?
|
|
130
|
+
current_path == "/" || current_path.empty?
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -124,7 +124,10 @@ module Docyard
|
|
|
124
124
|
end
|
|
125
125
|
|
|
126
126
|
def valid_navigation_item?(item)
|
|
127
|
-
|
|
127
|
+
return false unless item[:path]
|
|
128
|
+
return false if external_link?(item[:path])
|
|
129
|
+
|
|
130
|
+
item[:type] == :file || (item[:type] == :directory && item[:has_index])
|
|
128
131
|
end
|
|
129
132
|
|
|
130
133
|
def build_link(item)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module Sidebar
|
|
5
|
+
class ChildrenDiscoverer
|
|
6
|
+
attr_reader :docs_path
|
|
7
|
+
|
|
8
|
+
def initialize(docs_path:)
|
|
9
|
+
@docs_path = docs_path
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def discover(relative_path, depth:, &item_builder)
|
|
13
|
+
full_path = File.join(docs_path, relative_path)
|
|
14
|
+
return [] unless File.directory?(full_path)
|
|
15
|
+
|
|
16
|
+
local_config = load_local_sidebar_config(full_path)
|
|
17
|
+
return yield(local_config, relative_path, depth) if local_config
|
|
18
|
+
|
|
19
|
+
discover_from_filesystem(full_path, relative_path, depth, &item_builder)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def load_local_sidebar_config(dir_path)
|
|
25
|
+
LocalConfigLoader.new(dir_path).load
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def discover_from_filesystem(full_path, relative_path, depth, &item_builder)
|
|
29
|
+
entries = filtered_entries(full_path)
|
|
30
|
+
entries.map { |entry| build_entry(entry, full_path, relative_path, depth, &item_builder) }.compact
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def filtered_entries(full_path)
|
|
34
|
+
Dir.children(full_path)
|
|
35
|
+
.reject { |e| e.start_with?(".") || e.start_with?("_") || e == "index.md" }
|
|
36
|
+
.sort
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def build_entry(entry, full_path, relative_path, depth)
|
|
40
|
+
entry_path = File.join(full_path, entry)
|
|
41
|
+
|
|
42
|
+
if File.directory?(entry_path)
|
|
43
|
+
yield(:directory, entry, relative_path, depth)
|
|
44
|
+
elsif entry.end_with?(".md")
|
|
45
|
+
slug = entry.delete_suffix(".md")
|
|
46
|
+
yield(:file, slug, relative_path, depth)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -2,178 +2,206 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "item"
|
|
4
4
|
require_relative "title_extractor"
|
|
5
|
+
require_relative "metadata_extractor"
|
|
6
|
+
require_relative "children_discoverer"
|
|
7
|
+
require_relative "file_resolver"
|
|
5
8
|
|
|
6
9
|
module Docyard
|
|
7
10
|
module Sidebar
|
|
8
|
-
class ConfigParser
|
|
9
|
-
attr_reader :config_items, :docs_path, :current_path, :
|
|
11
|
+
class ConfigParser # rubocop:disable Metrics/ClassLength
|
|
12
|
+
attr_reader :config_items, :docs_path, :current_path, :metadata_extractor,
|
|
13
|
+
:children_discoverer, :file_resolver
|
|
10
14
|
|
|
11
15
|
def initialize(config_items, docs_path:, current_path: "/", title_extractor: TitleExtractor.new)
|
|
12
16
|
@config_items = config_items || []
|
|
13
17
|
@docs_path = docs_path
|
|
14
18
|
@current_path = Utils::PathResolver.normalize(current_path)
|
|
15
|
-
@
|
|
19
|
+
@metadata_extractor = MetadataExtractor.new(docs_path: docs_path, title_extractor: title_extractor)
|
|
20
|
+
@children_discoverer = ChildrenDiscoverer.new(docs_path: docs_path)
|
|
21
|
+
@file_resolver = FileResolver.new(
|
|
22
|
+
docs_path: docs_path, current_path: @current_path, metadata_extractor: metadata_extractor
|
|
23
|
+
)
|
|
16
24
|
end
|
|
17
25
|
|
|
18
26
|
def parse
|
|
19
|
-
parse_items(config_items)
|
|
27
|
+
parse_items(config_items, "", depth: 1)
|
|
20
28
|
end
|
|
21
29
|
|
|
22
30
|
private
|
|
23
31
|
|
|
24
|
-
def parse_items(items, base_path
|
|
25
|
-
items.map
|
|
26
|
-
parse_item(item_config, base_path)
|
|
27
|
-
end.compact
|
|
32
|
+
def parse_items(items, base_path, depth:)
|
|
33
|
+
items.map { |item_config| parse_item(item_config, base_path, depth: depth) }.compact
|
|
28
34
|
end
|
|
29
35
|
|
|
30
|
-
def parse_item(item_config, base_path)
|
|
36
|
+
def parse_item(item_config, base_path, depth:)
|
|
31
37
|
case item_config
|
|
32
|
-
when String
|
|
33
|
-
|
|
34
|
-
when Hash
|
|
35
|
-
parse_hash_item(item_config, base_path)
|
|
38
|
+
when String then resolve_string_item(item_config, base_path, depth: depth)
|
|
39
|
+
when Hash then parse_hash_item(item_config, base_path, depth: depth)
|
|
36
40
|
end
|
|
37
41
|
end
|
|
38
42
|
|
|
39
|
-
def
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
options = item_config.values.first || {}
|
|
46
|
-
resolve_file_item(slug, base_path, options)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def link_item?(config)
|
|
50
|
-
config.key?("link") || config.key?(:link)
|
|
43
|
+
def resolve_string_item(slug, base_path, depth:)
|
|
44
|
+
if File.directory?(File.join(docs_path, base_path, slug))
|
|
45
|
+
build_directory_item(slug, {}, [], base_path, depth: depth)
|
|
46
|
+
else
|
|
47
|
+
file_resolver.resolve(slug, base_path)
|
|
48
|
+
end
|
|
51
49
|
end
|
|
52
50
|
|
|
53
|
-
def
|
|
54
|
-
|
|
55
|
-
end
|
|
51
|
+
def parse_hash_item(item_config, base_path, depth:)
|
|
52
|
+
return file_resolver.build_link_item(item_config) if external_link?(item_config)
|
|
56
53
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
end
|
|
54
|
+
slug = item_config.keys.first
|
|
55
|
+
options = item_config.values.first
|
|
60
56
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
text = config["text"] || config[:text]
|
|
64
|
-
icon = config["icon"] || config[:icon]
|
|
65
|
-
target = config["target"] || config[:target] || "_blank"
|
|
57
|
+
return file_resolver.resolve(slug, base_path, {}) if options.nil?
|
|
58
|
+
return parse_nested_item(slug, options, base_path, depth: depth) if options.is_a?(Hash)
|
|
66
59
|
|
|
67
|
-
|
|
68
|
-
text: text,
|
|
69
|
-
link: link,
|
|
70
|
-
path: link,
|
|
71
|
-
icon: icon,
|
|
72
|
-
target: target,
|
|
73
|
-
type: :external
|
|
74
|
-
)
|
|
60
|
+
file_resolver.resolve(slug, base_path, options)
|
|
75
61
|
end
|
|
76
62
|
|
|
77
|
-
def
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
nested_items = extract_nested_items(options)
|
|
63
|
+
def external_link?(config)
|
|
64
|
+
config.key?("link") || config.key?(:link)
|
|
65
|
+
end
|
|
81
66
|
|
|
67
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
68
|
+
def parse_nested_item(slug, options, base_path, depth:)
|
|
69
|
+
slug = slug.to_s
|
|
70
|
+
nested_items = options["items"] || options[:items] || []
|
|
82
71
|
dir_path = File.join(docs_path, base_path, slug)
|
|
72
|
+
is_virtual_group = (options["section"] == false || options[:section] == false) && nested_items.any?
|
|
83
73
|
|
|
84
|
-
if
|
|
85
|
-
|
|
74
|
+
if is_virtual_group
|
|
75
|
+
build_virtual_group_item(slug, options, nested_items, base_path, depth: depth)
|
|
76
|
+
elsif File.directory?(dir_path)
|
|
77
|
+
build_directory_item(slug, options, nested_items, base_path, depth: depth)
|
|
86
78
|
elsif nested_items.any?
|
|
87
|
-
build_file_with_children_item(slug, options, nested_items, base_path)
|
|
79
|
+
build_file_with_children_item(slug, options, nested_items, base_path, depth: depth)
|
|
88
80
|
else
|
|
89
|
-
|
|
81
|
+
file_resolver.resolve(slug, base_path, options)
|
|
90
82
|
end
|
|
91
83
|
end
|
|
84
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
92
85
|
|
|
93
|
-
def
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def extract_common_options(options)
|
|
98
|
-
{
|
|
99
|
-
text: options["text"] || options[:text],
|
|
100
|
-
icon: options["icon"] || options[:icon],
|
|
101
|
-
collapsed: options["collapsed"] || options[:collapsed] || false
|
|
102
|
-
}
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def build_directory_item(slug, options, nested_items, base_path)
|
|
106
|
-
common_opts = extract_common_options(options)
|
|
107
|
-
new_base_path = File.join(base_path, slug)
|
|
108
|
-
parsed_items = parse_items(nested_items, new_base_path)
|
|
86
|
+
def build_virtual_group_item(slug, options, nested_items, base_path, depth:)
|
|
87
|
+
common_opts = metadata_extractor.extract_common_options(options)
|
|
88
|
+
parsed_items = parse_items(nested_items, base_path, depth: depth + 1)
|
|
89
|
+
is_collapsed = common_opts[:collapsed] != false && !active_child?(parsed_items)
|
|
109
90
|
|
|
110
91
|
Item.new(
|
|
111
92
|
slug: slug,
|
|
112
93
|
text: common_opts[:text] || Utils::TextFormatter.titleize(slug),
|
|
94
|
+
path: nil,
|
|
113
95
|
icon: common_opts[:icon],
|
|
114
|
-
collapsed:
|
|
96
|
+
collapsed: is_collapsed,
|
|
97
|
+
active: false,
|
|
115
98
|
items: parsed_items,
|
|
116
|
-
type: :directory
|
|
99
|
+
type: :directory,
|
|
100
|
+
section: false
|
|
117
101
|
)
|
|
118
102
|
end
|
|
119
103
|
|
|
120
|
-
def
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
104
|
+
def build_directory_item(slug, options, nested_items, base_path, depth:)
|
|
105
|
+
context = build_directory_context(slug, options, nested_items, base_path, depth)
|
|
106
|
+
context[:parsed_items] = prepend_intro_if_needed(context, depth)
|
|
107
|
+
create_directory_item(slug, context, depth)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def build_directory_context(slug, options, nested_items, base_path, depth)
|
|
111
|
+
new_base_path = File.join(base_path, slug)
|
|
112
|
+
{
|
|
113
|
+
common_opts: metadata_extractor.extract_common_options(options),
|
|
114
|
+
parsed_items: resolve_directory_children(nested_items, new_base_path, depth),
|
|
115
|
+
**build_index_info(new_base_path)
|
|
116
|
+
}
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def build_index_info(base_path)
|
|
120
|
+
index_file_path = File.join(docs_path, base_path, "index.md")
|
|
121
|
+
has_index = File.file?(index_file_path)
|
|
122
|
+
url_path = has_index ? Utils::PathResolver.to_url(base_path) : nil
|
|
123
|
+
|
|
124
|
+
{ index_file_path: index_file_path, has_index: has_index,
|
|
125
|
+
url_path: url_path, is_active: has_index && current_path == url_path }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def resolve_directory_children(nested_items, base_path, depth)
|
|
129
|
+
return parse_items(nested_items, base_path, depth: depth + 1) if nested_items.any?
|
|
130
|
+
|
|
131
|
+
auto_discover_children(base_path, depth: depth + 1)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def prepend_intro_if_needed(context, depth)
|
|
135
|
+
is_section = section_for_depth?(context[:common_opts][:section], depth)
|
|
136
|
+
return context[:parsed_items] unless is_section && context[:has_index]
|
|
137
|
+
|
|
138
|
+
[build_introduction_item(context[:index_file_path], context[:url_path])] + context[:parsed_items]
|
|
139
|
+
end
|
|
125
140
|
|
|
141
|
+
def create_directory_item(slug, context, depth)
|
|
142
|
+
is_section = section_for_depth?(context[:common_opts][:section], depth)
|
|
126
143
|
Item.new(
|
|
127
144
|
slug: slug,
|
|
128
|
-
text:
|
|
129
|
-
path: url_path,
|
|
130
|
-
icon: common_opts[:icon],
|
|
131
|
-
collapsed:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
145
|
+
text: context[:common_opts][:text] || Utils::TextFormatter.titleize(slug),
|
|
146
|
+
path: is_section ? nil : context[:url_path],
|
|
147
|
+
icon: context[:common_opts][:icon],
|
|
148
|
+
collapsed: is_section ? false : directory_collapsed?(context),
|
|
149
|
+
active: is_section ? false : context[:is_active],
|
|
150
|
+
has_index: is_section ? false : context[:has_index],
|
|
151
|
+
items: context[:parsed_items],
|
|
152
|
+
type: :directory,
|
|
153
|
+
section: is_section
|
|
135
154
|
)
|
|
136
155
|
end
|
|
137
156
|
|
|
138
|
-
def
|
|
139
|
-
|
|
140
|
-
end
|
|
157
|
+
def section_for_depth?(explicit_section, depth)
|
|
158
|
+
return explicit_section unless explicit_section.nil?
|
|
141
159
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
options ||= {}
|
|
160
|
+
depth == 1
|
|
161
|
+
end
|
|
145
162
|
|
|
146
|
-
|
|
147
|
-
|
|
163
|
+
def directory_collapsed?(context)
|
|
164
|
+
return false if context[:is_active] || active_child?(context[:parsed_items])
|
|
148
165
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
icon = resolve_item_icon(options, frontmatter[:icon])
|
|
152
|
-
final_path = options["link"] || options[:link] || url_path
|
|
166
|
+
context[:common_opts][:collapsed] != false
|
|
167
|
+
end
|
|
153
168
|
|
|
169
|
+
def build_introduction_item(index_file_path, url_path)
|
|
170
|
+
metadata = metadata_extractor.extract_index_metadata(index_file_path)
|
|
154
171
|
Item.new(
|
|
155
|
-
slug:
|
|
156
|
-
active: current_path ==
|
|
172
|
+
slug: "index", text: metadata[:sidebar_text] || "Overview",
|
|
173
|
+
path: url_path, icon: metadata[:icon], active: current_path == url_path, type: :file
|
|
157
174
|
)
|
|
158
175
|
end
|
|
159
176
|
|
|
160
|
-
def
|
|
161
|
-
|
|
177
|
+
def build_file_with_children_item(slug, options, nested_items, base_path, depth:)
|
|
178
|
+
file_resolver.build_file_with_children(
|
|
179
|
+
slug: slug, options: options, base_path: base_path,
|
|
180
|
+
parsed_items: parse_items(nested_items, base_path, depth: depth + 1),
|
|
181
|
+
depth: depth
|
|
182
|
+
)
|
|
183
|
+
end
|
|
162
184
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
185
|
+
def auto_discover_children(relative_path, depth:)
|
|
186
|
+
children_discoverer.discover(relative_path, depth: depth) do |config_or_type, *args|
|
|
187
|
+
if config_or_type.is_a?(Array)
|
|
188
|
+
parse_items(config_or_type, args[0], depth: args[1])
|
|
189
|
+
else
|
|
190
|
+
build_discovered_item(config_or_type, args[0], args[1], depth)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
168
193
|
end
|
|
169
194
|
|
|
170
|
-
def
|
|
171
|
-
|
|
172
|
-
|
|
195
|
+
def build_discovered_item(type, slug, base_path, depth)
|
|
196
|
+
if type == :directory
|
|
197
|
+
build_directory_item(slug, {}, [], base_path, depth: depth)
|
|
198
|
+
else
|
|
199
|
+
file_resolver.resolve(slug, base_path)
|
|
200
|
+
end
|
|
173
201
|
end
|
|
174
202
|
|
|
175
|
-
def
|
|
176
|
-
|
|
203
|
+
def active_child?(items)
|
|
204
|
+
items.any? { |item| item.active || active_child?(item.items) }
|
|
177
205
|
end
|
|
178
206
|
end
|
|
179
207
|
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "item"
|
|
4
|
+
|
|
5
|
+
module Docyard
|
|
6
|
+
module Sidebar
|
|
7
|
+
class FileResolver
|
|
8
|
+
attr_reader :docs_path, :current_path, :metadata_extractor
|
|
9
|
+
|
|
10
|
+
def initialize(docs_path:, current_path:, metadata_extractor:)
|
|
11
|
+
@docs_path = docs_path
|
|
12
|
+
@current_path = current_path
|
|
13
|
+
@metadata_extractor = metadata_extractor
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def resolve(slug, base_path, options = {})
|
|
17
|
+
context = build_context(slug.to_s, base_path, options || {})
|
|
18
|
+
Item.new(**context)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def build_link_item(config)
|
|
22
|
+
Item.new(
|
|
23
|
+
text: config["text"] || config[:text],
|
|
24
|
+
link: config["link"] || config[:link],
|
|
25
|
+
path: config["link"] || config[:link],
|
|
26
|
+
icon: config["icon"] || config[:icon],
|
|
27
|
+
target: config["target"] || config[:target] || "_blank",
|
|
28
|
+
type: :external,
|
|
29
|
+
section: false
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def build_file_with_children(slug:, options:, base_path:, parsed_items:, depth: 1)
|
|
34
|
+
common_opts = metadata_extractor.extract_common_options(options)
|
|
35
|
+
file_path = File.join(docs_path, base_path, "#{slug}.md")
|
|
36
|
+
url_path = Utils::PathResolver.to_url(File.join(base_path, slug))
|
|
37
|
+
is_section = section_for_depth?(common_opts[:section], depth)
|
|
38
|
+
|
|
39
|
+
Item.new(
|
|
40
|
+
slug: slug,
|
|
41
|
+
text: common_opts[:text] || metadata_extractor.extract_file_title(file_path, slug),
|
|
42
|
+
path: is_section ? nil : url_path,
|
|
43
|
+
icon: common_opts[:icon],
|
|
44
|
+
collapsed: is_section ? false : common_opts[:collapsed],
|
|
45
|
+
items: parsed_items,
|
|
46
|
+
active: is_section ? false : current_path == url_path,
|
|
47
|
+
type: is_section ? :section : :file,
|
|
48
|
+
section: is_section
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def section_for_depth?(explicit_section, depth)
|
|
55
|
+
return explicit_section unless explicit_section.nil?
|
|
56
|
+
|
|
57
|
+
depth == 1
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def build_context(slug, base_path, options)
|
|
61
|
+
paths = resolve_paths(slug, base_path, options)
|
|
62
|
+
frontmatter = metadata_extractor.extract_frontmatter_metadata(paths[:file])
|
|
63
|
+
|
|
64
|
+
build_context_hash(slug, paths, options, frontmatter)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def resolve_paths(slug, base_path, options)
|
|
68
|
+
file_path = File.join(docs_path, base_path, "#{slug}.md")
|
|
69
|
+
url_path = Utils::PathResolver.to_url(File.join(base_path, slug))
|
|
70
|
+
final_path = options["link"] || options[:link] || url_path
|
|
71
|
+
|
|
72
|
+
{ file: file_path, final: final_path }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def build_context_hash(slug, paths, options, frontmatter)
|
|
76
|
+
{
|
|
77
|
+
slug: slug,
|
|
78
|
+
text: metadata_extractor.resolve_item_text(slug, paths[:file], options, frontmatter[:text]),
|
|
79
|
+
path: paths[:final],
|
|
80
|
+
icon: metadata_extractor.resolve_item_icon(options, frontmatter[:icon]),
|
|
81
|
+
badge: frontmatter[:badge],
|
|
82
|
+
badge_type: frontmatter[:badge_type],
|
|
83
|
+
active: current_path == paths[:final],
|
|
84
|
+
type: :file,
|
|
85
|
+
section: false
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -66,7 +66,8 @@ module Docyard
|
|
|
66
66
|
def hidden_or_ignored?(entry, relative_path)
|
|
67
67
|
entry.start_with?(".") ||
|
|
68
68
|
entry.start_with?("_") ||
|
|
69
|
-
(entry == "index.md" && relative_path.empty?)
|
|
69
|
+
(entry == "index.md" && relative_path.empty?) ||
|
|
70
|
+
(entry == "public" && relative_path.empty?)
|
|
70
71
|
end
|
|
71
72
|
|
|
72
73
|
def sort_key(entry)
|