docyard 0.9.0 → 1.0.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/CHANGELOG.md +43 -0
- data/README.md +8 -253
- data/exe/docyard +6 -0
- data/lib/docyard/build/asset_bundler.rb +2 -2
- data/lib/docyard/build/file_copier.rb +12 -5
- data/lib/docyard/build/llms_txt_generator.rb +103 -0
- data/lib/docyard/build/sitemap_generator.rb +1 -1
- data/lib/docyard/build/static_generator.rb +115 -79
- data/lib/docyard/builder.rb +6 -2
- data/lib/docyard/cli.rb +14 -4
- data/lib/docyard/components/processors/callout_processor.rb +1 -1
- data/lib/docyard/components/processors/code_block_extended_fence_postprocessor.rb +24 -0
- data/lib/docyard/components/processors/code_block_extended_fence_preprocessor.rb +44 -0
- data/lib/docyard/components/processors/code_block_options_preprocessor.rb +11 -1
- data/lib/docyard/components/processors/code_block_processor.rb +5 -24
- data/lib/docyard/components/processors/code_group_processor.rb +6 -22
- data/lib/docyard/components/processors/code_snippet_import_preprocessor.rb +1 -0
- data/lib/docyard/components/processors/file_tree_processor.rb +1 -2
- data/lib/docyard/components/processors/icon_processor.rb +8 -2
- data/lib/docyard/components/processors/include_processor.rb +10 -10
- data/lib/docyard/components/processors/video_embed_processor.rb +14 -3
- data/lib/docyard/components/support/code_block/feature_extractor.rb +3 -1
- data/lib/docyard/components/support/code_block/icon_detector.rb +5 -12
- data/lib/docyard/components/support/code_block/line_number_resolver.rb +30 -0
- data/lib/docyard/components/support/code_detector.rb +2 -12
- data/lib/docyard/components/support/code_group/html_builder.rb +2 -6
- data/lib/docyard/components/support/tabs/icon_detector.rb +6 -2
- data/lib/docyard/components/support/tabs/parser.rb +6 -23
- data/lib/docyard/config/analytics_resolver.rb +24 -0
- data/lib/docyard/config/branding_resolver.rb +58 -27
- data/lib/docyard/config/key_validator.rb +30 -0
- data/lib/docyard/config/logo_detector.rb +8 -8
- data/lib/docyard/config/schema.rb +39 -0
- data/lib/docyard/config/section.rb +21 -0
- data/lib/docyard/config/validation_helpers.rb +83 -0
- data/lib/docyard/config/validator.rb +45 -144
- data/lib/docyard/config/validators/navigation.rb +43 -0
- data/lib/docyard/config/validators/section.rb +114 -0
- data/lib/docyard/config.rb +46 -102
- data/lib/docyard/constants.rb +59 -0
- data/lib/docyard/{utils/errors.rb → errors.rb} +6 -0
- data/lib/docyard/initializer.rb +100 -49
- data/lib/docyard/navigation/page_navigation_builder.rb +65 -0
- data/lib/docyard/navigation/sidebar/auto_builder.rb +107 -0
- data/lib/docyard/navigation/sidebar/cache.rb +96 -0
- data/lib/docyard/navigation/sidebar/config_builder.rb +179 -0
- data/lib/docyard/navigation/sidebar/distributed_builder.rb +145 -0
- data/lib/docyard/navigation/sidebar/local_config_loader.rb +69 -3
- data/lib/docyard/navigation/sidebar/renderer.rb +12 -1
- data/lib/docyard/navigation/sidebar_builder.rb +43 -81
- data/lib/docyard/rendering/branding_variables.rb +65 -0
- data/lib/docyard/rendering/icon_helpers.rb +14 -1
- data/lib/docyard/rendering/icons/devicons.rb +63 -0
- data/lib/docyard/rendering/icons.rb +26 -27
- data/lib/docyard/rendering/markdown.rb +5 -23
- data/lib/docyard/rendering/og_helpers.rb +36 -0
- data/lib/docyard/rendering/renderer.rb +87 -59
- data/lib/docyard/rendering/template_resolver.rb +14 -0
- data/lib/docyard/routing/fallback_resolver.rb +3 -3
- data/lib/docyard/search/build_indexer.rb +2 -2
- data/lib/docyard/search/dev_indexer.rb +36 -28
- data/lib/docyard/search/pagefind_support.rb +1 -1
- data/lib/docyard/server/asset_handler.rb +39 -15
- data/lib/docyard/server/dev_server.rb +90 -55
- data/lib/docyard/server/file_watcher.rb +68 -18
- data/lib/docyard/server/pagefind_handler.rb +1 -1
- data/lib/docyard/server/preview_server.rb +29 -33
- data/lib/docyard/server/rack_application.rb +38 -70
- data/lib/docyard/server/router.rb +11 -7
- data/lib/docyard/server/sse_server.rb +157 -0
- data/lib/docyard/server/static_file_app.rb +42 -0
- data/lib/docyard/templates/assets/css/components/banner.css +31 -0
- data/lib/docyard/templates/assets/css/components/breadcrumbs.css +2 -1
- data/lib/docyard/templates/assets/css/components/callout.css +26 -6
- data/lib/docyard/templates/assets/css/components/code-block.css +4 -2
- data/lib/docyard/templates/assets/css/components/code-group.css +20 -7
- data/lib/docyard/templates/assets/css/components/feedback.css +126 -0
- data/lib/docyard/templates/assets/css/components/file-tree.css +5 -4
- data/lib/docyard/templates/assets/css/components/icon.css +5 -0
- data/lib/docyard/templates/assets/css/components/nav-menu.css +20 -4
- data/lib/docyard/templates/assets/css/components/navigation.css +25 -3
- data/lib/docyard/templates/assets/css/components/page-actions.css +131 -0
- data/lib/docyard/templates/assets/css/components/prev-next.css +14 -7
- data/lib/docyard/templates/assets/css/components/search.css +6 -10
- data/lib/docyard/templates/assets/css/components/tab-bar.css +7 -4
- data/lib/docyard/templates/assets/css/components/table-of-contents.css +57 -11
- data/lib/docyard/templates/assets/css/components/tabs.css +12 -4
- data/lib/docyard/templates/assets/css/components/theme-toggle.css +3 -1
- data/lib/docyard/templates/assets/css/landing.css +82 -13
- data/lib/docyard/templates/assets/css/layout.css +17 -0
- data/lib/docyard/templates/assets/css/markdown.css +22 -2
- data/lib/docyard/templates/assets/css/variables.css +13 -1
- data/lib/docyard/templates/assets/js/components/code-group.js +4 -1
- data/lib/docyard/templates/assets/js/components/copy-page.js +115 -0
- data/lib/docyard/templates/assets/js/components/feedback.js +66 -0
- data/lib/docyard/templates/assets/js/components/file-tree.js +5 -5
- data/lib/docyard/templates/assets/js/components/navigation.js +3 -3
- data/lib/docyard/templates/assets/js/components/search.js +3 -3
- data/lib/docyard/templates/assets/js/components/table-of-contents.js +12 -6
- data/lib/docyard/templates/assets/js/components/tabs.js +45 -22
- data/lib/docyard/templates/assets/js/components/tooltip.js +4 -4
- data/lib/docyard/templates/assets/js/hot-reload.js +44 -0
- data/lib/docyard/templates/errors/404.html.erb +114 -5
- data/lib/docyard/templates/errors/500.html.erb +173 -10
- data/lib/docyard/templates/init/_sidebar.yml +36 -0
- data/lib/docyard/templates/init/docyard.yml +36 -0
- data/lib/docyard/templates/init/pages/components.md +146 -0
- data/lib/docyard/templates/init/pages/getting-started.md +94 -0
- data/lib/docyard/templates/init/pages/index.md +22 -0
- data/lib/docyard/templates/layouts/default.html.erb +10 -0
- data/lib/docyard/templates/layouts/splash.html.erb +14 -1
- data/lib/docyard/templates/partials/_analytics.html.erb +24 -0
- data/lib/docyard/templates/partials/_banner.html.erb +1 -1
- data/lib/docyard/templates/partials/_code_block.html.erb +1 -1
- data/lib/docyard/templates/partials/_feedback.html.erb +14 -0
- data/lib/docyard/templates/partials/_footer.html.erb +1 -1
- data/lib/docyard/templates/partials/_head.html.erb +79 -4
- data/lib/docyard/templates/partials/_icon_library.html.erb +8 -0
- data/lib/docyard/templates/partials/_page_actions.html.erb +21 -0
- data/lib/docyard/templates/partials/_scripts.html.erb +6 -3
- data/lib/docyard/templates/partials/_tabs.html.erb +4 -1
- data/lib/docyard/utils/git_info.rb +157 -0
- data/lib/docyard/utils/hash_utils.rb +31 -0
- data/lib/docyard/utils/html_helpers.rb +8 -0
- data/lib/docyard/utils/logging.rb +44 -3
- data/lib/docyard/utils/path_resolver.rb +0 -10
- data/lib/docyard/utils/path_utils.rb +73 -0
- data/lib/docyard/version.rb +1 -1
- data/lib/docyard.rb +2 -2
- metadata +77 -47
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -31
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
- data/.github/pull_request_template.md +0 -14
- data/.github/workflows/ci.yml +0 -49
- data/.rubocop.yml +0 -42
- data/CODE_OF_CONDUCT.md +0 -132
- data/CONTRIBUTING.md +0 -55
- data/LICENSE.vscode-icons +0 -42
- data/Rakefile +0 -8
- data/lib/docyard/config/constants.rb +0 -31
- data/lib/docyard/navigation/sidebar/children_discoverer.rb +0 -51
- data/lib/docyard/navigation/sidebar/config_parser.rb +0 -208
- data/lib/docyard/navigation/sidebar/file_resolver.rb +0 -90
- data/lib/docyard/navigation/sidebar/file_system_scanner.rb +0 -78
- data/lib/docyard/navigation/sidebar/metadata_extractor.rb +0 -71
- data/lib/docyard/navigation/sidebar/metadata_reader.rb +0 -51
- data/lib/docyard/navigation/sidebar/path_prefixer.rb +0 -34
- data/lib/docyard/navigation/sidebar/sorter.rb +0 -21
- data/lib/docyard/navigation/sidebar/title_extractor.rb +0 -25
- data/lib/docyard/navigation/sidebar/tree_builder.rb +0 -140
- data/lib/docyard/rendering/icons/LICENSE.phosphor +0 -21
- data/lib/docyard/rendering/icons/file_types.rb +0 -79
- data/lib/docyard/rendering/icons/phosphor.rb +0 -93
- data/lib/docyard/rendering/language_mapping.rb +0 -52
- data/lib/docyard/templates/assets/js/reload.js +0 -98
- data/lib/docyard/templates/partials/_icon.html.erb +0 -1
- data/lib/docyard/templates/partials/_icon_file_extension.html.erb +0 -1
- data/sig/docyard.rbs +0 -4
|
@@ -1,90 +0,0 @@
|
|
|
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
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Docyard
|
|
4
|
-
module Sidebar
|
|
5
|
-
class FileSystemScanner
|
|
6
|
-
attr_reader :docs_path
|
|
7
|
-
|
|
8
|
-
def initialize(docs_path)
|
|
9
|
-
@docs_path = docs_path
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def scan
|
|
13
|
-
return [] unless File.directory?(docs_path)
|
|
14
|
-
|
|
15
|
-
scan_directory(docs_path, "")
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
private
|
|
19
|
-
|
|
20
|
-
def scan_directory(base_path, relative_path)
|
|
21
|
-
full_path = File.join(base_path, relative_path)
|
|
22
|
-
return [] unless File.directory?(full_path)
|
|
23
|
-
|
|
24
|
-
entries = sorted_entries(full_path, relative_path)
|
|
25
|
-
entries.map { |entry| build_item(entry, base_path, relative_path, full_path) }.compact
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def sorted_entries(full_path, relative_path)
|
|
29
|
-
Dir.children(full_path)
|
|
30
|
-
.reject { |entry| hidden_or_ignored?(entry, relative_path) }
|
|
31
|
-
.sort_by { |entry| sort_key(entry) }
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def build_item(entry, base_path, relative_path, full_path)
|
|
35
|
-
entry_full_path = File.join(full_path, entry)
|
|
36
|
-
entry_relative_path = build_relative_path(relative_path, entry)
|
|
37
|
-
|
|
38
|
-
if File.directory?(entry_full_path)
|
|
39
|
-
build_directory_item(entry, entry_relative_path, base_path)
|
|
40
|
-
elsif entry.end_with?(".md")
|
|
41
|
-
build_file_item(entry, entry_relative_path)
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def build_relative_path(relative_path, entry)
|
|
46
|
-
relative_path.empty? ? entry : File.join(relative_path, entry)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def build_directory_item(entry, entry_relative_path, base_path)
|
|
50
|
-
{
|
|
51
|
-
type: :directory,
|
|
52
|
-
name: entry,
|
|
53
|
-
path: entry_relative_path,
|
|
54
|
-
children: scan_directory(base_path, entry_relative_path)
|
|
55
|
-
}
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def build_file_item(entry, entry_relative_path)
|
|
59
|
-
{
|
|
60
|
-
type: :file,
|
|
61
|
-
name: entry.delete_suffix(".md"),
|
|
62
|
-
path: entry_relative_path
|
|
63
|
-
}
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def hidden_or_ignored?(entry, relative_path)
|
|
67
|
-
entry.start_with?(".") ||
|
|
68
|
-
entry.start_with?("_") ||
|
|
69
|
-
(entry == "index.md" && relative_path.empty?) ||
|
|
70
|
-
(entry == "public" && relative_path.empty?)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def sort_key(entry)
|
|
74
|
-
entry.downcase
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
@@ -1,71 +0,0 @@
|
|
|
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
|
|
@@ -1,51 +0,0 @@
|
|
|
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
|
|
@@ -1,34 +0,0 @@
|
|
|
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,21 +0,0 @@
|
|
|
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,25 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Docyard
|
|
4
|
-
module Sidebar
|
|
5
|
-
class TitleExtractor
|
|
6
|
-
def extract(file_path)
|
|
7
|
-
return titleize_filename(file_path) unless File.file?(file_path)
|
|
8
|
-
|
|
9
|
-
content = File.read(file_path)
|
|
10
|
-
markdown = Markdown.new(content)
|
|
11
|
-
markdown.title || titleize_filename(file_path)
|
|
12
|
-
rescue StandardError => e
|
|
13
|
-
Docyard.logger.warn "Failed to extract title from #{file_path}: #{e.message}"
|
|
14
|
-
titleize_filename(file_path)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
private
|
|
18
|
-
|
|
19
|
-
def titleize_filename(file_path)
|
|
20
|
-
filename = File.basename(file_path, Constants::MARKDOWN_EXTENSION)
|
|
21
|
-
Utils::TextFormatter.titleize(filename)
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
@@ -1,140 +0,0 @@
|
|
|
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], badge: metadata[:badge],
|
|
136
|
-
badge_type: metadata[:badge_type], children: [] }
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
end
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2020-2023 Phosphor Icons
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|