docyard 0.9.0 → 1.0.1
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 +57 -1
- data/README.md +8 -253
- data/exe/docyard +6 -0
- data/lib/docyard/build/asset_bundler.rb +24 -2
- data/lib/docyard/build/error_page_generator.rb +33 -0
- data/lib/docyard/build/file_copier.rb +12 -5
- data/lib/docyard/build/file_writer.rb +19 -0
- data/lib/docyard/build/llms_txt_generator.rb +103 -0
- data/lib/docyard/build/root_fallback_generator.rb +66 -0
- data/lib/docyard/build/sitemap_generator.rb +1 -1
- data/lib/docyard/build/static_generator.rb +119 -81
- 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/breadcrumb_builder.rb +45 -6
- 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 +96 -61
- 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 +39 -71
- 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/heading-anchor.css +2 -2
- 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 +9 -6
- data/lib/docyard/templates/assets/css/components/table-of-contents.css +63 -17
- 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 +32 -16
- data/lib/docyard/templates/assets/css/markdown.css +22 -2
- data/lib/docyard/templates/assets/css/variables.css +14 -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 +125 -5
- data/lib/docyard/templates/errors/500.html.erb +184 -10
- data/lib/docyard/templates/errors/redirect.html.erb +12 -0
- 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 +80 -5
- 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 +81 -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
data/LICENSE.vscode-icons
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
File Type Icons from VSCode Icons
|
|
2
|
-
=====================================
|
|
3
|
-
|
|
4
|
-
The file type icons embedded in lib/docyard/icons/file_types.rb are from the VSCode Icons project:
|
|
5
|
-
https://github.com/vscode-icons/vscode-icons
|
|
6
|
-
|
|
7
|
-
License: Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)
|
|
8
|
-
https://creativecommons.org/licenses/by-sa/4.0/
|
|
9
|
-
|
|
10
|
-
Copyright (c) 2016 Roberto Huertas
|
|
11
|
-
|
|
12
|
-
Icons included:
|
|
13
|
-
- JavaScript (.js)
|
|
14
|
-
- TypeScript (.ts)
|
|
15
|
-
- JSX (.jsx)
|
|
16
|
-
- TSX (.tsx)
|
|
17
|
-
- Python (.py)
|
|
18
|
-
- Ruby (.rb)
|
|
19
|
-
- HTML (.html)
|
|
20
|
-
- CSS (.css)
|
|
21
|
-
- JSON (.json)
|
|
22
|
-
- YAML (.yaml)
|
|
23
|
-
- TOML (.toml)
|
|
24
|
-
- Go (.go)
|
|
25
|
-
- Rust (.rs)
|
|
26
|
-
- PHP (.php)
|
|
27
|
-
- SQL (.sql)
|
|
28
|
-
- MySQL (.mysql)
|
|
29
|
-
- PostgreSQL (.pgsql)
|
|
30
|
-
- GraphQL (.graphql)
|
|
31
|
-
- Vue (.vue)
|
|
32
|
-
- Svelte (.svelte)
|
|
33
|
-
- Protobuf (.proto)
|
|
34
|
-
|
|
35
|
-
These icons are used under the terms of the CC BY-SA 4.0 license.
|
|
36
|
-
You are free to:
|
|
37
|
-
- Share — copy and redistribute the material in any medium or format
|
|
38
|
-
- Adapt — remix, transform, and build upon the material for any purpose, even commercially
|
|
39
|
-
|
|
40
|
-
Under the following terms:
|
|
41
|
-
- Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made
|
|
42
|
-
- ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license
|
data/Rakefile
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Docyard
|
|
4
|
-
module Constants
|
|
5
|
-
CONTENT_TYPE_HTML = "text/html; charset=utf-8"
|
|
6
|
-
CONTENT_TYPE_JSON = "application/json; charset=utf-8"
|
|
7
|
-
CONTENT_TYPE_CSS = "text/css; charset=utf-8"
|
|
8
|
-
CONTENT_TYPE_JS = "application/javascript; charset=utf-8"
|
|
9
|
-
|
|
10
|
-
RELOAD_ENDPOINT = "/_docyard/reload"
|
|
11
|
-
DOCYARD_ASSETS_PREFIX = "/_docyard/"
|
|
12
|
-
PAGEFIND_PREFIX = "/pagefind/"
|
|
13
|
-
PUBLIC_DIR = "docs/public"
|
|
14
|
-
|
|
15
|
-
INDEX_FILE = "index"
|
|
16
|
-
INDEX_TITLE = "Home"
|
|
17
|
-
|
|
18
|
-
MARKDOWN_EXTENSION = ".md"
|
|
19
|
-
HTML_EXTENSION = ".html"
|
|
20
|
-
|
|
21
|
-
STATUS_OK = 200
|
|
22
|
-
STATUS_REDIRECT = 302
|
|
23
|
-
STATUS_NOT_FOUND = 404
|
|
24
|
-
STATUS_INTERNAL_ERROR = 500
|
|
25
|
-
|
|
26
|
-
DEFAULT_SITE_TITLE = "Documentation"
|
|
27
|
-
DEFAULT_LOGO_PATH = "_docyard/logo.svg"
|
|
28
|
-
DEFAULT_LOGO_DARK_PATH = "_docyard/logo-dark.svg"
|
|
29
|
-
DEFAULT_FAVICON_PATH = "_docyard/favicon.svg"
|
|
30
|
-
end
|
|
31
|
-
end
|
|
@@ -1,51 +0,0 @@
|
|
|
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
|
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "item"
|
|
4
|
-
require_relative "title_extractor"
|
|
5
|
-
require_relative "metadata_extractor"
|
|
6
|
-
require_relative "children_discoverer"
|
|
7
|
-
require_relative "file_resolver"
|
|
8
|
-
|
|
9
|
-
module Docyard
|
|
10
|
-
module Sidebar
|
|
11
|
-
class ConfigParser # rubocop:disable Metrics/ClassLength
|
|
12
|
-
attr_reader :config_items, :docs_path, :current_path, :metadata_extractor,
|
|
13
|
-
:children_discoverer, :file_resolver
|
|
14
|
-
|
|
15
|
-
def initialize(config_items, docs_path:, current_path: "/", title_extractor: TitleExtractor.new)
|
|
16
|
-
@config_items = config_items || []
|
|
17
|
-
@docs_path = docs_path
|
|
18
|
-
@current_path = Utils::PathResolver.normalize(current_path)
|
|
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
|
-
)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def parse
|
|
27
|
-
parse_items(config_items, "", depth: 1)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
private
|
|
31
|
-
|
|
32
|
-
def parse_items(items, base_path, depth:)
|
|
33
|
-
items.map { |item_config| parse_item(item_config, base_path, depth: depth) }.compact
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def parse_item(item_config, base_path, depth:)
|
|
37
|
-
case item_config
|
|
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)
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
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
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def parse_hash_item(item_config, base_path, depth:)
|
|
52
|
-
return file_resolver.build_link_item(item_config) if external_link?(item_config)
|
|
53
|
-
|
|
54
|
-
slug = item_config.keys.first
|
|
55
|
-
options = item_config.values.first
|
|
56
|
-
|
|
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)
|
|
59
|
-
|
|
60
|
-
file_resolver.resolve(slug, base_path, options)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def external_link?(config)
|
|
64
|
-
config.key?("link") || config.key?(:link)
|
|
65
|
-
end
|
|
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] || []
|
|
71
|
-
dir_path = File.join(docs_path, base_path, slug)
|
|
72
|
-
is_virtual_group = (options["section"] == false || options[:section] == false) && nested_items.any?
|
|
73
|
-
|
|
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)
|
|
78
|
-
elsif nested_items.any?
|
|
79
|
-
build_file_with_children_item(slug, options, nested_items, base_path, depth: depth)
|
|
80
|
-
else
|
|
81
|
-
file_resolver.resolve(slug, base_path, options)
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
85
|
-
|
|
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)
|
|
90
|
-
|
|
91
|
-
Item.new(
|
|
92
|
-
slug: slug,
|
|
93
|
-
text: common_opts[:text] || Utils::TextFormatter.titleize(slug),
|
|
94
|
-
path: nil,
|
|
95
|
-
icon: common_opts[:icon],
|
|
96
|
-
collapsed: is_collapsed,
|
|
97
|
-
active: false,
|
|
98
|
-
items: parsed_items,
|
|
99
|
-
type: :directory,
|
|
100
|
-
section: false
|
|
101
|
-
)
|
|
102
|
-
end
|
|
103
|
-
|
|
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
|
|
140
|
-
|
|
141
|
-
def create_directory_item(slug, context, depth)
|
|
142
|
-
is_section = section_for_depth?(context[:common_opts][:section], depth)
|
|
143
|
-
Item.new(
|
|
144
|
-
slug: slug,
|
|
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
|
|
154
|
-
)
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def section_for_depth?(explicit_section, depth)
|
|
158
|
-
return explicit_section unless explicit_section.nil?
|
|
159
|
-
|
|
160
|
-
depth == 1
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
def directory_collapsed?(context)
|
|
164
|
-
return false if context[:is_active] || active_child?(context[:parsed_items])
|
|
165
|
-
|
|
166
|
-
context[:common_opts][:collapsed] != false
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
def build_introduction_item(index_file_path, url_path)
|
|
170
|
-
metadata = metadata_extractor.extract_index_metadata(index_file_path)
|
|
171
|
-
Item.new(
|
|
172
|
-
slug: "index", text: metadata[:sidebar_text] || "Overview",
|
|
173
|
-
path: url_path, icon: metadata[:icon], active: current_path == url_path, type: :file
|
|
174
|
-
)
|
|
175
|
-
end
|
|
176
|
-
|
|
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
|
|
184
|
-
|
|
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
|
|
193
|
-
end
|
|
194
|
-
|
|
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
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
def active_child?(items)
|
|
204
|
-
items.any? { |item| item.active || active_child?(item.items) }
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
end
|
|
208
|
-
end
|
|
@@ -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
|