docyard 0.8.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 +67 -1
- 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/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 +9 -3
- data/lib/docyard/components/processors/cards_processor.rb +100 -0
- 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 +34 -3
- data/lib/docyard/components/processors/code_block_processor.rb +11 -24
- data/lib/docyard/components/processors/code_group_processor.rb +182 -0
- data/lib/docyard/components/processors/code_snippet_import_preprocessor.rb +7 -1
- data/lib/docyard/components/processors/custom_anchor_processor.rb +42 -0
- data/lib/docyard/components/processors/file_tree_processor.rb +150 -0
- data/lib/docyard/components/processors/icon_processor.rb +8 -2
- 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 +207 -0
- 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 +118 -0
- data/lib/docyard/components/support/markdown_code_block_helper.rb +56 -0
- 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 +84 -58
- data/lib/docyard/config/key_validator.rb +30 -0
- data/lib/docyard/config/logo_detector.rb +39 -0
- 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 +45 -96
- 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/item.rb +6 -1
- data/lib/docyard/navigation/sidebar/local_config_loader.rb +69 -3
- data/lib/docyard/navigation/sidebar/renderer.rb +18 -3
- 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 +20 -15
- data/lib/docyard/rendering/og_helpers.rb +36 -0
- data/lib/docyard/rendering/renderer.rb +87 -58
- 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 +40 -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/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 +233 -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/cards.css +100 -0
- data/lib/docyard/templates/assets/css/components/code-block.css +14 -2
- data/lib/docyard/templates/assets/css/components/code-group.css +294 -0
- data/lib/docyard/templates/assets/css/components/feedback.css +126 -0
- data/lib/docyard/templates/assets/css/components/figure.css +22 -0
- data/lib/docyard/templates/assets/css/components/file-tree.css +125 -0
- data/lib/docyard/templates/assets/css/components/heading-anchor.css +21 -13
- data/lib/docyard/templates/assets/css/components/icon.css +5 -0
- data/lib/docyard/templates/assets/css/components/lightbox.css +65 -0
- data/lib/docyard/templates/assets/css/components/nav-menu.css +20 -4
- data/lib/docyard/templates/assets/css/components/navigation.css +32 -3
- data/lib/docyard/templates/assets/css/components/page-actions.css +131 -0
- data/lib/docyard/templates/assets/css/components/prev-next.css +20 -22
- data/lib/docyard/templates/assets/css/components/search.css +6 -10
- data/lib/docyard/templates/assets/css/components/steps.css +122 -0
- 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 +13 -5
- data/lib/docyard/templates/assets/css/components/theme-toggle.css +3 -1
- 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 +82 -13
- data/lib/docyard/templates/assets/css/layout.css +17 -0
- data/lib/docyard/templates/assets/css/markdown.css +25 -3
- data/lib/docyard/templates/assets/css/variables.css +13 -1
- 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-group.js +286 -0
- 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 +39 -0
- data/lib/docyard/templates/assets/js/components/lightbox.js +72 -0
- 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 +118 -0
- 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 +11 -0
- data/lib/docyard/templates/layouts/splash.html.erb +15 -1
- data/lib/docyard/templates/partials/_accordion.html.erb +9 -0
- data/lib/docyard/templates/partials/_analytics.html.erb +24 -0
- data/lib/docyard/templates/partials/_banner.html.erb +27 -0
- data/lib/docyard/templates/partials/_card.html.erb +23 -0
- 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/_nav_group.html.erb +6 -0
- data/lib/docyard/templates/partials/_nav_leaf.html.erb +3 -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/_step.html.erb +14 -0
- 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 +114 -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 -78
- data/lib/docyard/navigation/sidebar/file_system_scanner.rb +0 -78
- data/lib/docyard/navigation/sidebar/metadata_extractor.rb +0 -69
- data/lib/docyard/navigation/sidebar/metadata_reader.rb +0 -47
- 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 -139
- 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 -90
- 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
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
class Config
|
|
5
|
+
module Validators
|
|
6
|
+
module Navigation
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def validate_navigation_section
|
|
10
|
+
cta = @config.dig("navigation", "cta")
|
|
11
|
+
return if cta.nil?
|
|
12
|
+
return add_array_error("navigation.cta") unless cta.is_a?(Array)
|
|
13
|
+
|
|
14
|
+
validate_cta_max_count(cta)
|
|
15
|
+
validate_cta_items(cta)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def validate_cta_items(cta)
|
|
19
|
+
cta.each_with_index do |item, idx|
|
|
20
|
+
validate_string(item["text"], "navigation.cta[#{idx}].text")
|
|
21
|
+
validate_string(item["href"], "navigation.cta[#{idx}].href")
|
|
22
|
+
validate_cta_variant(item["variant"], idx) if item.key?("variant")
|
|
23
|
+
validate_boolean(item["external"], "navigation.cta[#{idx}].external") if item.key?("external")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def validate_cta_max_count(cta)
|
|
28
|
+
return if cta.length <= 2
|
|
29
|
+
|
|
30
|
+
add_error(field: "navigation.cta", error: "maximum 2 CTAs allowed",
|
|
31
|
+
got: "#{cta.length} items", fix: "Remove extra CTA items to have at most 2")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def validate_cta_variant(variant, idx)
|
|
35
|
+
return if variant.nil? || %w[primary secondary].include?(variant)
|
|
36
|
+
|
|
37
|
+
add_error(field: "navigation.cta[#{idx}].variant", error: "must be 'primary' or 'secondary'",
|
|
38
|
+
got: variant, fix: "Change to 'primary' or 'secondary'")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
class Config
|
|
5
|
+
module Validators
|
|
6
|
+
module Section
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def validate_top_level
|
|
10
|
+
validate_string(@config["title"], "title")
|
|
11
|
+
validate_string(@config["description"], "description")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def validate_branding_section
|
|
15
|
+
branding = @config["branding"]
|
|
16
|
+
return unless branding
|
|
17
|
+
|
|
18
|
+
validate_file_path_or_url(branding["logo"], "branding.logo")
|
|
19
|
+
validate_file_path_or_url(branding["favicon"], "branding.favicon")
|
|
20
|
+
validate_boolean(branding["credits"], "branding.credits") if branding.key?("credits")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def validate_socials_section
|
|
24
|
+
socials = @config["socials"]
|
|
25
|
+
return unless socials
|
|
26
|
+
return add_hash_error("socials") unless socials.is_a?(Hash)
|
|
27
|
+
|
|
28
|
+
socials.each { |platform, url| validate_url(url, "socials.#{platform}") unless platform == "custom" }
|
|
29
|
+
validate_custom_socials(socials["custom"]) if socials.key?("custom")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def validate_custom_socials(custom)
|
|
33
|
+
return if custom.nil?
|
|
34
|
+
return add_array_error("socials.custom") unless custom.is_a?(Array)
|
|
35
|
+
|
|
36
|
+
custom.each_with_index do |item, index|
|
|
37
|
+
validate_string(item["icon"], "socials.custom[#{index}].icon")
|
|
38
|
+
validate_url(item["href"], "socials.custom[#{index}].href")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def validate_tabs_section
|
|
43
|
+
tabs = @config["tabs"]
|
|
44
|
+
return unless tabs
|
|
45
|
+
return add_array_error("tabs") unless tabs.is_a?(Array)
|
|
46
|
+
|
|
47
|
+
tabs.each_with_index do |tab, index|
|
|
48
|
+
validate_string(tab["text"], "tabs[#{index}].text")
|
|
49
|
+
validate_string(tab["href"], "tabs[#{index}].href")
|
|
50
|
+
validate_boolean(tab["external"], "tabs[#{index}].external") if tab.key?("external")
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def validate_sidebar_setting
|
|
55
|
+
sidebar = @config["sidebar"]
|
|
56
|
+
return if sidebar.nil? || Config::SIDEBAR_MODES.include?(sidebar)
|
|
57
|
+
|
|
58
|
+
add_error(
|
|
59
|
+
field: "sidebar",
|
|
60
|
+
error: "must be one of: #{Config::SIDEBAR_MODES.join(', ')}",
|
|
61
|
+
got: sidebar.inspect,
|
|
62
|
+
fix: "Change to 'config', 'auto', or 'distributed'"
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def validate_build_section
|
|
67
|
+
build = @config["build"]
|
|
68
|
+
return unless build
|
|
69
|
+
|
|
70
|
+
validate_string(build["output"], "build.output")
|
|
71
|
+
validate_no_slashes(build["output"], "build.output")
|
|
72
|
+
validate_string(build["base"], "build.base")
|
|
73
|
+
validate_starts_with_slash(build["base"], "build.base")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def validate_search_section
|
|
77
|
+
search = @config["search"]
|
|
78
|
+
return unless search
|
|
79
|
+
|
|
80
|
+
validate_boolean(search["enabled"], "search.enabled") if search.key?("enabled")
|
|
81
|
+
validate_string(search["placeholder"], "search.placeholder") if search.key?("placeholder")
|
|
82
|
+
validate_array(search["exclude"], "search.exclude") if search.key?("exclude")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def validate_announcement_section
|
|
86
|
+
announcement = @config["announcement"]
|
|
87
|
+
return unless announcement.is_a?(Hash)
|
|
88
|
+
|
|
89
|
+
validate_string(announcement["text"], "announcement.text") if announcement.key?("text")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def validate_feedback_section
|
|
93
|
+
feedback = @config["feedback"]
|
|
94
|
+
return unless feedback.is_a?(Hash) && feedback["enabled"] == true
|
|
95
|
+
return if analytics_configured?
|
|
96
|
+
|
|
97
|
+
add_error(
|
|
98
|
+
field: "feedback.enabled",
|
|
99
|
+
error: "requires analytics to be configured",
|
|
100
|
+
got: "feedback enabled without analytics",
|
|
101
|
+
fix: "Configure analytics (google, plausible, fathom, or script) to collect feedback responses"
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def analytics_configured?
|
|
106
|
+
analytics = @config["analytics"]
|
|
107
|
+
return false unless analytics.is_a?(Hash)
|
|
108
|
+
|
|
109
|
+
analytics["google"] || analytics["plausible"] || analytics["fathom"] || analytics["script"]
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
data/lib/docyard/config.rb
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "yaml"
|
|
4
|
+
require_relative "config/section"
|
|
5
|
+
require_relative "config/schema"
|
|
4
6
|
require_relative "config/validator"
|
|
5
|
-
require_relative "
|
|
7
|
+
require_relative "constants"
|
|
8
|
+
require_relative "utils/hash_utils"
|
|
6
9
|
|
|
7
10
|
module Docyard
|
|
8
11
|
class Config
|
|
12
|
+
SIDEBAR_MODES = %w[config auto distributed].freeze
|
|
13
|
+
|
|
9
14
|
DEFAULT_CONFIG = {
|
|
10
15
|
"title" => Constants::DEFAULT_SITE_TITLE,
|
|
11
16
|
"description" => "",
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
},
|
|
17
|
+
"url" => nil,
|
|
18
|
+
"og_image" => nil,
|
|
19
|
+
"twitter" => nil,
|
|
20
|
+
"source" => "docs",
|
|
21
|
+
"branding" => { "logo" => nil, "favicon" => nil, "credits" => true, "copyright" => nil, "color" => nil },
|
|
18
22
|
"socials" => {},
|
|
19
23
|
"tabs" => [],
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
},
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
"navigation" => {
|
|
30
|
-
"cta" => [],
|
|
31
|
-
"breadcrumbs" => true
|
|
32
|
-
}
|
|
24
|
+
"sidebar" => "config",
|
|
25
|
+
"build" => { "output" => "dist", "base" => "/" },
|
|
26
|
+
"search" => { "enabled" => true, "placeholder" => "Search...", "exclude" => [] },
|
|
27
|
+
"navigation" => { "cta" => [], "breadcrumbs" => true },
|
|
28
|
+
"announcement" => nil,
|
|
29
|
+
"repo" => { "url" => nil, "branch" => "main", "edit_path" => nil, "edit_link" => true,
|
|
30
|
+
"last_updated" => true },
|
|
31
|
+
"analytics" => { "google" => nil, "plausible" => nil, "fathom" => nil, "script" => nil },
|
|
32
|
+
"feedback" => { "enabled" => false, "question" => "Was this page helpful?" }
|
|
33
33
|
}.freeze
|
|
34
34
|
|
|
35
35
|
attr_reader :data, :file_path
|
|
@@ -49,83 +49,50 @@ module Docyard
|
|
|
49
49
|
File.exist?(file_path)
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
def title
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
def title = data["title"]
|
|
53
|
+
def description = data["description"]
|
|
54
|
+
def url = data["url"]
|
|
55
|
+
def og_image = data["og_image"]
|
|
56
|
+
def twitter = data["twitter"]
|
|
57
|
+
def source = data["source"]
|
|
58
|
+
def public_dir = File.join(source, "public")
|
|
59
|
+
def socials = data["socials"]
|
|
60
|
+
def tabs = data["tabs"]
|
|
61
|
+
def sidebar = data["sidebar"]
|
|
55
62
|
|
|
56
|
-
def
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
def sidebar_config? = sidebar == "config"
|
|
64
|
+
def sidebar_auto? = sidebar == "auto"
|
|
65
|
+
def sidebar_distributed? = sidebar == "distributed"
|
|
59
66
|
|
|
60
|
-
def branding
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
def branding = @branding ||= Section.new(data["branding"])
|
|
68
|
+
def build = @build ||= Section.new(data["build"])
|
|
69
|
+
def search = @search ||= Section.new(data["search"])
|
|
70
|
+
def navigation = @navigation ||= Section.new(data["navigation"])
|
|
71
|
+
def repo = @repo ||= Section.new(data["repo"])
|
|
72
|
+
def analytics = @analytics ||= Section.new(data["analytics"])
|
|
73
|
+
def feedback = @feedback ||= Section.new(data["feedback"])
|
|
63
74
|
|
|
64
|
-
def
|
|
65
|
-
data["
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def tabs
|
|
69
|
-
data["tabs"]
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def build
|
|
73
|
-
@build ||= ConfigSection.new(data["build"])
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def search
|
|
77
|
-
@search ||= ConfigSection.new(data["search"])
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def navigation
|
|
81
|
-
@navigation ||= ConfigSection.new(data["navigation"])
|
|
75
|
+
def announcement
|
|
76
|
+
@announcement ||= data["announcement"] ? Section.new(data["announcement"]) : nil
|
|
82
77
|
end
|
|
83
78
|
|
|
84
79
|
private
|
|
85
80
|
|
|
86
81
|
def load_config_data
|
|
87
|
-
|
|
88
|
-
load_and_merge_config
|
|
89
|
-
else
|
|
90
|
-
deep_dup(DEFAULT_CONFIG)
|
|
91
|
-
end
|
|
82
|
+
file_exists? ? load_and_merge_config : Utils::HashUtils.deep_dup(DEFAULT_CONFIG)
|
|
92
83
|
end
|
|
93
84
|
|
|
94
85
|
def load_and_merge_config
|
|
95
86
|
yaml_content = YAML.load_file(file_path)
|
|
96
|
-
deep_merge(deep_dup(DEFAULT_CONFIG), yaml_content || {})
|
|
87
|
+
Utils::HashUtils.deep_merge(Utils::HashUtils.deep_dup(DEFAULT_CONFIG), yaml_content || {})
|
|
97
88
|
rescue Psych::SyntaxError => e
|
|
98
89
|
raise ConfigError, build_yaml_error_message(e)
|
|
99
90
|
rescue StandardError => e
|
|
100
91
|
raise ConfigError, "Error loading docyard.yml: #{e.message}"
|
|
101
92
|
end
|
|
102
93
|
|
|
103
|
-
def deep_merge(hash1, hash2)
|
|
104
|
-
hash1.merge(hash2) do |_key, v1, v2|
|
|
105
|
-
if v2.nil?
|
|
106
|
-
v1
|
|
107
|
-
elsif v1.is_a?(Hash) && v2.is_a?(Hash)
|
|
108
|
-
deep_merge(v1, v2)
|
|
109
|
-
else
|
|
110
|
-
v2
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def deep_dup(hash)
|
|
116
|
-
hash.transform_values do |value|
|
|
117
|
-
case value
|
|
118
|
-
when Hash then deep_dup(value)
|
|
119
|
-
when Array then value.map { |v| v.is_a?(Hash) ? deep_dup(v) : v }
|
|
120
|
-
else value
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
94
|
def build_yaml_error_message(error)
|
|
126
|
-
message = "Invalid YAML in docyard.yml:\n\n"
|
|
127
|
-
message += " #{error.message}\n\n"
|
|
128
|
-
message += "Fix: Check YAML syntax"
|
|
95
|
+
message = "Invalid YAML in docyard.yml:\n\n #{error.message}\n\nFix: Check YAML syntax"
|
|
129
96
|
message += " at line #{error.line}" if error.respond_to?(:line)
|
|
130
97
|
message
|
|
131
98
|
end
|
|
@@ -134,22 +101,4 @@ module Docyard
|
|
|
134
101
|
Validator.new(data).validate!
|
|
135
102
|
end
|
|
136
103
|
end
|
|
137
|
-
|
|
138
|
-
class ConfigSection
|
|
139
|
-
def initialize(data)
|
|
140
|
-
@data = data || {}
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def method_missing(method, *args)
|
|
144
|
-
return @data[method.to_s] if args.empty?
|
|
145
|
-
|
|
146
|
-
super
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def respond_to_missing?(method, include_private = false)
|
|
150
|
-
@data.key?(method.to_s) || super
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
class ConfigError < StandardError; end
|
|
155
104
|
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module Constants
|
|
5
|
+
CONTENT_TYPE_HTML = "text/html; charset=utf-8"
|
|
6
|
+
|
|
7
|
+
DOCYARD_ASSETS_PREFIX = "/_docyard/"
|
|
8
|
+
PAGEFIND_PREFIX = "/_docyard/pagefind/"
|
|
9
|
+
|
|
10
|
+
INDEX_FILE = "index"
|
|
11
|
+
MARKDOWN_EXTENSION = ".md"
|
|
12
|
+
|
|
13
|
+
STATUS_OK = 200
|
|
14
|
+
STATUS_REDIRECT = 302
|
|
15
|
+
STATUS_NOT_FOUND = 404
|
|
16
|
+
STATUS_INTERNAL_ERROR = 500
|
|
17
|
+
|
|
18
|
+
DEFAULT_SITE_TITLE = "Documentation"
|
|
19
|
+
DEFAULT_LOGO_PATH = "_docyard/logo.svg"
|
|
20
|
+
DEFAULT_LOGO_DARK_PATH = "_docyard/logo-dark.svg"
|
|
21
|
+
DEFAULT_FAVICON_PATH = "_docyard/favicon.svg"
|
|
22
|
+
|
|
23
|
+
SOCIAL_ICON_MAP = {
|
|
24
|
+
"github" => "github-logo",
|
|
25
|
+
"x" => "x-logo",
|
|
26
|
+
"twitter" => "x-logo",
|
|
27
|
+
"discord" => "discord-logo",
|
|
28
|
+
"slack" => "slack-logo",
|
|
29
|
+
"linkedin" => "linkedin-logo",
|
|
30
|
+
"youtube" => "youtube-logo",
|
|
31
|
+
"twitch" => "twitch-logo",
|
|
32
|
+
"instagram" => "instagram-logo",
|
|
33
|
+
"facebook" => "facebook-logo",
|
|
34
|
+
"tiktok" => "tiktok-logo",
|
|
35
|
+
"reddit" => "reddit-logo",
|
|
36
|
+
"mastodon" => "mastodon-logo",
|
|
37
|
+
"threads" => "threads-logo",
|
|
38
|
+
"pinterest" => "pinterest-logo",
|
|
39
|
+
"medium" => "medium-logo",
|
|
40
|
+
"gitlab" => "gitlab-logo",
|
|
41
|
+
"figma" => "figma-logo",
|
|
42
|
+
"dribbble" => "dribbble-logo",
|
|
43
|
+
"behance" => "behance-logo",
|
|
44
|
+
"codepen" => "codepen-logo",
|
|
45
|
+
"codesandbox" => "codesandbox-logo",
|
|
46
|
+
"notion" => "notion-logo",
|
|
47
|
+
"spotify" => "spotify-logo",
|
|
48
|
+
"soundcloud" => "soundcloud-logo",
|
|
49
|
+
"whatsapp" => "whatsapp-logo",
|
|
50
|
+
"telegram" => "telegram-logo",
|
|
51
|
+
"snapchat" => "snapchat-logo",
|
|
52
|
+
"patreon" => "patreon-logo",
|
|
53
|
+
"paypal" => "paypal-logo",
|
|
54
|
+
"stripe" => "stripe-logo",
|
|
55
|
+
"google-podcasts" => "google-podcasts-logo",
|
|
56
|
+
"apple-podcasts" => "apple-podcasts-logo"
|
|
57
|
+
}.freeze
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
module Docyard
|
|
4
4
|
class Error < StandardError; end
|
|
5
5
|
|
|
6
|
+
class ConfigError < Error; end
|
|
7
|
+
|
|
8
|
+
class SidebarConfigError < Error; end
|
|
9
|
+
|
|
6
10
|
class FileNotFoundError < Error
|
|
7
11
|
attr_reader :path
|
|
8
12
|
|
|
@@ -51,4 +55,6 @@ module Docyard
|
|
|
51
55
|
super("Asset not found: #{asset_path}")
|
|
52
56
|
end
|
|
53
57
|
end
|
|
58
|
+
|
|
59
|
+
class BuildError < Error; end
|
|
54
60
|
end
|
data/lib/docyard/initializer.rb
CHANGED
|
@@ -5,19 +5,21 @@ require "fileutils"
|
|
|
5
5
|
module Docyard
|
|
6
6
|
class Initializer
|
|
7
7
|
DOCS_DIR = "docs"
|
|
8
|
-
|
|
8
|
+
TEMPLATES_DIR = File.join(__dir__, "templates", "init")
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
attr_reader :project_name, :project_path, :docs_path, :force
|
|
11
|
+
|
|
12
|
+
def initialize(project_name = nil, force: false)
|
|
13
|
+
@project_name = project_name
|
|
14
|
+
@project_path = project_name ? File.join(".", project_name) : "."
|
|
15
|
+
@docs_path = File.join(@project_path, DOCS_DIR)
|
|
16
|
+
@force = force
|
|
13
17
|
end
|
|
14
18
|
|
|
15
|
-
def run
|
|
16
|
-
|
|
17
|
-
print_already_exists_error
|
|
18
|
-
return
|
|
19
|
-
end
|
|
19
|
+
def run # rubocop:disable Naming/PredicateMethod
|
|
20
|
+
return false unless check_existing_files
|
|
20
21
|
|
|
22
|
+
create_project_directory if project_name
|
|
21
23
|
create_structure
|
|
22
24
|
print_success
|
|
23
25
|
true
|
|
@@ -25,74 +27,123 @@ module Docyard
|
|
|
25
27
|
|
|
26
28
|
private
|
|
27
29
|
|
|
28
|
-
def
|
|
29
|
-
|
|
30
|
+
def check_existing_files # rubocop:disable Naming/PredicateMethod
|
|
31
|
+
return true if force
|
|
32
|
+
return true unless files_exist?
|
|
33
|
+
|
|
34
|
+
print_existing_files_warning
|
|
35
|
+
return true if user_confirms_overwrite?
|
|
36
|
+
|
|
37
|
+
print_abort_message
|
|
38
|
+
false
|
|
30
39
|
end
|
|
31
40
|
|
|
32
|
-
def
|
|
33
|
-
|
|
34
|
-
create_index_file
|
|
35
|
-
create_example_config
|
|
41
|
+
def files_exist?
|
|
42
|
+
File.exist?(docs_path) || File.exist?(config_path)
|
|
36
43
|
end
|
|
37
44
|
|
|
38
|
-
def
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
title: Welcome
|
|
43
|
-
---
|
|
45
|
+
def config_path
|
|
46
|
+
File.join(project_path, "docyard.yml")
|
|
47
|
+
end
|
|
44
48
|
|
|
45
|
-
|
|
49
|
+
def user_confirms_overwrite?
|
|
50
|
+
print "\nOverwrite existing files? [y/N] "
|
|
51
|
+
response = $stdin.gets&.strip&.downcase
|
|
52
|
+
%w[y yes].include?(response)
|
|
53
|
+
end
|
|
46
54
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
def print_existing_files_warning
|
|
56
|
+
puts ""
|
|
57
|
+
puts "\e[33mWarning:\e[0m Existing files found:"
|
|
58
|
+
puts " - #{docs_path}/" if File.exist?(docs_path)
|
|
59
|
+
puts " - #{config_path}" if File.exist?(config_path)
|
|
50
60
|
end
|
|
51
61
|
|
|
52
|
-
def
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
def print_abort_message
|
|
63
|
+
puts ""
|
|
64
|
+
puts "Aborted. Use \e[1m--force\e[0m to overwrite existing files."
|
|
65
|
+
end
|
|
55
66
|
|
|
56
|
-
|
|
57
|
-
|
|
67
|
+
def create_project_directory
|
|
68
|
+
FileUtils.mkdir_p(project_path)
|
|
69
|
+
end
|
|
58
70
|
|
|
59
|
-
|
|
71
|
+
def create_structure
|
|
72
|
+
FileUtils.mkdir_p(docs_path)
|
|
73
|
+
FileUtils.mkdir_p(File.join(docs_path, "public"))
|
|
74
|
+
create_config_file
|
|
75
|
+
create_sidebar_file
|
|
76
|
+
create_starter_pages
|
|
60
77
|
end
|
|
61
78
|
|
|
62
|
-
def
|
|
63
|
-
|
|
64
|
-
|
|
79
|
+
def create_config_file
|
|
80
|
+
template = File.read(File.join(TEMPLATES_DIR, "docyard.yml"))
|
|
81
|
+
content = template.gsub("{{PROJECT_NAME}}", display_name)
|
|
82
|
+
File.write(config_path, content)
|
|
65
83
|
end
|
|
66
84
|
|
|
67
|
-
def
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
85
|
+
def create_sidebar_file
|
|
86
|
+
template = File.read(File.join(TEMPLATES_DIR, "_sidebar.yml"))
|
|
87
|
+
File.write(File.join(docs_path, "_sidebar.yml"), template)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def create_starter_pages
|
|
91
|
+
pages_dir = File.join(TEMPLATES_DIR, "pages")
|
|
92
|
+
Dir.glob(File.join(pages_dir, "*.md")).each do |template_path|
|
|
93
|
+
filename = File.basename(template_path)
|
|
94
|
+
content = File.read(template_path).gsub("{{PROJECT_NAME}}", display_name)
|
|
95
|
+
File.write(File.join(docs_path, filename), content)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def display_name
|
|
100
|
+
return "My Documentation" unless project_name
|
|
101
|
+
|
|
102
|
+
project_name.split(/[-_]/).map(&:capitalize).join(" ")
|
|
71
103
|
end
|
|
72
104
|
|
|
73
|
-
def
|
|
105
|
+
def print_success
|
|
74
106
|
puts ""
|
|
75
|
-
puts "Docyard initialized successfully"
|
|
107
|
+
puts "\e[32m#{success_icon} Docyard project initialized successfully!\e[0m"
|
|
76
108
|
puts ""
|
|
109
|
+
print_created_structure
|
|
110
|
+
print_next_steps
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def success_icon
|
|
114
|
+
"\u2714"
|
|
77
115
|
end
|
|
78
116
|
|
|
79
|
-
def
|
|
80
|
-
puts "Created
|
|
117
|
+
def print_created_structure
|
|
118
|
+
puts "Created:"
|
|
81
119
|
puts ""
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
120
|
+
if project_name
|
|
121
|
+
puts " \e[1m#{project_name}/\e[0m"
|
|
122
|
+
puts " \u251C\u2500\u2500 docyard.yml"
|
|
123
|
+
puts " \u2514\u2500\u2500 docs/"
|
|
124
|
+
else
|
|
125
|
+
puts " \e[1mdocyard.yml\e[0m"
|
|
126
|
+
puts " \e[1mdocs/\e[0m"
|
|
127
|
+
end
|
|
128
|
+
puts " \u251C\u2500\u2500 _sidebar.yml"
|
|
129
|
+
puts " \u251C\u2500\u2500 index.md"
|
|
130
|
+
puts " \u251C\u2500\u2500 getting-started.md"
|
|
131
|
+
puts " \u251C\u2500\u2500 components.md"
|
|
132
|
+
puts " \u2514\u2500\u2500 public/"
|
|
85
133
|
puts ""
|
|
86
134
|
end
|
|
87
135
|
|
|
88
136
|
def print_next_steps
|
|
89
137
|
puts "Next steps:"
|
|
90
138
|
puts ""
|
|
91
|
-
|
|
92
|
-
|
|
139
|
+
if project_name
|
|
140
|
+
puts " \e[1mcd #{project_name}\e[0m"
|
|
141
|
+
puts ""
|
|
142
|
+
end
|
|
143
|
+
puts " Start the development server:"
|
|
144
|
+
puts " \e[1m$ docyard serve\e[0m"
|
|
93
145
|
puts ""
|
|
94
|
-
puts "
|
|
95
|
-
puts " docyard build"
|
|
146
|
+
puts " Then open \e[4mhttp://localhost:4200\e[0m in your browser"
|
|
96
147
|
puts ""
|
|
97
148
|
end
|
|
98
149
|
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "sidebar_builder"
|
|
4
|
+
require_relative "prev_next_builder"
|
|
5
|
+
require_relative "breadcrumb_builder"
|
|
6
|
+
|
|
7
|
+
module Docyard
|
|
8
|
+
module Navigation
|
|
9
|
+
class PageNavigationBuilder
|
|
10
|
+
def initialize(docs_path:, config:, sidebar_cache: nil)
|
|
11
|
+
@docs_path = docs_path
|
|
12
|
+
@config = config
|
|
13
|
+
@sidebar_cache = sidebar_cache
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def build(current_path:, markdown:, header_ctas: [], show_sidebar: true)
|
|
17
|
+
return empty_navigation unless show_sidebar
|
|
18
|
+
|
|
19
|
+
sidebar_builder = build_sidebar(current_path, header_ctas)
|
|
20
|
+
{
|
|
21
|
+
sidebar_html: sidebar_builder.to_html,
|
|
22
|
+
prev_next_html: build_prev_next(sidebar_builder, current_path, markdown),
|
|
23
|
+
breadcrumbs: build_breadcrumbs(sidebar_builder.tree, current_path)
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
attr_reader :docs_path, :config, :sidebar_cache
|
|
30
|
+
|
|
31
|
+
def empty_navigation
|
|
32
|
+
{ sidebar_html: "", prev_next_html: "", breadcrumbs: nil }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def build_sidebar(current_path, header_ctas)
|
|
36
|
+
SidebarBuilder.new(
|
|
37
|
+
docs_path: docs_path,
|
|
38
|
+
current_path: current_path,
|
|
39
|
+
config: config,
|
|
40
|
+
header_ctas: header_ctas,
|
|
41
|
+
sidebar_cache: sidebar_cache
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def build_prev_next(sidebar_builder, current_path, markdown)
|
|
46
|
+
PrevNextBuilder.new(
|
|
47
|
+
sidebar_tree: sidebar_builder.tree,
|
|
48
|
+
current_path: current_path,
|
|
49
|
+
frontmatter: markdown.frontmatter,
|
|
50
|
+
config: {}
|
|
51
|
+
).to_html
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def build_breadcrumbs(sidebar_tree, current_path)
|
|
55
|
+
return nil unless breadcrumbs_enabled?
|
|
56
|
+
|
|
57
|
+
BreadcrumbBuilder.new(sidebar_tree: sidebar_tree, current_path: current_path)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def breadcrumbs_enabled?
|
|
61
|
+
config&.navigation&.breadcrumbs != false
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|