docyard 1.0.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 +14 -1
- data/lib/docyard/build/asset_bundler.rb +22 -0
- data/lib/docyard/build/error_page_generator.rb +33 -0
- data/lib/docyard/build/file_writer.rb +19 -0
- data/lib/docyard/build/root_fallback_generator.rb +66 -0
- data/lib/docyard/build/static_generator.rb +20 -18
- data/lib/docyard/navigation/breadcrumb_builder.rb +45 -6
- data/lib/docyard/rendering/renderer.rb +9 -2
- data/lib/docyard/server/rack_application.rb +3 -3
- data/lib/docyard/templates/assets/css/components/banner.css +3 -3
- data/lib/docyard/templates/assets/css/components/heading-anchor.css +2 -2
- data/lib/docyard/templates/assets/css/components/tab-bar.css +2 -2
- data/lib/docyard/templates/assets/css/components/table-of-contents.css +16 -16
- data/lib/docyard/templates/assets/css/layout.css +15 -16
- data/lib/docyard/templates/assets/css/variables.css +1 -0
- data/lib/docyard/templates/errors/404.html.erb +13 -2
- data/lib/docyard/templates/errors/500.html.erb +13 -2
- data/lib/docyard/templates/errors/redirect.html.erb +12 -0
- data/lib/docyard/templates/partials/_head.html.erb +1 -1
- data/lib/docyard/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9ea61eefa97dc9199d17929def1315fdf2ce71f75ddf4f03833b9291fa64ce75
|
|
4
|
+
data.tar.gz: '0538fae77efffb84a8aac4ba979510419ff2a8cd8eec1b5aaf9effc2950595d8'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f184c67f1be8766cdd6339da6b25ccd8b7f1d0150ce3f6c0db0de8dd68c590b82e29f76a4ec1928c29151018d318225f2769cea58be9ad1dc2c721a2593663d5
|
|
7
|
+
data.tar.gz: 982f4bdcd4170dd013dd5ee10fb21db935eeafcb486372a45db7ae58a3f8d75e6c716555585695ea9527cc7fbd2bf0e43c5a7982894160ec6832c591c2db45a1
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.0.1] - 2026-01-22
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Root Fallback Redirect** - Auto-redirect to first page when no index.md exists (#127)
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Breadcrumb sections without index pages now link to first navigable child (#126)
|
|
17
|
+
- Error pages (404/500) now use custom branding colors (#125)
|
|
18
|
+
- Base URL now correctly applied to all assets including fonts and search (#124)
|
|
19
|
+
- Dev server no longer applies build.base config, always uses root path (#123)
|
|
20
|
+
|
|
10
21
|
## [1.0.0] - 2026-01-22
|
|
11
22
|
|
|
12
23
|
### Added
|
|
@@ -205,7 +216,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
205
216
|
- Initial gem structure
|
|
206
217
|
- Project scaffolding
|
|
207
218
|
|
|
208
|
-
[Unreleased]: https://github.com/sanifhimani/docyard/compare/
|
|
219
|
+
[Unreleased]: https://github.com/sanifhimani/docyard/compare/v1.0.1...HEAD
|
|
220
|
+
[1.0.1]: https://github.com/sanifhimani/docyard/compare/v1.0.0...v1.0.1
|
|
221
|
+
[1.0.0]: https://github.com/sanifhimani/docyard/compare/v0.9.0...v1.0.0
|
|
209
222
|
[0.9.0]: https://github.com/sanifhimani/docyard/compare/v0.8.0...v0.9.0
|
|
210
223
|
[0.8.0]: https://github.com/sanifhimani/docyard/compare/v0.7.0...v0.8.0
|
|
211
224
|
[0.7.0]: https://github.com/sanifhimani/docyard/compare/v0.6.0...v0.7.0
|
|
@@ -37,6 +37,7 @@ module Docyard
|
|
|
37
37
|
minified = CSSminify.compress(css_content)
|
|
38
38
|
minified = fix_calc_whitespace(minified)
|
|
39
39
|
minified = fix_css_math_functions(minified)
|
|
40
|
+
minified = replace_css_asset_urls(minified)
|
|
40
41
|
hash = generate_hash(minified)
|
|
41
42
|
|
|
42
43
|
write_bundled_asset(minified, hash, "css")
|
|
@@ -58,6 +59,11 @@ module Docyard
|
|
|
58
59
|
css.gsub(/\bmax\(0,/, "max(0px,").gsub(/\bmin\(0,/, "min(0px,)")
|
|
59
60
|
end
|
|
60
61
|
|
|
62
|
+
def replace_css_asset_urls(css)
|
|
63
|
+
base_url = normalize_base_url(config.build.base)
|
|
64
|
+
css.gsub(%r{/_docyard/fonts/}, "#{base_url}_docyard/fonts/")
|
|
65
|
+
end
|
|
66
|
+
|
|
61
67
|
def resolve_css_imports(css_content)
|
|
62
68
|
css_content.gsub(/@import url\('([^']+)'\);/) do |match|
|
|
63
69
|
import_file = Regexp.last_match(1)
|
|
@@ -86,6 +92,7 @@ module Docyard
|
|
|
86
92
|
components_js = concatenate_component_js
|
|
87
93
|
js_content = [theme_js, components_js].join("\n")
|
|
88
94
|
minified = Terser.compile(js_content)
|
|
95
|
+
minified = replace_js_asset_urls(minified)
|
|
89
96
|
hash = generate_hash(minified)
|
|
90
97
|
|
|
91
98
|
write_bundled_asset(minified, hash, "js")
|
|
@@ -94,6 +101,12 @@ module Docyard
|
|
|
94
101
|
hash
|
|
95
102
|
end
|
|
96
103
|
|
|
104
|
+
def replace_js_asset_urls(js_content)
|
|
105
|
+
base_url = normalize_base_url(config.build.base)
|
|
106
|
+
js_content.gsub(%r{"/_docyard/pagefind/}, "\"#{base_url}_docyard/pagefind/")
|
|
107
|
+
.gsub(%r{baseUrl:\s*["']/["']}, "baseUrl:\"#{base_url}\"")
|
|
108
|
+
end
|
|
109
|
+
|
|
97
110
|
def concatenate_component_js
|
|
98
111
|
components_dir = File.join(ASSETS_PATH, "js", "components")
|
|
99
112
|
return "" unless Dir.exist?(components_dir)
|
|
@@ -123,6 +136,15 @@ module Docyard
|
|
|
123
136
|
.gsub(%r{/_docyard/js/theme\.js}, "#{base_url}_docyard/bundle.#{js_hash}.js")
|
|
124
137
|
.gsub(%r{/_docyard/js/components\.js}, "")
|
|
125
138
|
.gsub(%r{<script src="/_docyard/js/reload\.js"></script>}, "")
|
|
139
|
+
.then { |html| replace_content_image_paths(html, base_url) }
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def replace_content_image_paths(content, base_url)
|
|
143
|
+
return content if base_url == "/"
|
|
144
|
+
|
|
145
|
+
content.gsub(%r{(<img[^>]*\ssrc=")/(?!_docyard/)([^"]*")}) do
|
|
146
|
+
"#{Regexp.last_match(1)}#{base_url}#{Regexp.last_match(2)}"
|
|
147
|
+
end
|
|
126
148
|
end
|
|
127
149
|
|
|
128
150
|
def write_bundled_asset(content, hash, extension)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module Build
|
|
5
|
+
class ErrorPageGenerator
|
|
6
|
+
attr_reader :config, :docs_path, :renderer
|
|
7
|
+
|
|
8
|
+
def initialize(config:, docs_path:, renderer:)
|
|
9
|
+
@config = config
|
|
10
|
+
@docs_path = docs_path
|
|
11
|
+
@renderer = renderer
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def generate
|
|
15
|
+
output_path = File.join(config.build.output, "404.html")
|
|
16
|
+
html_content = load_content
|
|
17
|
+
|
|
18
|
+
FileUtils.mkdir_p(File.dirname(output_path))
|
|
19
|
+
File.write(output_path, html_content)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def load_content
|
|
25
|
+
custom_error_page = File.join(docs_path, "404.html")
|
|
26
|
+
return File.read(custom_error_page) if File.exist?(custom_error_page)
|
|
27
|
+
|
|
28
|
+
branding = BrandingResolver.new(config).resolve
|
|
29
|
+
renderer.render_not_found(branding: branding)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module Build
|
|
5
|
+
module FileWriter
|
|
6
|
+
def safe_file_write(path)
|
|
7
|
+
yield
|
|
8
|
+
rescue Errno::EACCES => e
|
|
9
|
+
raise BuildError, "Permission denied writing to #{path}: #{e.message}"
|
|
10
|
+
rescue Errno::ENOSPC => e
|
|
11
|
+
raise BuildError, "Disk full, cannot write to #{path}: #{e.message}"
|
|
12
|
+
rescue Errno::EROFS => e
|
|
13
|
+
raise BuildError, "Read-only filesystem, cannot write to #{path}: #{e.message}"
|
|
14
|
+
rescue SystemCallError => e
|
|
15
|
+
raise BuildError, "Failed to write #{path}: #{e.message}"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module Build
|
|
5
|
+
class RootFallbackGenerator
|
|
6
|
+
attr_reader :config, :docs_path, :sidebar_cache, :renderer
|
|
7
|
+
|
|
8
|
+
def initialize(config:, docs_path:, sidebar_cache:, renderer:)
|
|
9
|
+
@config = config
|
|
10
|
+
@docs_path = docs_path
|
|
11
|
+
@sidebar_cache = sidebar_cache
|
|
12
|
+
@renderer = renderer
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def generate_if_needed
|
|
16
|
+
return if root_index_exists?
|
|
17
|
+
|
|
18
|
+
first_path = find_first_navigable_path
|
|
19
|
+
return unless first_path
|
|
20
|
+
|
|
21
|
+
generate_redirect_page(first_path)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def root_index_exists?
|
|
27
|
+
File.exist?(File.join(docs_path, "index.md")) ||
|
|
28
|
+
File.exist?(File.join(docs_path, "index.html"))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def find_first_navigable_path
|
|
32
|
+
return nil unless sidebar_cache&.tree&.any?
|
|
33
|
+
|
|
34
|
+
find_first_file_in_tree(sidebar_cache.tree)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def find_first_file_in_tree(items)
|
|
38
|
+
items.each do |item|
|
|
39
|
+
return item[:path] if item[:type] == :file && item[:path]
|
|
40
|
+
|
|
41
|
+
if item[:children]&.any?
|
|
42
|
+
nested = find_first_file_in_tree(item[:children])
|
|
43
|
+
return nested if nested
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def generate_redirect_page(target_path)
|
|
51
|
+
output_path = File.join(config.build.output, "index.html")
|
|
52
|
+
full_target = build_full_target_url(target_path)
|
|
53
|
+
|
|
54
|
+
FileUtils.mkdir_p(File.dirname(output_path))
|
|
55
|
+
File.write(output_path, renderer.render_redirect(full_target))
|
|
56
|
+
|
|
57
|
+
full_target
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def build_full_target_url(target_path)
|
|
61
|
+
base = config.build.base&.chomp("/") || ""
|
|
62
|
+
"#{base}#{target_path}"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -7,10 +7,15 @@ require_relative "../navigation/page_navigation_builder"
|
|
|
7
7
|
require_relative "../navigation/sidebar/cache"
|
|
8
8
|
require_relative "../utils/path_utils"
|
|
9
9
|
require_relative "../utils/git_info"
|
|
10
|
+
require_relative "root_fallback_generator"
|
|
11
|
+
require_relative "error_page_generator"
|
|
12
|
+
require_relative "file_writer"
|
|
10
13
|
|
|
11
14
|
module Docyard
|
|
12
15
|
module Build
|
|
13
16
|
class StaticGenerator
|
|
17
|
+
include FileWriter
|
|
18
|
+
|
|
14
19
|
PARALLEL_THRESHOLD = 10
|
|
15
20
|
|
|
16
21
|
attr_reader :config, :verbose, :sidebar_cache
|
|
@@ -31,6 +36,7 @@ module Docyard
|
|
|
31
36
|
|
|
32
37
|
generate_all_pages(markdown_files)
|
|
33
38
|
generate_error_page
|
|
39
|
+
generate_root_fallback_if_needed
|
|
34
40
|
|
|
35
41
|
markdown_files.size
|
|
36
42
|
ensure
|
|
@@ -161,18 +167,6 @@ module Docyard
|
|
|
161
167
|
log "Generated: #{output_path}" if verbose
|
|
162
168
|
end
|
|
163
169
|
|
|
164
|
-
def safe_file_write(path, &block)
|
|
165
|
-
block.call
|
|
166
|
-
rescue Errno::EACCES => e
|
|
167
|
-
raise BuildError, "Permission denied writing to #{path}: #{e.message}"
|
|
168
|
-
rescue Errno::ENOSPC => e
|
|
169
|
-
raise BuildError, "Disk full, cannot write to #{path}: #{e.message}"
|
|
170
|
-
rescue Errno::EROFS => e
|
|
171
|
-
raise BuildError, "Read-only filesystem, cannot write to #{path}: #{e.message}"
|
|
172
|
-
rescue SystemCallError => e
|
|
173
|
-
raise BuildError, "Failed to write #{path}: #{e.message}"
|
|
174
|
-
end
|
|
175
|
-
|
|
176
170
|
def build_sidebar_cache
|
|
177
171
|
@sidebar_cache = Sidebar::Cache.new(
|
|
178
172
|
docs_path: docs_path,
|
|
@@ -194,15 +188,23 @@ module Docyard
|
|
|
194
188
|
end
|
|
195
189
|
|
|
196
190
|
def generate_error_page
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
191
|
+
ErrorPageGenerator.new(
|
|
192
|
+
config: config,
|
|
193
|
+
docs_path: docs_path,
|
|
194
|
+
renderer: build_renderer
|
|
195
|
+
).generate
|
|
200
196
|
log "[✓] Generated 404.html"
|
|
201
197
|
end
|
|
202
198
|
|
|
203
|
-
def
|
|
204
|
-
|
|
205
|
-
|
|
199
|
+
def generate_root_fallback_if_needed
|
|
200
|
+
generator = RootFallbackGenerator.new(
|
|
201
|
+
config: config,
|
|
202
|
+
docs_path: docs_path,
|
|
203
|
+
sidebar_cache: sidebar_cache,
|
|
204
|
+
renderer: build_renderer
|
|
205
|
+
)
|
|
206
|
+
target = generator.generate_if_needed
|
|
207
|
+
log "[✓] Generated root redirect to #{target}" if target
|
|
206
208
|
end
|
|
207
209
|
end
|
|
208
210
|
end
|
|
@@ -76,9 +76,10 @@ module Docyard
|
|
|
76
76
|
def search_in_ancestors(node, path, title, href)
|
|
77
77
|
return unless node[:children]&.any?
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
return unless path_is_ancestor?(
|
|
79
|
+
section_path = derive_section_path(node, href)
|
|
80
|
+
return unless path_is_ancestor?(section_path)
|
|
81
81
|
|
|
82
|
+
effective_href = resolve_section_href(node, href, section_path)
|
|
82
83
|
result = find_breadcrumb_path(
|
|
83
84
|
node[:children],
|
|
84
85
|
path + [Item.new(title: title, href: effective_href, current: false)]
|
|
@@ -86,14 +87,52 @@ module Docyard
|
|
|
86
87
|
result.any? ? result : nil
|
|
87
88
|
end
|
|
88
89
|
|
|
89
|
-
def derive_section_path(node)
|
|
90
|
-
|
|
90
|
+
def derive_section_path(node, href)
|
|
91
|
+
return href if section_has_index?(href)
|
|
92
|
+
|
|
93
|
+
derive_section_path_from_children(node[:children])
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def derive_section_path_from_children(children)
|
|
97
|
+
first_child = children&.first
|
|
91
98
|
return nil unless first_child
|
|
92
99
|
|
|
93
100
|
child_path = first_child[:path]
|
|
94
|
-
return nil
|
|
101
|
+
return nil unless navigable_path?(child_path)
|
|
102
|
+
|
|
103
|
+
parent_dir = File.dirname(child_path)
|
|
104
|
+
root_parent?(parent_dir) ? child_path : parent_dir
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def root_parent?(parent_dir)
|
|
108
|
+
parent_dir == "/" || parent_dir.empty?
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def resolve_section_href(node, href, section_path)
|
|
112
|
+
return href if section_has_index?(href)
|
|
113
|
+
|
|
114
|
+
find_first_navigable_child(node[:children]) || section_path
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def section_has_index?(path)
|
|
118
|
+
path && !path.empty? && path != "/"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def find_first_navigable_child(children)
|
|
122
|
+
return nil unless children&.any?
|
|
123
|
+
|
|
124
|
+
children.each do |child|
|
|
125
|
+
return child[:path] if navigable_path?(child[:path])
|
|
126
|
+
|
|
127
|
+
nested = find_first_navigable_child(child[:children])
|
|
128
|
+
return nested if nested
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
nil
|
|
132
|
+
end
|
|
95
133
|
|
|
96
|
-
|
|
134
|
+
def navigable_path?(path)
|
|
135
|
+
path && !path.empty? && path != "/"
|
|
97
136
|
end
|
|
98
137
|
|
|
99
138
|
def search_in_children(node, path)
|
|
@@ -83,16 +83,23 @@ module Docyard
|
|
|
83
83
|
ERB.new(template).result(binding)
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
-
def render_not_found
|
|
86
|
+
def render_not_found(branding: nil)
|
|
87
|
+
@primary_color = branding&.dig(:primary_color)
|
|
87
88
|
render_error_template(404)
|
|
88
89
|
end
|
|
89
90
|
|
|
90
|
-
def render_server_error(error)
|
|
91
|
+
def render_server_error(error, branding: nil)
|
|
91
92
|
@error_message = error.message
|
|
92
93
|
@backtrace = error.backtrace&.join("\n") || "No backtrace available"
|
|
94
|
+
@primary_color = branding&.dig(:primary_color)
|
|
93
95
|
render_error_template(500)
|
|
94
96
|
end
|
|
95
97
|
|
|
98
|
+
def render_redirect(target_url)
|
|
99
|
+
@target_url = target_url
|
|
100
|
+
render_error_template(:redirect)
|
|
101
|
+
end
|
|
102
|
+
|
|
96
103
|
def render_error_template(status)
|
|
97
104
|
error_template_path = File.join(ERRORS_PATH, "#{status}.html.erb")
|
|
98
105
|
template = File.read(error_template_path)
|
|
@@ -19,7 +19,7 @@ module Docyard
|
|
|
19
19
|
@dev_mode = !sse_port.nil?
|
|
20
20
|
@sidebar_cache = sidebar_cache
|
|
21
21
|
@router = Router.new(docs_path: docs_path)
|
|
22
|
-
@renderer = Renderer.new(base_url:
|
|
22
|
+
@renderer = Renderer.new(base_url: "/", config: config, dev_mode: @dev_mode,
|
|
23
23
|
sse_port: sse_port)
|
|
24
24
|
@asset_handler = AssetHandler.new(public_dir: config&.public_dir || "docs/public")
|
|
25
25
|
@pagefind_handler = PagefindHandler.new(pagefind_path: pagefind_path, config: config)
|
|
@@ -124,7 +124,7 @@ module Docyard
|
|
|
124
124
|
end
|
|
125
125
|
|
|
126
126
|
def render_not_found_page
|
|
127
|
-
html = renderer.render_not_found
|
|
127
|
+
html = renderer.render_not_found(branding: branding_options)
|
|
128
128
|
[Constants::STATUS_NOT_FOUND, { "Content-Type" => Constants::CONTENT_TYPE_HTML }, [html]]
|
|
129
129
|
end
|
|
130
130
|
|
|
@@ -147,7 +147,7 @@ module Docyard
|
|
|
147
147
|
Docyard.logger.error("Request error: #{error.message} [#{request_context}]")
|
|
148
148
|
Docyard.logger.debug(error.backtrace.join("\n"))
|
|
149
149
|
[Constants::STATUS_INTERNAL_ERROR, { "Content-Type" => Constants::CONTENT_TYPE_HTML },
|
|
150
|
-
[renderer.render_server_error(error)]]
|
|
150
|
+
[renderer.render_server_error(error, branding: branding_options)]]
|
|
151
151
|
end
|
|
152
152
|
|
|
153
153
|
def build_request_context(env)
|
|
@@ -76,11 +76,11 @@ body.has-announcement:not(.has-tabs) .layout {
|
|
|
76
76
|
|
|
77
77
|
@media (max-width: 1280px) and (min-width: 1025px) {
|
|
78
78
|
body.has-announcement .layout {
|
|
79
|
-
padding-top: calc(var(--header-height) + var(--tab-bar-height) + var(--announcement-height) +
|
|
79
|
+
padding-top: calc(var(--header-height) + var(--tab-bar-height) + var(--announcement-height) + var(--secondary-header-height));
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
body.has-announcement:not(.has-tabs) .layout {
|
|
83
|
-
padding-top: calc(var(--header-height) + var(--announcement-height) +
|
|
83
|
+
padding-top: calc(var(--header-height) + var(--announcement-height) + var(--secondary-header-height));
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
@@ -91,7 +91,7 @@ body.has-announcement:not(.has-tabs) .layout {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
body.has-announcement .layout {
|
|
94
|
-
padding-top: calc(var(--header-height) + var(--announcement-height) +
|
|
94
|
+
padding-top: calc(var(--header-height) + var(--announcement-height) + var(--secondary-header-height));
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
body.has-announcement .sidebar {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
.has-tabs .content h4[id],
|
|
13
13
|
.has-tabs .content h5[id],
|
|
14
14
|
.has-tabs .content h6[id] {
|
|
15
|
-
scroll-margin-top: calc(var(--header-height) +
|
|
15
|
+
scroll-margin-top: calc(var(--header-height) + var(--tab-bar-height) + var(--spacing-6));
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
@media (max-width: 1280px) and (min-width: 1025px) {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
.content h4[id],
|
|
23
23
|
.content h5[id],
|
|
24
24
|
.content h6[id] {
|
|
25
|
-
scroll-margin-top: calc(var(--header-height) +
|
|
25
|
+
scroll-margin-top: calc(var(--header-height) + var(--secondary-header-height) + var(--spacing-6));
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
|
|
124
124
|
@media (max-width: 1280px) and (min-width: 1025px) {
|
|
125
125
|
.has-tabs .layout {
|
|
126
|
-
padding-top: calc(var(--header-height) + var(--tab-bar-height) +
|
|
126
|
+
padding-top: calc(var(--header-height) + var(--tab-bar-height) + var(--secondary-header-height));
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
.has-tabs .layout {
|
|
146
|
-
padding-top: calc(var(--header-height) +
|
|
146
|
+
padding-top: calc(var(--header-height) + var(--secondary-header-height));
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
.has-tabs .sidebar {
|
|
@@ -257,30 +257,30 @@
|
|
|
257
257
|
|
|
258
258
|
@media (max-width: 1280px) and (min-width: 1025px) {
|
|
259
259
|
.docyard-toc__nav {
|
|
260
|
-
top: calc(var(--header-height) +
|
|
260
|
+
top: calc(var(--header-height) + var(--secondary-header-height) + var(--spacing-2));
|
|
261
261
|
right: var(--spacing-6);
|
|
262
262
|
width: 280px;
|
|
263
|
-
max-height: calc(100vh - var(--header-height) -
|
|
263
|
+
max-height: calc(100vh - var(--header-height) - var(--secondary-header-height) - var(--spacing-8));
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
.has-tabs .docyard-toc__nav {
|
|
267
|
-
top: calc(var(--header-height) + var(--tab-bar-height) +
|
|
268
|
-
max-height: calc(100vh - var(--header-height) - var(--tab-bar-height) -
|
|
267
|
+
top: calc(var(--header-height) + var(--tab-bar-height) + var(--secondary-header-height) + var(--spacing-2));
|
|
268
|
+
max-height: calc(100vh - var(--header-height) - var(--tab-bar-height) - var(--secondary-header-height) - var(--spacing-8));
|
|
269
269
|
}
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
@media (max-width: 1024px) {
|
|
273
273
|
.docyard-toc__nav {
|
|
274
|
-
top: calc(var(--header-height) +
|
|
274
|
+
top: calc(var(--header-height) + var(--secondary-header-height) - 2px);
|
|
275
275
|
left: var(--spacing-4);
|
|
276
276
|
right: var(--spacing-4);
|
|
277
|
-
max-height: calc(100vh - var(--header-height) -
|
|
277
|
+
max-height: calc(100vh - var(--header-height) - var(--secondary-header-height) - var(--spacing-2));
|
|
278
278
|
transform-origin: top center;
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
.secondary-header.shift-up ~ .layout .docyard-toc__nav {
|
|
282
|
-
top: calc(
|
|
283
|
-
max-height: calc(100vh -
|
|
282
|
+
top: calc(var(--secondary-header-height) - 2px);
|
|
283
|
+
max-height: calc(100vh - var(--secondary-header-height) - var(--spacing-2));
|
|
284
284
|
}
|
|
285
285
|
|
|
286
286
|
.docyard-toc__list .docyard-toc__list {
|
|
@@ -292,25 +292,25 @@
|
|
|
292
292
|
/* Adjust TOC dropdown position when announcement banner is visible */
|
|
293
293
|
@media (max-width: 1280px) and (min-width: 1025px) {
|
|
294
294
|
body.has-announcement .docyard-toc__nav {
|
|
295
|
-
top: calc(var(--header-height) + var(--announcement-height) +
|
|
296
|
-
max-height: calc(100vh - var(--header-height) - var(--announcement-height) -
|
|
295
|
+
top: calc(var(--header-height) + var(--announcement-height) + var(--secondary-header-height) + var(--spacing-2));
|
|
296
|
+
max-height: calc(100vh - var(--header-height) - var(--announcement-height) - var(--secondary-header-height) - var(--spacing-8));
|
|
297
297
|
}
|
|
298
298
|
|
|
299
299
|
body.has-announcement.has-tabs .docyard-toc__nav {
|
|
300
|
-
top: calc(var(--header-height) + var(--tab-bar-height) + var(--announcement-height) +
|
|
301
|
-
max-height: calc(100vh - var(--header-height) - var(--tab-bar-height) - var(--announcement-height) -
|
|
300
|
+
top: calc(var(--header-height) + var(--tab-bar-height) + var(--announcement-height) + var(--secondary-header-height) + var(--spacing-2));
|
|
301
|
+
max-height: calc(100vh - var(--header-height) - var(--tab-bar-height) - var(--announcement-height) - var(--secondary-header-height) - var(--spacing-8));
|
|
302
302
|
}
|
|
303
303
|
}
|
|
304
304
|
|
|
305
305
|
@media (max-width: 1024px) {
|
|
306
306
|
body.has-announcement .docyard-toc__nav {
|
|
307
|
-
top: calc(var(--header-height) + var(--announcement-height) +
|
|
308
|
-
max-height: calc(100vh - var(--header-height) - var(--announcement-height) -
|
|
307
|
+
top: calc(var(--header-height) + var(--announcement-height) + var(--secondary-header-height) - 2px);
|
|
308
|
+
max-height: calc(100vh - var(--header-height) - var(--announcement-height) - var(--secondary-header-height) - var(--spacing-2));
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
body.has-announcement .secondary-header.shift-up ~ .layout .docyard-toc__nav {
|
|
312
|
-
top: calc(var(--announcement-height) +
|
|
313
|
-
max-height: calc(100vh - var(--announcement-height) -
|
|
312
|
+
top: calc(var(--announcement-height) + var(--secondary-header-height) - 2px);
|
|
313
|
+
max-height: calc(100vh - var(--announcement-height) - var(--secondary-header-height) - var(--spacing-2));
|
|
314
314
|
}
|
|
315
315
|
}
|
|
316
316
|
|
|
@@ -179,6 +179,15 @@ html:has(.has-tabs) {
|
|
|
179
179
|
display: none;
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
.layout {
|
|
183
|
+
display: flex;
|
|
184
|
+
min-height: calc(100vh - var(--header-height));
|
|
185
|
+
padding-top: var(--header-height);
|
|
186
|
+
width: 100%;
|
|
187
|
+
max-width: var(--layout-max-width);
|
|
188
|
+
margin: 0 auto;
|
|
189
|
+
position: relative;
|
|
190
|
+
}
|
|
182
191
|
|
|
183
192
|
.secondary-header {
|
|
184
193
|
display: none;
|
|
@@ -193,8 +202,8 @@ html:has(.has-tabs) {
|
|
|
193
202
|
top: var(--header-height);
|
|
194
203
|
left: calc(max(0px, (100vw - var(--layout-max-width)) / 2) + var(--sidebar-width));
|
|
195
204
|
right: max(0px, calc((100vw - var(--layout-max-width)) / 2));
|
|
196
|
-
height:
|
|
197
|
-
min-height:
|
|
205
|
+
height: var(--secondary-header-height);
|
|
206
|
+
min-height: var(--secondary-header-height);
|
|
198
207
|
background: var(--background);
|
|
199
208
|
border-bottom: 1px solid var(--border);
|
|
200
209
|
padding: 0 var(--spacing-6);
|
|
@@ -213,7 +222,7 @@ html:has(.has-tabs) {
|
|
|
213
222
|
}
|
|
214
223
|
|
|
215
224
|
.layout {
|
|
216
|
-
padding-top: calc(var(--header-height) +
|
|
225
|
+
padding-top: calc(var(--header-height) + var(--secondary-header-height));
|
|
217
226
|
}
|
|
218
227
|
}
|
|
219
228
|
|
|
@@ -226,8 +235,8 @@ html:has(.has-tabs) {
|
|
|
226
235
|
top: var(--header-height);
|
|
227
236
|
left: 0;
|
|
228
237
|
right: 0;
|
|
229
|
-
height:
|
|
230
|
-
min-height:
|
|
238
|
+
height: var(--secondary-header-height);
|
|
239
|
+
min-height: var(--secondary-header-height);
|
|
231
240
|
background: var(--background);
|
|
232
241
|
border-bottom: 1px solid var(--border);
|
|
233
242
|
padding: 0 var(--spacing-6);
|
|
@@ -278,16 +287,6 @@ html:has(.has-tabs) {
|
|
|
278
287
|
}
|
|
279
288
|
}
|
|
280
289
|
|
|
281
|
-
.layout {
|
|
282
|
-
display: flex;
|
|
283
|
-
min-height: calc(100vh - var(--header-height));
|
|
284
|
-
padding-top: var(--header-height);
|
|
285
|
-
width: 100%;
|
|
286
|
-
max-width: var(--layout-max-width);
|
|
287
|
-
margin: 0 auto;
|
|
288
|
-
position: relative;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
290
|
.sidebar {
|
|
292
291
|
width: var(--sidebar-width);
|
|
293
292
|
height: calc(100vh - var(--header-height));
|
|
@@ -424,7 +423,7 @@ main.content {
|
|
|
424
423
|
}
|
|
425
424
|
|
|
426
425
|
.layout {
|
|
427
|
-
padding-top: calc(var(--header-height) +
|
|
426
|
+
padding-top: calc(var(--header-height) + var(--secondary-header-height));
|
|
428
427
|
flex-direction: column;
|
|
429
428
|
}
|
|
430
429
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<style>
|
|
8
8
|
@font-face {
|
|
9
9
|
font-family: 'Inter';
|
|
10
|
-
src: url('
|
|
10
|
+
src: url('<%= base_url %>_docyard/fonts/Inter-Variable.ttf') format('truetype');
|
|
11
11
|
font-weight: 100 900;
|
|
12
12
|
font-style: normal;
|
|
13
13
|
}
|
|
@@ -31,6 +31,17 @@
|
|
|
31
31
|
--primary-foreground: oklch(0.30 0.05 230);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
<% if @primary_color && (@primary_color[:light] || @primary_color[:dark]) %>
|
|
35
|
+
<% light_color = @primary_color[:light] %>
|
|
36
|
+
<% dark_color = @primary_color[:dark] || light_color %>
|
|
37
|
+
<% if light_color %>
|
|
38
|
+
:root { --primary: <%= light_color %>; }
|
|
39
|
+
.dark { --primary: <%= dark_color %>; }
|
|
40
|
+
<% elsif dark_color %>
|
|
41
|
+
.dark { --primary: <%= dark_color %>; }
|
|
42
|
+
<% end %>
|
|
43
|
+
<% end %>
|
|
44
|
+
|
|
34
45
|
* {
|
|
35
46
|
margin: 0;
|
|
36
47
|
padding: 0;
|
|
@@ -116,7 +127,7 @@
|
|
|
116
127
|
<p class="error-code">404</p>
|
|
117
128
|
<h1 class="error-title">Page not found</h1>
|
|
118
129
|
<p class="error-message">This page doesn't exist or has been moved.</p>
|
|
119
|
-
<a href="
|
|
130
|
+
<a href="<%= base_url %>" class="btn">
|
|
120
131
|
<%= icon(:house_line) %>
|
|
121
132
|
Back to home
|
|
122
133
|
</a>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<style>
|
|
8
8
|
@font-face {
|
|
9
9
|
font-family: 'Inter';
|
|
10
|
-
src: url('
|
|
10
|
+
src: url('<%= base_url %>_docyard/fonts/Inter-Variable.ttf') format('truetype');
|
|
11
11
|
font-weight: 100 900;
|
|
12
12
|
font-style: normal;
|
|
13
13
|
}
|
|
@@ -35,6 +35,17 @@
|
|
|
35
35
|
--destructive: #f87171;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
<% if @primary_color && (@primary_color[:light] || @primary_color[:dark]) %>
|
|
39
|
+
<% light_color = @primary_color[:light] %>
|
|
40
|
+
<% dark_color = @primary_color[:dark] || light_color %>
|
|
41
|
+
<% if light_color %>
|
|
42
|
+
:root { --primary: <%= light_color %>; }
|
|
43
|
+
.dark { --primary: <%= dark_color %>; }
|
|
44
|
+
<% elsif dark_color %>
|
|
45
|
+
.dark { --primary: <%= dark_color %>; }
|
|
46
|
+
<% end %>
|
|
47
|
+
<% end %>
|
|
48
|
+
|
|
38
49
|
* {
|
|
39
50
|
margin: 0;
|
|
40
51
|
padding: 0;
|
|
@@ -166,7 +177,7 @@
|
|
|
166
177
|
<p class="error-code">500</p>
|
|
167
178
|
<h1 class="error-title">Something went wrong</h1>
|
|
168
179
|
<p class="error-message">We encountered an unexpected error. Please try again.</p>
|
|
169
|
-
<a href="
|
|
180
|
+
<a href="<%= base_url %>" class="btn">
|
|
170
181
|
<%= icon(:house_line) %>
|
|
171
182
|
Back to home
|
|
172
183
|
</a>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta http-equiv="refresh" content="0;url=<%= @target_url %>">
|
|
6
|
+
<title>Redirecting...</title>
|
|
7
|
+
<script>window.location.href = "<%= @target_url %>";</script>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<p>Redirecting to <a href="<%= @target_url %>"><%= @target_url %></a>...</p>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
<% end %>
|
|
33
33
|
<% end %>
|
|
34
34
|
|
|
35
|
-
<link rel="preload" href="
|
|
35
|
+
<link rel="preload" href="<%= asset_path('_docyard/fonts/Inter-Variable.ttf') %>" as="font" type="font/ttf" crossorigin>
|
|
36
36
|
|
|
37
37
|
<%= render_partial('_icon_library') %>
|
|
38
38
|
|
data/lib/docyard/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: docyard
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sanif Himani
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-01-
|
|
11
|
+
date: 2026-01-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: cssminify
|
|
@@ -192,8 +192,11 @@ files:
|
|
|
192
192
|
- exe/docyard
|
|
193
193
|
- lib/docyard.rb
|
|
194
194
|
- lib/docyard/build/asset_bundler.rb
|
|
195
|
+
- lib/docyard/build/error_page_generator.rb
|
|
195
196
|
- lib/docyard/build/file_copier.rb
|
|
197
|
+
- lib/docyard/build/file_writer.rb
|
|
196
198
|
- lib/docyard/build/llms_txt_generator.rb
|
|
199
|
+
- lib/docyard/build/root_fallback_generator.rb
|
|
197
200
|
- lib/docyard/build/sitemap_generator.rb
|
|
198
201
|
- lib/docyard/build/static_generator.rb
|
|
199
202
|
- lib/docyard/builder.rb
|
|
@@ -347,6 +350,7 @@ files:
|
|
|
347
350
|
- lib/docyard/templates/config/docyard.yml.erb
|
|
348
351
|
- lib/docyard/templates/errors/404.html.erb
|
|
349
352
|
- lib/docyard/templates/errors/500.html.erb
|
|
353
|
+
- lib/docyard/templates/errors/redirect.html.erb
|
|
350
354
|
- lib/docyard/templates/init/_sidebar.yml
|
|
351
355
|
- lib/docyard/templates/init/docyard.yml
|
|
352
356
|
- lib/docyard/templates/init/pages/components.md
|