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
|
@@ -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
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "item"
|
|
4
|
+
|
|
5
|
+
module Docyard
|
|
6
|
+
module Sidebar
|
|
7
|
+
class AutoBuilder
|
|
8
|
+
attr_reader :docs_path, :current_path
|
|
9
|
+
|
|
10
|
+
def initialize(docs_path, current_path: "/")
|
|
11
|
+
@docs_path = docs_path
|
|
12
|
+
@current_path = Utils::PathResolver.normalize(current_path)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def build
|
|
16
|
+
return [] unless File.directory?(docs_path)
|
|
17
|
+
|
|
18
|
+
scan_directory("").map(&:to_h)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def scan_directory(relative_path, depth: 1)
|
|
24
|
+
full_path = File.join(docs_path, relative_path)
|
|
25
|
+
return [] unless File.directory?(full_path)
|
|
26
|
+
|
|
27
|
+
entries = sorted_entries(full_path, relative_path)
|
|
28
|
+
entries.map { |entry| build_item(entry, relative_path, depth) }.compact
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def sorted_entries(full_path, relative_path)
|
|
32
|
+
Dir.children(full_path)
|
|
33
|
+
.reject { |entry| ignored_entry?(entry, relative_path) }
|
|
34
|
+
.sort_by(&:downcase)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def build_item(entry, relative_path, depth)
|
|
38
|
+
entry_relative_path = build_relative_path(relative_path, entry)
|
|
39
|
+
entry_full_path = File.join(docs_path, entry_relative_path)
|
|
40
|
+
|
|
41
|
+
if File.directory?(entry_full_path)
|
|
42
|
+
build_directory_item(entry, entry_relative_path, depth)
|
|
43
|
+
elsif entry.end_with?(".md")
|
|
44
|
+
build_file_item(entry, entry_relative_path)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def build_relative_path(relative_path, entry)
|
|
49
|
+
relative_path.empty? ? entry : File.join(relative_path, entry)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def build_directory_item(name, relative_path, depth)
|
|
53
|
+
children = scan_directory(relative_path, depth: depth + 1)
|
|
54
|
+
return nil if children.empty?
|
|
55
|
+
|
|
56
|
+
url_path = "/#{relative_path}"
|
|
57
|
+
has_index = File.file?(File.join(docs_path, relative_path, "index.md"))
|
|
58
|
+
|
|
59
|
+
Item.new(
|
|
60
|
+
slug: name,
|
|
61
|
+
text: Utils::TextFormatter.titleize(name),
|
|
62
|
+
path: has_index ? url_path : nil,
|
|
63
|
+
type: :directory,
|
|
64
|
+
section: depth == 1,
|
|
65
|
+
collapsed: depth > 1 && !child_active?(children),
|
|
66
|
+
has_index: has_index,
|
|
67
|
+
active: has_index && current_path == url_path,
|
|
68
|
+
items: children
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def build_file_item(filename, relative_path)
|
|
73
|
+
slug = filename.delete_suffix(".md")
|
|
74
|
+
url_path = "/#{relative_path.delete_suffix('.md')}"
|
|
75
|
+
|
|
76
|
+
Item.new(
|
|
77
|
+
slug: slug,
|
|
78
|
+
text: Utils::TextFormatter.titleize(slug),
|
|
79
|
+
path: url_path,
|
|
80
|
+
type: :file,
|
|
81
|
+
section: false,
|
|
82
|
+
active: current_path == url_path,
|
|
83
|
+
items: []
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def ignored_entry?(entry, relative_path)
|
|
88
|
+
entry.start_with?(".") ||
|
|
89
|
+
entry.start_with?("_") ||
|
|
90
|
+
root_index?(entry, relative_path) ||
|
|
91
|
+
public_folder?(entry, relative_path)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def root_index?(entry, relative_path)
|
|
95
|
+
entry == "index.md" && relative_path.empty?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def public_folder?(entry, relative_path)
|
|
99
|
+
entry == "public" && relative_path.empty?
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def child_active?(children)
|
|
103
|
+
children.any? { |child| child.active || child_active?(child.items) }
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "config_builder"
|
|
4
|
+
require_relative "auto_builder"
|
|
5
|
+
require_relative "distributed_builder"
|
|
6
|
+
require_relative "local_config_loader"
|
|
7
|
+
|
|
8
|
+
module Docyard
|
|
9
|
+
module Sidebar
|
|
10
|
+
class Cache
|
|
11
|
+
attr_reader :docs_path, :config, :tree, :built_at
|
|
12
|
+
|
|
13
|
+
def initialize(docs_path:, config:)
|
|
14
|
+
@docs_path = docs_path
|
|
15
|
+
@config = config
|
|
16
|
+
@tree = nil
|
|
17
|
+
@built_at = nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def build
|
|
21
|
+
@tree = build_tree
|
|
22
|
+
@built_at = Time.now
|
|
23
|
+
@tree
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def get(current_path: "/")
|
|
27
|
+
return nil unless @tree
|
|
28
|
+
|
|
29
|
+
mark_active_items(@tree, current_path)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def invalidate
|
|
33
|
+
@tree = nil
|
|
34
|
+
@built_at = nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def valid?
|
|
38
|
+
!@tree.nil?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def build_tree
|
|
44
|
+
case config.sidebar
|
|
45
|
+
when "auto"
|
|
46
|
+
AutoBuilder.new(docs_path, current_path: "/").build
|
|
47
|
+
when "distributed"
|
|
48
|
+
DistributedBuilder.new(docs_path, current_path: "/").build
|
|
49
|
+
else
|
|
50
|
+
build_config_tree
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def build_config_tree
|
|
55
|
+
config_items = LocalConfigLoader.new(docs_path).load
|
|
56
|
+
return [] unless config_items
|
|
57
|
+
|
|
58
|
+
ConfigBuilder.new(config_items, current_path: "/").build
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def mark_active_items(items, current_path)
|
|
62
|
+
deep_copy_with_active(items, current_path)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def deep_copy_with_active(items, current_path)
|
|
66
|
+
items.map do |item|
|
|
67
|
+
copied = item.dup
|
|
68
|
+
copied[:active] = path_matches?(copied[:path], current_path)
|
|
69
|
+
copied[:children] = deep_copy_with_active(item[:children] || [], current_path)
|
|
70
|
+
copied[:collapsed] = determine_collapsed_for_copy(copied)
|
|
71
|
+
copied
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def path_matches?(item_path, current_path)
|
|
76
|
+
return false if item_path.nil?
|
|
77
|
+
|
|
78
|
+
normalized_item = Utils::PathResolver.normalize(item_path)
|
|
79
|
+
normalized_current = Utils::PathResolver.normalize(current_path)
|
|
80
|
+
normalized_item == normalized_current
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def determine_collapsed_for_copy(item)
|
|
84
|
+
return false if item[:section]
|
|
85
|
+
return false if item[:active]
|
|
86
|
+
return false if child_active?(item[:children] || [])
|
|
87
|
+
|
|
88
|
+
item[:collapsed]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def child_active?(children)
|
|
92
|
+
children.any? { |child| child[:active] || child_active?(child[:children] || []) }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|