docyard 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -1
- data/CHANGELOG.md +43 -1
- data/lib/docyard/build/asset_bundler.rb +22 -7
- data/lib/docyard/build/file_copier.rb +49 -27
- data/lib/docyard/build/sitemap_generator.rb +6 -6
- data/lib/docyard/build/static_generator.rb +85 -12
- data/lib/docyard/builder.rb +6 -6
- data/lib/docyard/components/aliases.rb +12 -0
- data/lib/docyard/components/processors/abbreviation_processor.rb +72 -0
- data/lib/docyard/components/processors/accordion_processor.rb +81 -0
- data/lib/docyard/components/processors/badge_processor.rb +72 -0
- data/lib/docyard/components/processors/callout_processor.rb +8 -2
- data/lib/docyard/components/processors/cards_processor.rb +100 -0
- data/lib/docyard/components/processors/code_block_options_preprocessor.rb +23 -2
- data/lib/docyard/components/processors/code_block_processor.rb +6 -0
- data/lib/docyard/components/processors/code_group_processor.rb +198 -0
- data/lib/docyard/components/processors/code_snippet_import_preprocessor.rb +6 -1
- data/lib/docyard/components/processors/custom_anchor_processor.rb +42 -0
- data/lib/docyard/components/processors/file_tree_processor.rb +151 -0
- data/lib/docyard/components/processors/image_caption_processor.rb +96 -0
- data/lib/docyard/components/processors/include_processor.rb +86 -0
- data/lib/docyard/components/processors/steps_processor.rb +89 -0
- data/lib/docyard/components/processors/tabs_processor.rb +9 -1
- data/lib/docyard/components/processors/tooltip_processor.rb +57 -0
- data/lib/docyard/components/processors/video_embed_processor.rb +196 -0
- data/lib/docyard/components/support/code_group/html_builder.rb +122 -0
- data/lib/docyard/components/support/markdown_code_block_helper.rb +56 -0
- data/lib/docyard/config/branding_resolver.rb +121 -17
- data/lib/docyard/config/constants.rb +6 -4
- data/lib/docyard/config/logo_detector.rb +39 -0
- data/lib/docyard/config/validator.rb +122 -99
- data/lib/docyard/config.rb +40 -42
- data/lib/docyard/initializer.rb +15 -76
- data/lib/docyard/navigation/breadcrumb_builder.rb +133 -0
- data/lib/docyard/navigation/prev_next_builder.rb +4 -1
- data/lib/docyard/navigation/sidebar/children_discoverer.rb +51 -0
- data/lib/docyard/navigation/sidebar/config_parser.rb +136 -108
- data/lib/docyard/navigation/sidebar/file_resolver.rb +90 -0
- data/lib/docyard/navigation/sidebar/file_system_scanner.rb +2 -1
- data/lib/docyard/navigation/sidebar/item.rb +50 -7
- data/lib/docyard/navigation/sidebar/local_config_loader.rb +51 -0
- data/lib/docyard/navigation/sidebar/metadata_extractor.rb +71 -0
- data/lib/docyard/navigation/sidebar/metadata_reader.rb +51 -0
- data/lib/docyard/navigation/sidebar/path_prefixer.rb +34 -0
- data/lib/docyard/navigation/sidebar/renderer.rb +60 -38
- data/lib/docyard/navigation/sidebar/sorter.rb +21 -0
- data/lib/docyard/navigation/sidebar/tree_builder.rb +100 -26
- data/lib/docyard/navigation/sidebar/tree_filter.rb +55 -0
- data/lib/docyard/navigation/sidebar_builder.rb +105 -36
- data/lib/docyard/rendering/icon_helpers.rb +13 -0
- data/lib/docyard/rendering/icons/phosphor.rb +26 -1
- data/lib/docyard/rendering/markdown.rb +29 -1
- data/lib/docyard/rendering/renderer.rb +75 -34
- data/lib/docyard/rendering/template_resolver.rb +172 -0
- data/lib/docyard/routing/fallback_resolver.rb +92 -0
- data/lib/docyard/search/build_indexer.rb +1 -1
- data/lib/docyard/search/dev_indexer.rb +51 -6
- data/lib/docyard/search/pagefind_support.rb +2 -0
- data/lib/docyard/server/asset_handler.rb +25 -19
- data/lib/docyard/server/pagefind_handler.rb +63 -0
- data/lib/docyard/server/preview_server.rb +1 -1
- data/lib/docyard/server/rack_application.rb +81 -64
- data/lib/docyard/templates/assets/css/code.css +18 -51
- data/lib/docyard/templates/assets/css/components/abbreviation.css +86 -0
- data/lib/docyard/templates/assets/css/components/accordion.css +138 -0
- data/lib/docyard/templates/assets/css/components/badges.css +47 -0
- data/lib/docyard/templates/assets/css/components/banner.css +202 -0
- data/lib/docyard/templates/assets/css/components/breadcrumbs.css +143 -0
- data/lib/docyard/templates/assets/css/components/callout.css +67 -67
- data/lib/docyard/templates/assets/css/components/cards.css +100 -0
- data/lib/docyard/templates/assets/css/components/code-block.css +190 -282
- data/lib/docyard/templates/assets/css/components/code-group.css +281 -0
- data/lib/docyard/templates/assets/css/components/figure.css +22 -0
- data/lib/docyard/templates/assets/css/components/file-tree.css +124 -0
- data/lib/docyard/templates/assets/css/components/heading-anchor.css +36 -15
- data/lib/docyard/templates/assets/css/components/icon.css +0 -1
- data/lib/docyard/templates/assets/css/components/lightbox.css +65 -0
- data/lib/docyard/templates/assets/css/components/logo.css +0 -2
- data/lib/docyard/templates/assets/css/components/nav-menu.css +237 -0
- data/lib/docyard/templates/assets/css/components/navigation.css +193 -167
- data/lib/docyard/templates/assets/css/components/prev-next.css +68 -48
- data/lib/docyard/templates/assets/css/components/search.css +186 -174
- data/lib/docyard/templates/assets/css/components/steps.css +122 -0
- data/lib/docyard/templates/assets/css/components/tab-bar.css +163 -0
- data/lib/docyard/templates/assets/css/components/table-of-contents.css +127 -114
- data/lib/docyard/templates/assets/css/components/tabs.css +119 -160
- data/lib/docyard/templates/assets/css/components/theme-toggle.css +48 -44
- data/lib/docyard/templates/assets/css/components/tooltip.css +113 -0
- data/lib/docyard/templates/assets/css/components/video.css +41 -0
- data/lib/docyard/templates/assets/css/landing.css +815 -0
- data/lib/docyard/templates/assets/css/layout.css +489 -87
- data/lib/docyard/templates/assets/css/main.css +1 -3
- data/lib/docyard/templates/assets/css/markdown.css +113 -93
- data/lib/docyard/templates/assets/css/reset.css +0 -3
- data/lib/docyard/templates/assets/css/typography.css +43 -41
- data/lib/docyard/templates/assets/css/variables.css +268 -208
- data/lib/docyard/templates/assets/favicon.svg +7 -8
- data/lib/docyard/templates/assets/fonts/Inter-Variable.ttf +0 -0
- data/lib/docyard/templates/assets/js/components/abbreviation.js +85 -0
- data/lib/docyard/templates/assets/js/components/banner.js +81 -0
- data/lib/docyard/templates/assets/js/components/code-block.js +24 -42
- data/lib/docyard/templates/assets/js/components/code-group.js +283 -0
- data/lib/docyard/templates/assets/js/components/file-tree.js +39 -0
- data/lib/docyard/templates/assets/js/components/heading-anchor.js +26 -24
- data/lib/docyard/templates/assets/js/components/lightbox.js +72 -0
- data/lib/docyard/templates/assets/js/components/navigation.js +181 -70
- data/lib/docyard/templates/assets/js/components/search.js +0 -75
- data/lib/docyard/templates/assets/js/components/sidebar-toggle.js +29 -0
- data/lib/docyard/templates/assets/js/components/tab-navigation.js +145 -0
- data/lib/docyard/templates/assets/js/components/table-of-contents.js +153 -66
- data/lib/docyard/templates/assets/js/components/tabs.js +31 -69
- data/lib/docyard/templates/assets/js/components/tooltip.js +118 -0
- data/lib/docyard/templates/assets/js/theme.js +0 -3
- data/lib/docyard/templates/assets/logo-dark.svg +8 -2
- data/lib/docyard/templates/assets/logo.svg +7 -4
- data/lib/docyard/templates/config/docyard.yml.erb +37 -34
- data/lib/docyard/templates/errors/404.html.erb +1 -1
- data/lib/docyard/templates/errors/500.html.erb +1 -1
- data/lib/docyard/templates/layouts/default.html.erb +19 -67
- data/lib/docyard/templates/layouts/splash.html.erb +177 -0
- data/lib/docyard/templates/partials/_accordion.html.erb +9 -0
- data/lib/docyard/templates/partials/_banner.html.erb +27 -0
- data/lib/docyard/templates/partials/_breadcrumbs.html.erb +24 -0
- data/lib/docyard/templates/partials/_card.html.erb +23 -0
- data/lib/docyard/templates/partials/_code_block.html.erb +5 -3
- data/lib/docyard/templates/partials/_doc_footer.html.erb +25 -0
- data/lib/docyard/templates/partials/_features.html.erb +15 -0
- data/lib/docyard/templates/partials/_footer.html.erb +42 -0
- data/lib/docyard/templates/partials/_head.html.erb +22 -0
- data/lib/docyard/templates/partials/_header.html.erb +49 -0
- data/lib/docyard/templates/partials/_heading_anchor.html.erb +3 -1
- data/lib/docyard/templates/partials/_hero.html.erb +27 -0
- data/lib/docyard/templates/partials/_nav_group.html.erb +31 -11
- data/lib/docyard/templates/partials/_nav_leaf.html.erb +4 -1
- data/lib/docyard/templates/partials/_nav_menu.html.erb +42 -0
- data/lib/docyard/templates/partials/_nav_nested_section.html.erb +11 -0
- data/lib/docyard/templates/partials/_nav_section.html.erb +1 -1
- data/lib/docyard/templates/partials/_prev_next.html.erb +8 -2
- data/lib/docyard/templates/partials/_scripts.html.erb +7 -0
- data/lib/docyard/templates/partials/_search_modal.html.erb +2 -6
- data/lib/docyard/templates/partials/_search_trigger.html.erb +2 -6
- data/lib/docyard/templates/partials/_sidebar.html.erb +21 -4
- data/lib/docyard/templates/partials/_step.html.erb +14 -0
- data/lib/docyard/templates/partials/_tab_bar.html.erb +25 -0
- data/lib/docyard/templates/partials/_table_of_contents.html.erb +12 -12
- data/lib/docyard/templates/partials/_table_of_contents_toggle.html.erb +1 -3
- data/lib/docyard/templates/partials/_tabs.html.erb +2 -2
- data/lib/docyard/templates/partials/_theme_toggle.html.erb +2 -11
- data/lib/docyard/version.rb +1 -1
- metadata +70 -5
- data/lib/docyard/templates/markdown/getting-started/installation.md.erb +0 -77
- data/lib/docyard/templates/markdown/guides/configuration.md.erb +0 -202
- data/lib/docyard/templates/markdown/guides/markdown-features.md.erb +0 -247
- data/lib/docyard/templates/markdown/index.md.erb +0 -82
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
class TemplateResolver
|
|
5
|
+
BACKGROUNDS = %w[grid glow mesh none].freeze
|
|
6
|
+
DEFAULT_BACKGROUND = "grid"
|
|
7
|
+
|
|
8
|
+
attr_reader :frontmatter, :site_config
|
|
9
|
+
|
|
10
|
+
def initialize(frontmatter, site_config = {})
|
|
11
|
+
@frontmatter = frontmatter || {}
|
|
12
|
+
@site_config = site_config || {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def landing?
|
|
16
|
+
landing_config.any?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def template
|
|
20
|
+
landing? ? "splash" : "default"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def show_sidebar?
|
|
24
|
+
if landing?
|
|
25
|
+
landing_config.fetch("sidebar", false)
|
|
26
|
+
else
|
|
27
|
+
true
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def show_toc?
|
|
32
|
+
return false if landing?
|
|
33
|
+
|
|
34
|
+
true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def hero_config
|
|
38
|
+
return nil unless landing?
|
|
39
|
+
|
|
40
|
+
hero = landing_config["hero"]
|
|
41
|
+
return nil unless hero.is_a?(Hash)
|
|
42
|
+
|
|
43
|
+
symbolize_hero(hero)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def features_config
|
|
47
|
+
return nil unless landing?
|
|
48
|
+
|
|
49
|
+
features = landing_config["features"]
|
|
50
|
+
return nil unless features.is_a?(Array)
|
|
51
|
+
|
|
52
|
+
features.map { |f| symbolize_feature(f) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def features_header_config
|
|
56
|
+
return nil unless landing?
|
|
57
|
+
|
|
58
|
+
header = landing_config["features_header"]
|
|
59
|
+
return nil unless header.is_a?(Hash)
|
|
60
|
+
|
|
61
|
+
{
|
|
62
|
+
label: header["label"],
|
|
63
|
+
title: header["title"],
|
|
64
|
+
description: header["description"]
|
|
65
|
+
}.compact
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def footer_config
|
|
69
|
+
return nil unless landing?
|
|
70
|
+
|
|
71
|
+
footer = landing_config["footer"]
|
|
72
|
+
return nil unless footer.is_a?(Hash)
|
|
73
|
+
|
|
74
|
+
{
|
|
75
|
+
links: normalize_footer_links(footer["links"])
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def to_options
|
|
80
|
+
{
|
|
81
|
+
template: template,
|
|
82
|
+
landing: landing?,
|
|
83
|
+
show_sidebar: show_sidebar?,
|
|
84
|
+
show_toc: show_toc?,
|
|
85
|
+
hero: hero_config,
|
|
86
|
+
features: features_config,
|
|
87
|
+
features_header: features_header_config,
|
|
88
|
+
footer: footer_config
|
|
89
|
+
}
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def normalize_footer_links(links)
|
|
95
|
+
return nil unless links.is_a?(Array)
|
|
96
|
+
|
|
97
|
+
links.map do |link|
|
|
98
|
+
next unless link.is_a?(Hash)
|
|
99
|
+
|
|
100
|
+
{ text: link["text"], link: link["link"] }
|
|
101
|
+
end.compact
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def landing_config
|
|
105
|
+
@landing_config ||= frontmatter["landing"] || site_config["landing"] || {}
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def symbolize_hero(hero)
|
|
109
|
+
background = hero["background"]
|
|
110
|
+
validated_bg = BACKGROUNDS.include?(background) ? background : DEFAULT_BACKGROUND
|
|
111
|
+
|
|
112
|
+
{
|
|
113
|
+
background: validated_bg,
|
|
114
|
+
badge: hero["badge"],
|
|
115
|
+
name: hero["name"],
|
|
116
|
+
title: hero["title"],
|
|
117
|
+
tagline: hero["tagline"],
|
|
118
|
+
gradient: hero.fetch("gradient", true),
|
|
119
|
+
image: symbolize_image(hero["image"]),
|
|
120
|
+
actions: symbolize_actions(hero["actions"])
|
|
121
|
+
}.compact
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def symbolize_image(image)
|
|
125
|
+
return nil unless image.is_a?(Hash)
|
|
126
|
+
|
|
127
|
+
if image["light"] || image["dark"]
|
|
128
|
+
{
|
|
129
|
+
light: image["light"],
|
|
130
|
+
dark: image["dark"],
|
|
131
|
+
alt: image["alt"]
|
|
132
|
+
}.compact
|
|
133
|
+
else
|
|
134
|
+
{
|
|
135
|
+
src: image["src"],
|
|
136
|
+
alt: image["alt"]
|
|
137
|
+
}.compact
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def symbolize_actions(actions)
|
|
142
|
+
return nil unless actions.is_a?(Array)
|
|
143
|
+
|
|
144
|
+
actions.map do |action|
|
|
145
|
+
{
|
|
146
|
+
text: action["text"],
|
|
147
|
+
link: action["link"],
|
|
148
|
+
icon: action["icon"],
|
|
149
|
+
variant: action["variant"] || "primary",
|
|
150
|
+
target: action["target"],
|
|
151
|
+
rel: action["rel"]
|
|
152
|
+
}.compact
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def symbolize_feature(feature)
|
|
157
|
+
return {} unless feature.is_a?(Hash)
|
|
158
|
+
|
|
159
|
+
{
|
|
160
|
+
title: feature["title"],
|
|
161
|
+
description: feature["description"],
|
|
162
|
+
icon: feature["icon"],
|
|
163
|
+
color: feature["color"],
|
|
164
|
+
link: feature["link"],
|
|
165
|
+
link_text: feature["link_text"],
|
|
166
|
+
size: feature["size"],
|
|
167
|
+
target: feature["target"],
|
|
168
|
+
rel: feature["rel"]
|
|
169
|
+
}.compact
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module Routing
|
|
5
|
+
class FallbackResolver
|
|
6
|
+
attr_reader :docs_path, :sidebar_builder
|
|
7
|
+
|
|
8
|
+
def initialize(docs_path:, sidebar_builder:)
|
|
9
|
+
@docs_path = docs_path
|
|
10
|
+
@sidebar_builder = sidebar_builder
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def resolve_fallback(request_path)
|
|
14
|
+
return nil if file_exists?(request_path)
|
|
15
|
+
|
|
16
|
+
find_first_item_in_section(request_path)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def file_exists?(request_path)
|
|
22
|
+
clean_path = sanitize_path(request_path)
|
|
23
|
+
|
|
24
|
+
file_path = File.join(docs_path, "#{clean_path}.md")
|
|
25
|
+
return true if File.file?(file_path)
|
|
26
|
+
|
|
27
|
+
index_path = File.join(docs_path, clean_path, "index.md")
|
|
28
|
+
File.file?(index_path)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def sanitize_path(request_path)
|
|
32
|
+
clean = request_path.to_s.delete_prefix("/").delete_suffix("/")
|
|
33
|
+
clean = "index" if clean.empty?
|
|
34
|
+
clean.delete_suffix(".md")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def find_first_item_in_section(request_path)
|
|
38
|
+
tree = sidebar_builder.tree
|
|
39
|
+
|
|
40
|
+
if root_path?(request_path)
|
|
41
|
+
find_first_navigable_item(tree)
|
|
42
|
+
else
|
|
43
|
+
section = find_section_in_tree(tree, request_path)
|
|
44
|
+
section ? find_first_navigable_item(section[:children] || []) : nil
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def root_path?(request_path)
|
|
49
|
+
request_path.nil? || request_path == "/" || request_path.empty?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def find_section_in_tree(tree, path)
|
|
53
|
+
normalized_path = normalize_path(path)
|
|
54
|
+
|
|
55
|
+
tree.each do |item|
|
|
56
|
+
return item if path_matches_section?(item, normalized_path)
|
|
57
|
+
|
|
58
|
+
if item[:children]&.any?
|
|
59
|
+
found = find_section_in_tree(item[:children], path)
|
|
60
|
+
return found if found
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
nil
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def normalize_path(path)
|
|
68
|
+
path.to_s.delete_prefix("/").delete_suffix("/").downcase
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def path_matches_section?(item, normalized_path)
|
|
72
|
+
return false unless item[:type] == :directory
|
|
73
|
+
|
|
74
|
+
item_path = item[:title].to_s.downcase.gsub(/\s+/, "-")
|
|
75
|
+
item_path == normalized_path
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def find_first_navigable_item(items)
|
|
79
|
+
items.each do |item|
|
|
80
|
+
return item[:path] if item[:path] && item[:type] == :file
|
|
81
|
+
|
|
82
|
+
if item[:children]&.any?
|
|
83
|
+
found = find_first_navigable_item(item[:children])
|
|
84
|
+
return found if found
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
nil
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -25,10 +25,10 @@ module Docyard
|
|
|
25
25
|
|
|
26
26
|
@temp_dir = Dir.mktmpdir("docyard-search-")
|
|
27
27
|
generate_html_files
|
|
28
|
-
run_pagefind
|
|
28
|
+
page_count = run_pagefind
|
|
29
29
|
@pagefind_path = File.join(temp_dir, "pagefind")
|
|
30
30
|
|
|
31
|
-
log_success
|
|
31
|
+
log_success(page_count)
|
|
32
32
|
pagefind_path
|
|
33
33
|
rescue StandardError => e
|
|
34
34
|
warn "[!] Search index generation failed: #{e.message}"
|
|
@@ -52,6 +52,8 @@ module Docyard
|
|
|
52
52
|
|
|
53
53
|
def generate_html_files
|
|
54
54
|
markdown_files = Dir.glob(File.join(docs_path, "**", "*.md"))
|
|
55
|
+
markdown_files = filter_excluded_files(markdown_files)
|
|
56
|
+
markdown_files = filter_non_indexable_files(markdown_files)
|
|
55
57
|
renderer = Renderer.new(base_url: "/", config: config)
|
|
56
58
|
|
|
57
59
|
progress = TTY::ProgressBar.new(
|
|
@@ -66,6 +68,45 @@ module Docyard
|
|
|
66
68
|
end
|
|
67
69
|
end
|
|
68
70
|
|
|
71
|
+
def filter_excluded_files(files)
|
|
72
|
+
exclude_patterns = config.search.exclude || []
|
|
73
|
+
return files if exclude_patterns.empty?
|
|
74
|
+
|
|
75
|
+
files.reject do |file_path|
|
|
76
|
+
url_path = file_to_url_path(file_path)
|
|
77
|
+
exclude_patterns.any? { |pattern| File.fnmatch(pattern, url_path, File::FNM_PATHNAME) }
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def filter_non_indexable_files(files)
|
|
82
|
+
files.reject do |file_path|
|
|
83
|
+
content = File.read(file_path)
|
|
84
|
+
markdown = Markdown.new(content)
|
|
85
|
+
frontmatter = markdown.frontmatter
|
|
86
|
+
|
|
87
|
+
uses_splash_template?(frontmatter)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def uses_splash_template?(frontmatter)
|
|
92
|
+
return true if frontmatter["template"] == "splash"
|
|
93
|
+
return true if frontmatter.key?("landing")
|
|
94
|
+
|
|
95
|
+
frontmatter.key?("hero") || frontmatter.key?("features")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def file_to_url_path(file_path)
|
|
99
|
+
relative_path = file_path.delete_prefix("#{docs_path}/")
|
|
100
|
+
base_name = File.basename(relative_path, ".md")
|
|
101
|
+
dir_name = File.dirname(relative_path)
|
|
102
|
+
|
|
103
|
+
if base_name == "index"
|
|
104
|
+
dir_name == "." ? "/" : "/#{dir_name}"
|
|
105
|
+
else
|
|
106
|
+
dir_name == "." ? "/#{base_name}" : "/#{dir_name}/#{base_name}"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
69
110
|
def generate_html_file(markdown_file, renderer)
|
|
70
111
|
relative_path = markdown_file.delete_prefix("#{docs_path}/")
|
|
71
112
|
output_path = determine_output_path(relative_path)
|
|
@@ -97,12 +138,16 @@ module Docyard
|
|
|
97
138
|
|
|
98
139
|
raise "Pagefind failed: #{stderr}" unless status.success?
|
|
99
140
|
|
|
100
|
-
stdout
|
|
141
|
+
extract_page_count(stdout)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def extract_page_count(output)
|
|
145
|
+
match = output.match(/Indexed (\d+) page/i)
|
|
146
|
+
match ? match[1].to_i : 0
|
|
101
147
|
end
|
|
102
148
|
|
|
103
|
-
def log_success
|
|
104
|
-
|
|
105
|
-
puts "=> Search index generated (#{page_count} pages)"
|
|
149
|
+
def log_success(page_count)
|
|
150
|
+
puts "=> Search index generated (#{page_count} pages indexed)"
|
|
106
151
|
puts "=> Temp directory: #{temp_dir}" if ENV["DOCYARD_DEBUG"]
|
|
107
152
|
end
|
|
108
153
|
end
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Docyard
|
|
4
4
|
class AssetHandler
|
|
5
|
-
|
|
6
|
-
USER_ASSETS_PATH = "docs/assets"
|
|
5
|
+
TEMPLATES_ASSETS_PATH = File.join(__dir__, "../templates", "assets")
|
|
7
6
|
|
|
8
7
|
CONTENT_TYPES = {
|
|
9
8
|
".css" => "text/css; charset=utf-8",
|
|
@@ -11,41 +10,48 @@ module Docyard
|
|
|
11
10
|
".png" => "image/png",
|
|
12
11
|
".jpg" => "image/jpeg",
|
|
13
12
|
".jpeg" => "image/jpeg",
|
|
13
|
+
".gif" => "image/gif",
|
|
14
|
+
".webp" => "image/webp",
|
|
15
|
+
".avif" => "image/avif",
|
|
14
16
|
".svg" => "image/svg+xml",
|
|
15
|
-
".woff" => "font/
|
|
17
|
+
".woff" => "font/woff",
|
|
16
18
|
".woff2" => "font/woff2",
|
|
17
|
-
".
|
|
19
|
+
".ttf" => "font/ttf",
|
|
20
|
+
".ico" => "image/x-icon",
|
|
21
|
+
".pdf" => "application/pdf",
|
|
22
|
+
".mp4" => "video/mp4",
|
|
23
|
+
".webm" => "video/webm"
|
|
18
24
|
}.freeze
|
|
19
25
|
|
|
20
|
-
def
|
|
21
|
-
asset_path =
|
|
26
|
+
def serve_docyard_assets(request_path)
|
|
27
|
+
asset_path = request_path.delete_prefix("/_docyard/")
|
|
22
28
|
|
|
23
29
|
return forbidden_response if directory_traversal?(asset_path)
|
|
24
30
|
|
|
25
31
|
return serve_components_css if asset_path == "css/components.css"
|
|
26
32
|
return serve_components_js if asset_path == "js/components.js"
|
|
27
33
|
|
|
28
|
-
file_path =
|
|
34
|
+
file_path = File.join(TEMPLATES_ASSETS_PATH, asset_path)
|
|
29
35
|
return not_found_response unless File.file?(file_path)
|
|
30
36
|
|
|
31
37
|
serve_file(file_path)
|
|
32
38
|
end
|
|
33
39
|
|
|
34
|
-
|
|
40
|
+
def serve_public_file(request_path)
|
|
41
|
+
asset_path = request_path.delete_prefix("/")
|
|
35
42
|
|
|
36
|
-
|
|
37
|
-
request_path.delete_prefix("/assets/")
|
|
38
|
-
end
|
|
43
|
+
return nil if directory_traversal?(asset_path)
|
|
39
44
|
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
file_path = File.join(Constants::PUBLIC_DIR, asset_path)
|
|
46
|
+
return nil unless File.file?(file_path)
|
|
47
|
+
|
|
48
|
+
serve_file(file_path)
|
|
42
49
|
end
|
|
43
50
|
|
|
44
|
-
|
|
45
|
-
user_path = File.join(USER_ASSETS_PATH, asset_path)
|
|
46
|
-
return user_path if File.file?(user_path)
|
|
51
|
+
private
|
|
47
52
|
|
|
48
|
-
|
|
53
|
+
def directory_traversal?(path)
|
|
54
|
+
path.include?("..")
|
|
49
55
|
end
|
|
50
56
|
|
|
51
57
|
def serve_file(file_path)
|
|
@@ -61,7 +67,7 @@ module Docyard
|
|
|
61
67
|
end
|
|
62
68
|
|
|
63
69
|
def concatenate_component_css
|
|
64
|
-
components_dir = File.join(
|
|
70
|
+
components_dir = File.join(TEMPLATES_ASSETS_PATH, "css", "components")
|
|
65
71
|
return "" unless Dir.exist?(components_dir)
|
|
66
72
|
|
|
67
73
|
css_files = Dir.glob(File.join(components_dir, "*.css"))
|
|
@@ -74,7 +80,7 @@ module Docyard
|
|
|
74
80
|
end
|
|
75
81
|
|
|
76
82
|
def concatenate_component_js
|
|
77
|
-
components_dir = File.join(
|
|
83
|
+
components_dir = File.join(TEMPLATES_ASSETS_PATH, "js", "components")
|
|
78
84
|
return "" unless Dir.exist?(components_dir)
|
|
79
85
|
|
|
80
86
|
js_files = Dir.glob(File.join(components_dir, "*.js"))
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
class PagefindHandler
|
|
5
|
+
CONTENT_TYPES = {
|
|
6
|
+
".js" => "application/javascript; charset=utf-8",
|
|
7
|
+
".css" => "text/css; charset=utf-8",
|
|
8
|
+
".json" => "application/json; charset=utf-8"
|
|
9
|
+
}.freeze
|
|
10
|
+
|
|
11
|
+
def initialize(pagefind_path:, config:)
|
|
12
|
+
@pagefind_path = pagefind_path
|
|
13
|
+
@config = config
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def serve(path)
|
|
17
|
+
relative_path = path.delete_prefix(Constants::PAGEFIND_PREFIX)
|
|
18
|
+
return not_found if relative_path.include?("..")
|
|
19
|
+
|
|
20
|
+
file_path = resolve_file(relative_path)
|
|
21
|
+
return not_found unless file_path && File.file?(file_path)
|
|
22
|
+
|
|
23
|
+
serve_file(file_path)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
attr_reader :pagefind_path, :config
|
|
29
|
+
|
|
30
|
+
def resolve_file(relative_path)
|
|
31
|
+
return File.join(pagefind_path, relative_path) if pagefind_path && Dir.exist?(pagefind_path)
|
|
32
|
+
|
|
33
|
+
output_dir = config&.build&.output_dir || "dist"
|
|
34
|
+
File.join(output_dir, "pagefind", relative_path)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def serve_file(file_path)
|
|
38
|
+
content = File.binread(file_path)
|
|
39
|
+
content_type = content_type_for(file_path)
|
|
40
|
+
|
|
41
|
+
[Constants::STATUS_OK, build_headers(content_type), [content]]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def build_headers(content_type)
|
|
45
|
+
{
|
|
46
|
+
"Content-Type" => content_type,
|
|
47
|
+
"Cache-Control" => "no-cache, no-store, must-revalidate",
|
|
48
|
+
"Pragma" => "no-cache",
|
|
49
|
+
"Expires" => "0"
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def content_type_for(file_path)
|
|
54
|
+
extension = File.extname(file_path)
|
|
55
|
+
CONTENT_TYPES.fetch(extension, "application/octet-stream")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def not_found
|
|
59
|
+
message = "Pagefind not found. Run 'docyard build' first."
|
|
60
|
+
[Constants::STATUS_NOT_FOUND, { "Content-Type" => "text/plain" }, [message]]
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|