docyard 1.0.0 → 1.0.2
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 +23 -1
- data/lib/docyard/build/asset_bundler.rb +23 -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 +22 -19
- data/lib/docyard/navigation/breadcrumb_builder.rb +45 -6
- data/lib/docyard/navigation/page_navigation_builder.rb +5 -3
- data/lib/docyard/navigation/prev_next_builder.rb +4 -3
- data/lib/docyard/rendering/renderer.rb +26 -4
- data/lib/docyard/server/preview_server.rb +11 -3
- data/lib/docyard/server/rack_application.rb +3 -3
- data/lib/docyard/server/static_file_app.rb +20 -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 +13 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 69c121af95a08c379f260d0d444aa4bc5272fdfe2d0699866091885c9f503601
|
|
4
|
+
data.tar.gz: 4550cdcc1d6863b545c4900aac9adb10f14d96a7394ccd3fe4c8924969b278dc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 690e11d0792b71540f09d26fe45dc46d57558b3aad1b3cf80f3e08c592a4056408b6b8a57bfc47f2459feba94e481038f11ccef287534a9f82e557c255728b53
|
|
7
|
+
data.tar.gz: 8baa1a707f2ff94152499b66c19596a57984c5ff763d2cf43390d986030e2e2eea994febac3089766a764ec1d6314a61479fe2caa54434ee5e64c86ade6431e1
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.0.2] - 2026-01-23
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- "Last updated" timestamps now show correct dates when deploying via CI (#129)
|
|
14
|
+
|
|
15
|
+
### Documentation
|
|
16
|
+
- Added git history requirements for accurate timestamps on GitHub Actions, Vercel, and Netlify
|
|
17
|
+
|
|
18
|
+
## [1.0.1] - 2026-01-22
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- **Root Fallback Redirect** - Auto-redirect to first page when no index.md exists (#127)
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- Breadcrumb sections without index pages now link to first navigable child (#126)
|
|
25
|
+
- Error pages (404/500) now use custom branding colors (#125)
|
|
26
|
+
- Base URL now correctly applied to all assets including fonts and search (#124)
|
|
27
|
+
- Dev server no longer applies build.base config, always uses root path (#123)
|
|
28
|
+
|
|
10
29
|
## [1.0.0] - 2026-01-22
|
|
11
30
|
|
|
12
31
|
### Added
|
|
@@ -205,7 +224,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
205
224
|
- Initial gem structure
|
|
206
225
|
- Project scaffolding
|
|
207
226
|
|
|
208
|
-
[Unreleased]: https://github.com/sanifhimani/docyard/compare/
|
|
227
|
+
[Unreleased]: https://github.com/sanifhimani/docyard/compare/v1.0.2...HEAD
|
|
228
|
+
[1.0.2]: https://github.com/sanifhimani/docyard/compare/v1.0.1...v1.0.2
|
|
229
|
+
[1.0.1]: https://github.com/sanifhimani/docyard/compare/v1.0.0...v1.0.1
|
|
230
|
+
[1.0.0]: https://github.com/sanifhimani/docyard/compare/v0.9.0...v1.0.0
|
|
209
231
|
[0.9.0]: https://github.com/sanifhimani/docyard/compare/v0.8.0...v0.9.0
|
|
210
232
|
[0.8.0]: https://github.com/sanifhimani/docyard/compare/v0.7.0...v0.8.0
|
|
211
233
|
[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,16 @@ 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
|
+
base_path_pattern = Regexp.escape(base_url.delete_prefix("/"))
|
|
146
|
+
content.gsub(%r{(<img[^>]*\ssrc=")/(?!_docyard/|#{base_path_pattern})([^"]*")}) do
|
|
147
|
+
"#{Regexp.last_match(1)}#{base_url}#{Regexp.last_match(2)}"
|
|
148
|
+
end
|
|
126
149
|
end
|
|
127
150
|
|
|
128
151
|
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
|
|
@@ -149,7 +155,8 @@ module Docyard
|
|
|
149
155
|
@navigation_builder ||= Navigation::PageNavigationBuilder.new(
|
|
150
156
|
docs_path: docs_path,
|
|
151
157
|
config: config,
|
|
152
|
-
sidebar_cache: sidebar_cache
|
|
158
|
+
sidebar_cache: sidebar_cache,
|
|
159
|
+
base_url: config.build.base
|
|
153
160
|
)
|
|
154
161
|
end
|
|
155
162
|
|
|
@@ -161,18 +168,6 @@ module Docyard
|
|
|
161
168
|
log "Generated: #{output_path}" if verbose
|
|
162
169
|
end
|
|
163
170
|
|
|
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
171
|
def build_sidebar_cache
|
|
177
172
|
@sidebar_cache = Sidebar::Cache.new(
|
|
178
173
|
docs_path: docs_path,
|
|
@@ -194,15 +189,23 @@ module Docyard
|
|
|
194
189
|
end
|
|
195
190
|
|
|
196
191
|
def generate_error_page
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
192
|
+
ErrorPageGenerator.new(
|
|
193
|
+
config: config,
|
|
194
|
+
docs_path: docs_path,
|
|
195
|
+
renderer: build_renderer
|
|
196
|
+
).generate
|
|
200
197
|
log "[✓] Generated 404.html"
|
|
201
198
|
end
|
|
202
199
|
|
|
203
|
-
def
|
|
204
|
-
|
|
205
|
-
|
|
200
|
+
def generate_root_fallback_if_needed
|
|
201
|
+
generator = RootFallbackGenerator.new(
|
|
202
|
+
config: config,
|
|
203
|
+
docs_path: docs_path,
|
|
204
|
+
sidebar_cache: sidebar_cache,
|
|
205
|
+
renderer: build_renderer
|
|
206
|
+
)
|
|
207
|
+
target = generator.generate_if_needed
|
|
208
|
+
log "[✓] Generated root redirect to #{target}" if target
|
|
206
209
|
end
|
|
207
210
|
end
|
|
208
211
|
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)
|
|
@@ -7,10 +7,11 @@ require_relative "breadcrumb_builder"
|
|
|
7
7
|
module Docyard
|
|
8
8
|
module Navigation
|
|
9
9
|
class PageNavigationBuilder
|
|
10
|
-
def initialize(docs_path:, config:, sidebar_cache: nil)
|
|
10
|
+
def initialize(docs_path:, config:, sidebar_cache: nil, base_url: "/")
|
|
11
11
|
@docs_path = docs_path
|
|
12
12
|
@config = config
|
|
13
13
|
@sidebar_cache = sidebar_cache
|
|
14
|
+
@base_url = base_url
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def build(current_path:, markdown:, header_ctas: [], show_sidebar: true)
|
|
@@ -26,7 +27,7 @@ module Docyard
|
|
|
26
27
|
|
|
27
28
|
private
|
|
28
29
|
|
|
29
|
-
attr_reader :docs_path, :config, :sidebar_cache
|
|
30
|
+
attr_reader :docs_path, :config, :sidebar_cache, :base_url
|
|
30
31
|
|
|
31
32
|
def empty_navigation
|
|
32
33
|
{ sidebar_html: "", prev_next_html: "", breadcrumbs: nil }
|
|
@@ -47,7 +48,8 @@ module Docyard
|
|
|
47
48
|
sidebar_tree: sidebar_builder.tree,
|
|
48
49
|
current_path: current_path,
|
|
49
50
|
frontmatter: markdown.frontmatter,
|
|
50
|
-
config: {}
|
|
51
|
+
config: {},
|
|
52
|
+
base_url: base_url
|
|
51
53
|
).to_html
|
|
52
54
|
end
|
|
53
55
|
|
|
@@ -5,13 +5,14 @@ require_relative "../utils/path_resolver"
|
|
|
5
5
|
|
|
6
6
|
module Docyard
|
|
7
7
|
class PrevNextBuilder
|
|
8
|
-
attr_reader :sidebar_tree, :current_path, :frontmatter, :config
|
|
8
|
+
attr_reader :sidebar_tree, :current_path, :frontmatter, :config, :base_url
|
|
9
9
|
|
|
10
|
-
def initialize(sidebar_tree:, current_path:, frontmatter: {}, config: {})
|
|
10
|
+
def initialize(sidebar_tree:, current_path:, frontmatter: {}, config: {}, base_url: "/")
|
|
11
11
|
@sidebar_tree = sidebar_tree
|
|
12
12
|
@current_path = Utils::PathResolver.normalize(current_path)
|
|
13
13
|
@frontmatter = frontmatter
|
|
14
14
|
@config = config
|
|
15
|
+
@base_url = base_url
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
def prev_next_links
|
|
@@ -27,7 +28,7 @@ module Docyard
|
|
|
27
28
|
links = prev_next_links
|
|
28
29
|
return "" if links.nil? || (links[:prev].nil? && links[:next].nil?)
|
|
29
30
|
|
|
30
|
-
Renderer.new.render_partial(
|
|
31
|
+
Renderer.new(base_url: base_url).render_partial(
|
|
31
32
|
"_prev_next", {
|
|
32
33
|
prev: links[:prev],
|
|
33
34
|
next: links[:next],
|
|
@@ -35,7 +35,7 @@ module Docyard
|
|
|
35
35
|
markdown = Markdown.new(raw_content, config: config, file_path: file_path)
|
|
36
36
|
|
|
37
37
|
render(
|
|
38
|
-
content:
|
|
38
|
+
content: process_content_links(markdown.html),
|
|
39
39
|
page_title: markdown.title || Constants::DEFAULT_SITE_TITLE,
|
|
40
40
|
page_description: markdown.description,
|
|
41
41
|
page_og_image: markdown.og_image,
|
|
@@ -51,7 +51,7 @@ module Docyard
|
|
|
51
51
|
def render_for_search(file_path)
|
|
52
52
|
markdown = Markdown.new(File.read(file_path), config: config, file_path: file_path)
|
|
53
53
|
title = markdown.title || Constants::DEFAULT_SITE_TITLE
|
|
54
|
-
content =
|
|
54
|
+
content = process_content_links(markdown.html)
|
|
55
55
|
|
|
56
56
|
<<~HTML
|
|
57
57
|
<!DOCTYPE html>
|
|
@@ -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)
|
|
@@ -163,10 +170,25 @@ module Docyard
|
|
|
163
170
|
@footer_links = footer[:links]
|
|
164
171
|
end
|
|
165
172
|
|
|
173
|
+
def process_content_links(html)
|
|
174
|
+
rewrite_internal_links(strip_md_from_links(html))
|
|
175
|
+
end
|
|
176
|
+
|
|
166
177
|
def strip_md_from_links(html)
|
|
167
178
|
html.gsub(/href="([^"]+)\.md"/, 'href="\1"')
|
|
168
179
|
end
|
|
169
180
|
|
|
181
|
+
def rewrite_internal_links(html)
|
|
182
|
+
return html if base_url == "/"
|
|
183
|
+
|
|
184
|
+
html.gsub(%r{href="(/(?!/)[^"]*)"}) do |_match|
|
|
185
|
+
path = ::Regexp.last_match(1)
|
|
186
|
+
next %(href="#{path}") if path.start_with?(base_url)
|
|
187
|
+
|
|
188
|
+
%(href="#{base_url.chomp('/')}#{path}")
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
170
192
|
def assign_git_info(branding, file_path)
|
|
171
193
|
@show_edit_link = branding[:show_edit_link] && file_path
|
|
172
194
|
@show_last_updated = branding[:show_last_updated] && file_path
|
|
@@ -11,12 +11,13 @@ module Docyard
|
|
|
11
11
|
class PreviewServer
|
|
12
12
|
DEFAULT_PORT = 4000
|
|
13
13
|
|
|
14
|
-
attr_reader :port, :output_dir
|
|
14
|
+
attr_reader :port, :output_dir, :base_url
|
|
15
15
|
|
|
16
16
|
def initialize(port: DEFAULT_PORT)
|
|
17
17
|
@port = port
|
|
18
18
|
@config = Config.load
|
|
19
19
|
@output_dir = File.expand_path(@config.build.output)
|
|
20
|
+
@base_url = normalize_base_url(@config.build.base)
|
|
20
21
|
@launcher = nil
|
|
21
22
|
end
|
|
22
23
|
|
|
@@ -38,12 +39,12 @@ module Docyard
|
|
|
38
39
|
def print_server_info
|
|
39
40
|
Docyard.logger.info("Starting preview server...")
|
|
40
41
|
Docyard.logger.info("* Version: #{Docyard::VERSION}")
|
|
41
|
-
Docyard.logger.info("* Running at: http://localhost:#{port}")
|
|
42
|
+
Docyard.logger.info("* Running at: http://localhost:#{port}#{base_url}")
|
|
42
43
|
Docyard.logger.info("Use Ctrl+C to stop\n")
|
|
43
44
|
end
|
|
44
45
|
|
|
45
46
|
def run_server
|
|
46
|
-
app = StaticFileApp.new(output_dir)
|
|
47
|
+
app = StaticFileApp.new(output_dir, base_path: base_url)
|
|
47
48
|
puma_config = build_puma_config(app)
|
|
48
49
|
log_writer = Puma::LogWriter.strings
|
|
49
50
|
|
|
@@ -64,5 +65,12 @@ module Docyard
|
|
|
64
65
|
config.quiet
|
|
65
66
|
end
|
|
66
67
|
end
|
|
68
|
+
|
|
69
|
+
def normalize_base_url(url)
|
|
70
|
+
return "/" if url.nil? || url.empty?
|
|
71
|
+
|
|
72
|
+
url = "/#{url}" unless url.start_with?("/")
|
|
73
|
+
url.end_with?("/") ? url : "#{url}/"
|
|
74
|
+
end
|
|
67
75
|
end
|
|
68
76
|
end
|
|
@@ -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)
|
|
@@ -4,15 +4,20 @@ require "rack/mime"
|
|
|
4
4
|
|
|
5
5
|
module Docyard
|
|
6
6
|
class StaticFileApp
|
|
7
|
-
def initialize(root)
|
|
7
|
+
def initialize(root, base_path: "/")
|
|
8
8
|
@root = root
|
|
9
|
+
@base_path = base_path.chomp("/")
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
def call(env)
|
|
12
13
|
path = env["PATH_INFO"]
|
|
13
|
-
file_path = File.join(@root, path)
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
return serve_not_found unless path_under_base?(path)
|
|
16
|
+
|
|
17
|
+
relative_path = strip_base_path(path)
|
|
18
|
+
file_path = File.join(@root, relative_path)
|
|
19
|
+
|
|
20
|
+
if relative_path.end_with?("/") || relative_path.empty? || File.directory?(file_path)
|
|
16
21
|
index_path = File.join(file_path, "index.html")
|
|
17
22
|
return serve_file(index_path) if File.file?(index_path)
|
|
18
23
|
elsif File.file?(file_path)
|
|
@@ -24,6 +29,18 @@ module Docyard
|
|
|
24
29
|
|
|
25
30
|
private
|
|
26
31
|
|
|
32
|
+
def path_under_base?(path)
|
|
33
|
+
return true if @base_path.empty?
|
|
34
|
+
|
|
35
|
+
path == @base_path || path.start_with?("#{@base_path}/")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def strip_base_path(path)
|
|
39
|
+
return path if @base_path.empty?
|
|
40
|
+
|
|
41
|
+
path.delete_prefix(@base_path)
|
|
42
|
+
end
|
|
43
|
+
|
|
27
44
|
def serve_file(path)
|
|
28
45
|
content = File.read(path)
|
|
29
46
|
content_type = Rack::Mime.mime_type(File.extname(path), "application/octet-stream")
|
|
@@ -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.2
|
|
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
|
|
@@ -178,7 +178,7 @@ dependencies:
|
|
|
178
178
|
- - "~>"
|
|
179
179
|
- !ruby/object:Gem::Version
|
|
180
180
|
version: '0.18'
|
|
181
|
-
description:
|
|
181
|
+
description: Generate beautiful documentation sites from Markdown
|
|
182
182
|
email:
|
|
183
183
|
- sanifhimani92@gmail.com
|
|
184
184
|
executables:
|
|
@@ -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
|
|
@@ -399,13 +403,16 @@ files:
|
|
|
399
403
|
- lib/docyard/utils/text_formatter.rb
|
|
400
404
|
- lib/docyard/utils/url_helpers.rb
|
|
401
405
|
- lib/docyard/version.rb
|
|
402
|
-
homepage: https://
|
|
406
|
+
homepage: https://docyard.dev
|
|
403
407
|
licenses:
|
|
404
408
|
- MIT
|
|
405
409
|
metadata:
|
|
406
410
|
allowed_push_host: https://rubygems.org
|
|
407
|
-
homepage_uri: https://
|
|
411
|
+
homepage_uri: https://docyard.dev
|
|
408
412
|
source_code_uri: https://github.com/sanifhimani/docyard
|
|
413
|
+
changelog_uri: https://github.com/sanifhimani/docyard/blob/main/CHANGELOG.md
|
|
414
|
+
documentation_uri: https://docyard.dev
|
|
415
|
+
bug_tracker_uri: https://github.com/sanifhimani/docyard/issues
|
|
409
416
|
rubygems_mfa_required: 'true'
|
|
410
417
|
post_install_message:
|
|
411
418
|
rdoc_options: []
|
|
@@ -425,5 +432,5 @@ requirements: []
|
|
|
425
432
|
rubygems_version: 3.5.22
|
|
426
433
|
signing_key:
|
|
427
434
|
specification_version: 4
|
|
428
|
-
summary:
|
|
435
|
+
summary: Generate beautiful documentation sites from Markdown
|
|
429
436
|
test_files: []
|