docyard 1.0.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +43 -1
- data/README.md +56 -11
- data/lib/docyard/build/asset_bundler.rb +19 -29
- data/lib/docyard/build/file_copier.rb +7 -15
- data/lib/docyard/build/llms_txt_generator.rb +0 -2
- data/lib/docyard/build/sitemap_generator.rb +1 -1
- data/lib/docyard/build/social_cards/card_renderer.rb +132 -0
- data/lib/docyard/build/social_cards/doc_card.rb +97 -0
- data/lib/docyard/build/social_cards/homepage_card.rb +98 -0
- data/lib/docyard/build/social_cards_generator.rb +188 -0
- data/lib/docyard/build/static_generator.rb +30 -32
- data/lib/docyard/build/step_runner.rb +90 -0
- data/lib/docyard/build/validator.rb +98 -0
- data/lib/docyard/builder.rb +92 -55
- data/lib/docyard/cli.rb +63 -4
- data/lib/docyard/components/aliases.rb +0 -4
- data/lib/docyard/components/processors/callout_processor.rb +1 -1
- data/lib/docyard/components/processors/code_block_diff_preprocessor.rb +1 -1
- data/lib/docyard/components/processors/code_block_focus_preprocessor.rb +1 -1
- data/lib/docyard/components/processors/code_block_options_preprocessor.rb +2 -2
- data/lib/docyard/components/processors/code_group_processor.rb +1 -1
- data/lib/docyard/components/processors/icon_processor.rb +2 -2
- data/lib/docyard/components/processors/tabs_processor.rb +1 -1
- data/lib/docyard/config/branding_resolver.rb +1 -1
- data/lib/docyard/config/schema/definition.rb +30 -0
- data/lib/docyard/config/schema/sections.rb +62 -0
- data/lib/docyard/config/schema/simple_sections.rb +85 -0
- data/lib/docyard/config/schema.rb +28 -31
- data/lib/docyard/config/type_validators.rb +121 -0
- data/lib/docyard/config/validator.rb +136 -61
- data/lib/docyard/config.rb +4 -14
- data/lib/docyard/customizer.rb +196 -0
- data/lib/docyard/diagnostic.rb +89 -0
- data/lib/docyard/diagnostic_context.rb +48 -0
- data/lib/docyard/doctor/code_block_checker.rb +136 -0
- data/lib/docyard/doctor/component_checker.rb +49 -0
- data/lib/docyard/doctor/component_checkers/abbreviation_checker.rb +74 -0
- data/lib/docyard/doctor/component_checkers/badge_checker.rb +71 -0
- data/lib/docyard/doctor/component_checkers/base.rb +111 -0
- data/lib/docyard/doctor/component_checkers/callout_checker.rb +34 -0
- data/lib/docyard/doctor/component_checkers/cards_checker.rb +57 -0
- data/lib/docyard/doctor/component_checkers/code_group_checker.rb +47 -0
- data/lib/docyard/doctor/component_checkers/details_checker.rb +51 -0
- data/lib/docyard/doctor/component_checkers/icon_checker.rb +36 -0
- data/lib/docyard/doctor/component_checkers/image_attrs_checker.rb +46 -0
- data/lib/docyard/doctor/component_checkers/space_after_colons_checker.rb +45 -0
- data/lib/docyard/doctor/component_checkers/steps_checker.rb +35 -0
- data/lib/docyard/doctor/component_checkers/tabs_checker.rb +35 -0
- data/lib/docyard/doctor/component_checkers/tooltip_checker.rb +67 -0
- data/lib/docyard/doctor/component_checkers/unknown_type_checker.rb +34 -0
- data/lib/docyard/doctor/config_checker.rb +19 -0
- data/lib/docyard/doctor/config_fixer.rb +87 -0
- data/lib/docyard/doctor/content_checker.rb +164 -0
- data/lib/docyard/doctor/file_scanner.rb +113 -0
- data/lib/docyard/doctor/image_checker.rb +103 -0
- data/lib/docyard/doctor/link_checker.rb +91 -0
- data/lib/docyard/doctor/markdown_fixer.rb +62 -0
- data/lib/docyard/doctor/orphan_checker.rb +82 -0
- data/lib/docyard/doctor/reporter.rb +152 -0
- data/lib/docyard/doctor/sidebar_checker.rb +127 -0
- data/lib/docyard/doctor/sidebar_fixer.rb +47 -0
- data/lib/docyard/doctor.rb +178 -0
- data/lib/docyard/editor_launcher.rb +119 -0
- data/lib/docyard/errors.rb +0 -49
- data/lib/docyard/initializer.rb +32 -39
- data/lib/docyard/navigation/sidebar/local_config_loader.rb +44 -21
- data/lib/docyard/rendering/icon_helpers.rb +1 -3
- data/lib/docyard/rendering/markdown.rb +4 -0
- data/lib/docyard/rendering/og_helpers.rb +19 -1
- data/lib/docyard/rendering/renderer.rb +10 -1
- data/lib/docyard/search/build_indexer.rb +45 -25
- data/lib/docyard/search/dev_indexer.rb +12 -25
- data/lib/docyard/search/pagefind_binary.rb +185 -0
- data/lib/docyard/search/pagefind_support.rb +9 -7
- data/lib/docyard/server/asset_handler.rb +28 -2
- data/lib/docyard/server/dev_server.rb +56 -13
- data/lib/docyard/server/error_overlay.rb +73 -0
- data/lib/docyard/server/file_watcher.rb +10 -6
- data/lib/docyard/server/page_diagnostics.rb +27 -0
- data/lib/docyard/server/preview_server.rb +17 -13
- data/lib/docyard/server/rack_application.rb +65 -4
- data/lib/docyard/server/resolution_result.rb +0 -4
- data/lib/docyard/templates/assets/css/error-overlay.css +669 -0
- data/lib/docyard/templates/assets/css/variables.css +1 -7
- data/lib/docyard/templates/assets/fonts/Inter-Variable.woff2 +0 -0
- data/lib/docyard/templates/assets/js/components/abbreviation.js +20 -11
- data/lib/docyard/templates/assets/js/components/code-block.js +8 -3
- data/lib/docyard/templates/assets/js/components/code-group.js +20 -3
- data/lib/docyard/templates/assets/js/components/file-tree.js +9 -3
- data/lib/docyard/templates/assets/js/components/heading-anchor.js +71 -72
- data/lib/docyard/templates/assets/js/components/lightbox.js +10 -3
- data/lib/docyard/templates/assets/js/components/relative-time.js +42 -0
- data/lib/docyard/templates/assets/js/components/tabs.js +8 -3
- data/lib/docyard/templates/assets/js/components/tooltip.js +32 -23
- data/lib/docyard/templates/assets/js/error-overlay.js +547 -0
- data/lib/docyard/templates/assets/js/hot-reload.js +77 -9
- data/lib/docyard/templates/errors/404.html.erb +1 -1
- data/lib/docyard/templates/errors/500.html.erb +1 -1
- data/lib/docyard/templates/partials/_head.html.erb +5 -1
- data/lib/docyard/templates/partials/_page_actions.html.erb +1 -1
- data/lib/docyard/templates/partials/_scripts.html.erb +3 -0
- data/lib/docyard/ui.rb +80 -0
- data/lib/docyard/utils/logging.rb +5 -1
- data/lib/docyard/utils/text_formatter.rb +0 -6
- data/lib/docyard/version.rb +1 -1
- data/lib/docyard.rb +6 -0
- metadata +53 -25
- data/lib/docyard/config/key_validator.rb +0 -30
- data/lib/docyard/config/validation_helpers.rb +0 -83
- data/lib/docyard/config/validators/navigation.rb +0 -43
- data/lib/docyard/config/validators/section.rb +0 -114
- data/lib/docyard/templates/assets/fonts/Inter-Variable.ttf +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fe9582d35d963462ff818674735f3b131607368f6adc6fdb70eac6152365efe8
|
|
4
|
+
data.tar.gz: a8e8b424702b4ca3ff2726a938ea0696f0b15bed3d82f449f803f64923bbbafc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4c6ae9f9e88f186d43aa13ef62c2c96d3c4ed65e0320558413df73915bd33478b673dae25dc4f3e3fdf6b0fe627ca595a8fc53cded6186ed3afb03ae08ef7f5d
|
|
7
|
+
data.tar.gz: 17ad431713b55f78b87a67b5de17db1e58c949895a08ff9d31b6f658aa277a8df8cab84f3d3ee178f9c70a9eb30557089fc0b732e2793b6c9b3c8054bf513b1c
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.0] - 2026-02-03
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Social Cards** - Auto-generate Open Graph images for social sharing with `social_cards.enabled: true` config (#146)
|
|
14
|
+
- **Customize Command** - Generate theme customization files with `docyard customize` for custom CSS variables and scripts (#147)
|
|
15
|
+
- **Pagefind Standalone Binary** - Download and cache Pagefind binary in `~/.docyard/bin/` for faster, more reliable search indexing without npx (#148)
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- **Hot Reload JS Reinitialization** - Components now properly reinitialize when content changes during development (#149)
|
|
19
|
+
|
|
20
|
+
### Documentation
|
|
21
|
+
- Added new pages: Abbreviations component, Docs for AI (llms.txt)
|
|
22
|
+
- Removed Troubleshooting page
|
|
23
|
+
- Removed unsupported bluesky platform (33 platforms now supported)
|
|
24
|
+
|
|
25
|
+
## [1.1.0] - 2026-01-30
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- **Doctor Command** - Health check CLI with `docyard doctor` to find broken links, missing images, orphan pages, and config issues (#135)
|
|
29
|
+
- **Auto-fix** - Automatically fix common issues with `docyard doctor --fix` including typos, string booleans, and missing slashes (#135)
|
|
30
|
+
- **Config Validation** - Validate `docyard.yml` and `_sidebar.yml` with helpful error messages and suggestions (#138)
|
|
31
|
+
- **Dev Server Error Overlay** - Bottom sheet overlay showing errors with syntax-highlighted code snippets, line numbers, and clickable file paths (#143)
|
|
32
|
+
- **Strict Mode** - Fail builds on validation errors with `--strict` flag or `build.strict` config (#143)
|
|
33
|
+
- **Colored CLI Output** - ANSI colors for better readability with `--no-color` flag and `NO_COLOR` env support (#142)
|
|
34
|
+
- **CLI Progress Spinners** - Animated spinners and step-by-step progress display for build and serve commands (#136)
|
|
35
|
+
- **Verbose Timing Breakdown** - Per-page timing in `docyard build --verbose` output (#141)
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
- "Last updated" timestamps now calculate relative time client-side for accurate display (#134)
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
- Build output format updated with progress indicators (#136, #141)
|
|
42
|
+
- Config validation now runs on every build (#138)
|
|
43
|
+
|
|
44
|
+
### Documentation
|
|
45
|
+
- Added `docyard doctor` command reference
|
|
46
|
+
- Added strict mode documentation to CLI and configuration references
|
|
47
|
+
- Added error overlay mention to dev server documentation
|
|
48
|
+
- Updated build output examples to match current format
|
|
49
|
+
|
|
10
50
|
## [1.0.2] - 2026-01-23
|
|
11
51
|
|
|
12
52
|
### Fixed
|
|
@@ -224,7 +264,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
224
264
|
- Initial gem structure
|
|
225
265
|
- Project scaffolding
|
|
226
266
|
|
|
227
|
-
[Unreleased]: https://github.com/sanifhimani/docyard/compare/v1.0
|
|
267
|
+
[Unreleased]: https://github.com/sanifhimani/docyard/compare/v1.2.0...HEAD
|
|
268
|
+
[1.2.0]: https://github.com/sanifhimani/docyard/compare/v1.1.0...v1.2.0
|
|
269
|
+
[1.1.0]: https://github.com/sanifhimani/docyard/compare/v1.0.2...v1.1.0
|
|
228
270
|
[1.0.2]: https://github.com/sanifhimani/docyard/compare/v1.0.1...v1.0.2
|
|
229
271
|
[1.0.1]: https://github.com/sanifhimani/docyard/compare/v1.0.0...v1.0.1
|
|
230
272
|
[1.0.0]: https://github.com/sanifhimani/docyard/compare/v0.9.0...v1.0.0
|
data/README.md
CHANGED
|
@@ -1,28 +1,73 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://docyard.dev">
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/public/logo-dark.svg">
|
|
5
|
+
<img src="docs/public/logo.svg" height="60" alt="Docyard">
|
|
6
|
+
</picture>
|
|
7
|
+
</a>
|
|
8
|
+
</p>
|
|
2
9
|
|
|
3
|
-
|
|
4
|
-
|
|
10
|
+
<p align="center">
|
|
11
|
+
Markdown to docs in seconds. No Node.js required.
|
|
12
|
+
</p>
|
|
5
13
|
|
|
6
|
-
|
|
14
|
+
<p align="center">
|
|
15
|
+
<a href="https://github.com/sanifhimani/docyard/actions/workflows/ci.yml"><img src="https://github.com/sanifhimani/docyard/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
16
|
+
<a href="https://badge.fury.io/rb/docyard"><img src="https://badge.fury.io/rb/docyard.svg" alt="Gem Version"></a>
|
|
17
|
+
<a href="https://github.com/sanifhimani/docyard/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
|
|
18
|
+
</p>
|
|
7
19
|
|
|
8
|
-
|
|
20
|
+
<p align="center">
|
|
21
|
+
<a href="https://docyard.dev">Docs</a> ·
|
|
22
|
+
<a href="https://docyard.dev/getting-started/quickstart">Quickstart</a> ·
|
|
23
|
+
<a href="https://github.com/sanifhimani/docyard/blob/main/CHANGELOG.md">Changelog</a>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Install
|
|
9
29
|
|
|
10
30
|
```bash
|
|
11
31
|
gem install docyard
|
|
12
|
-
docyard init
|
|
13
|
-
cd my-docs
|
|
32
|
+
docyard init
|
|
14
33
|
docyard serve
|
|
15
34
|
```
|
|
16
35
|
|
|
17
|
-
Open
|
|
36
|
+
Open `localhost:4200`. Edits reload instantly.
|
|
37
|
+
|
|
38
|
+
## Example
|
|
39
|
+
|
|
40
|
+
```markdown
|
|
41
|
+
:::note
|
|
42
|
+
Requires Ruby 3.2 or higher.
|
|
43
|
+
:::
|
|
44
|
+
|
|
45
|
+
:::tabs
|
|
46
|
+
== macOS
|
|
47
|
+
brew install ruby
|
|
48
|
+
|
|
49
|
+
== Linux
|
|
50
|
+
sudo apt install ruby-full
|
|
51
|
+
:::
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Callouts, tabs, steps, cards, code groups, accordions, and more. [See all components](https://docyard.dev/write-content/components)
|
|
55
|
+
|
|
56
|
+
## Build
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
docyard build
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Static HTML, search index, sitemap, and social cards in `dist/`. Deploy anywhere.
|
|
18
63
|
|
|
19
|
-
##
|
|
64
|
+
## Docs
|
|
20
65
|
|
|
21
|
-
|
|
66
|
+
[docyard.dev](https://docyard.dev) is built with Docyard.
|
|
22
67
|
|
|
23
68
|
## Contributing
|
|
24
69
|
|
|
25
|
-
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
70
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
26
71
|
|
|
27
72
|
## License
|
|
28
73
|
|
|
@@ -7,6 +7,8 @@ require "digest"
|
|
|
7
7
|
module Docyard
|
|
8
8
|
module Build
|
|
9
9
|
class AssetBundler
|
|
10
|
+
include Utils::UrlHelpers
|
|
11
|
+
|
|
10
12
|
ASSETS_PATH = File.join(__dir__, "..", "templates", "assets")
|
|
11
13
|
|
|
12
14
|
attr_reader :config, :verbose
|
|
@@ -17,23 +19,20 @@ module Docyard
|
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def bundle
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
css_hash = bundle_css
|
|
23
|
-
js_hash = bundle_js
|
|
22
|
+
css_hash, css_size = bundle_css
|
|
23
|
+
js_hash, js_size = bundle_js
|
|
24
24
|
|
|
25
25
|
update_html_references(css_hash, js_hash)
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
[css_size, js_size]
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
private
|
|
31
31
|
|
|
32
32
|
def bundle_css
|
|
33
|
-
log " Bundling CSS..."
|
|
34
|
-
|
|
35
33
|
main_css = File.read(File.join(ASSETS_PATH, "css", "main.css"))
|
|
36
34
|
css_content = resolve_css_imports(main_css)
|
|
35
|
+
css_content = append_custom_css(css_content)
|
|
37
36
|
minified = CSSminify.compress(css_content)
|
|
38
37
|
minified = fix_calc_whitespace(minified)
|
|
39
38
|
minified = fix_css_math_functions(minified)
|
|
@@ -41,9 +40,8 @@ module Docyard
|
|
|
41
40
|
hash = generate_hash(minified)
|
|
42
41
|
|
|
43
42
|
write_bundled_asset(minified, hash, "css")
|
|
44
|
-
log_compression_stats(css_content, minified, "CSS")
|
|
45
43
|
|
|
46
|
-
hash
|
|
44
|
+
[hash, minified.bytesize]
|
|
47
45
|
end
|
|
48
46
|
|
|
49
47
|
def fix_calc_whitespace(css)
|
|
@@ -86,19 +84,17 @@ module Docyard
|
|
|
86
84
|
end
|
|
87
85
|
|
|
88
86
|
def bundle_js
|
|
89
|
-
log " Bundling JS..."
|
|
90
|
-
|
|
91
87
|
theme_js = File.read(File.join(ASSETS_PATH, "js", "theme.js"))
|
|
92
88
|
components_js = concatenate_component_js
|
|
93
|
-
|
|
89
|
+
custom_js = load_custom_js
|
|
90
|
+
js_content = [theme_js, components_js, custom_js].compact.join("\n")
|
|
94
91
|
minified = Terser.compile(js_content)
|
|
95
92
|
minified = replace_js_asset_urls(minified)
|
|
96
93
|
hash = generate_hash(minified)
|
|
97
94
|
|
|
98
95
|
write_bundled_asset(minified, hash, "js")
|
|
99
|
-
log_compression_stats(js_content, minified, "JS")
|
|
100
96
|
|
|
101
|
-
hash
|
|
97
|
+
[hash, minified.bytesize]
|
|
102
98
|
end
|
|
103
99
|
|
|
104
100
|
def replace_js_asset_urls(js_content)
|
|
@@ -127,8 +123,6 @@ module Docyard
|
|
|
127
123
|
content = replace_asset_references(File.read(file), css_hash, js_hash, base_url)
|
|
128
124
|
File.write(file, content)
|
|
129
125
|
end
|
|
130
|
-
|
|
131
|
-
log " [✓] Updated asset references in #{html_files.size} HTML files"
|
|
132
126
|
end
|
|
133
127
|
|
|
134
128
|
def replace_asset_references(content, css_hash, js_hash, base_url)
|
|
@@ -155,22 +149,18 @@ module Docyard
|
|
|
155
149
|
File.write(output_path, content)
|
|
156
150
|
end
|
|
157
151
|
|
|
158
|
-
def
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
reduction = (((original_size - minified_size) / original_size) * 100).round(0)
|
|
162
|
-
log " [✓] #{label}: #{original_size} KB -> #{minified_size} KB (-#{reduction}%)"
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
def normalize_base_url(url)
|
|
166
|
-
return "/" if url.nil? || url.empty? || url == "/"
|
|
152
|
+
def append_custom_css(css_content)
|
|
153
|
+
custom_path = File.join(config.source, "_custom", "styles.css")
|
|
154
|
+
return css_content unless File.exist?(custom_path)
|
|
167
155
|
|
|
168
|
-
|
|
169
|
-
url.end_with?("/") ? url : "#{url}/"
|
|
156
|
+
"#{css_content}\n#{File.read(custom_path)}"
|
|
170
157
|
end
|
|
171
158
|
|
|
172
|
-
def
|
|
173
|
-
|
|
159
|
+
def load_custom_js
|
|
160
|
+
custom_path = File.join(config.source, "_custom", "scripts.js")
|
|
161
|
+
return nil unless File.exist?(custom_path)
|
|
162
|
+
|
|
163
|
+
File.read(custom_path)
|
|
174
164
|
end
|
|
175
165
|
end
|
|
176
166
|
end
|
|
@@ -13,14 +13,12 @@ module Docyard
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def copy
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
@copied_files = []
|
|
18
17
|
count = 0
|
|
19
18
|
count += copy_public_files
|
|
20
19
|
count += copy_branding_assets
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
count
|
|
20
|
+
details = verbose ? @copied_files : nil
|
|
21
|
+
[count, details]
|
|
24
22
|
end
|
|
25
23
|
|
|
26
24
|
private
|
|
@@ -32,7 +30,6 @@ module Docyard
|
|
|
32
30
|
files = find_files_in_dir(public_dir)
|
|
33
31
|
files.each { |file| copy_single_file(file, "#{public_dir}/", config.build.output) }
|
|
34
32
|
|
|
35
|
-
log "[✓] Copied #{files.size} public files from #{public_dir}/" if files.any?
|
|
36
33
|
files.size
|
|
37
34
|
end
|
|
38
35
|
|
|
@@ -47,14 +44,13 @@ module Docyard
|
|
|
47
44
|
FileUtils.mkdir_p(File.dirname(dest_path))
|
|
48
45
|
FileUtils.cp(file, dest_path)
|
|
49
46
|
|
|
50
|
-
|
|
47
|
+
@copied_files << relative_path if verbose
|
|
51
48
|
end
|
|
52
49
|
|
|
53
50
|
def copy_branding_assets
|
|
54
51
|
count = 0
|
|
55
52
|
count += copy_default_branding_assets
|
|
56
53
|
count += copy_user_branding_assets
|
|
57
|
-
log "[✓] Copied #{count} branding assets" if count.positive?
|
|
58
54
|
count
|
|
59
55
|
end
|
|
60
56
|
|
|
@@ -80,7 +76,7 @@ module Docyard
|
|
|
80
76
|
dest_path = File.join(config.build.output, DOCYARD_OUTPUT_DIR, filename)
|
|
81
77
|
FileUtils.mkdir_p(File.dirname(dest_path))
|
|
82
78
|
FileUtils.cp(source_path, dest_path)
|
|
83
|
-
|
|
79
|
+
@copied_files << "#{filename} (#{label})" if verbose
|
|
84
80
|
1
|
|
85
81
|
end
|
|
86
82
|
|
|
@@ -96,7 +92,7 @@ module Docyard
|
|
|
96
92
|
dest_path = File.join(config.build.output, DOCYARD_OUTPUT_DIR, "fonts", File.basename(font_file))
|
|
97
93
|
FileUtils.mkdir_p(File.dirname(dest_path))
|
|
98
94
|
FileUtils.cp(font_file, dest_path)
|
|
99
|
-
|
|
95
|
+
@copied_files << "#{File.basename(font_file)} (font)" if verbose
|
|
100
96
|
1
|
|
101
97
|
end
|
|
102
98
|
|
|
@@ -122,13 +118,9 @@ module Docyard
|
|
|
122
118
|
FileUtils.mkdir_p(File.dirname(dest_path))
|
|
123
119
|
FileUtils.cp(full_path, dest_path)
|
|
124
120
|
|
|
125
|
-
|
|
121
|
+
@copied_files << "#{asset_path} (user branding)" if verbose
|
|
126
122
|
1
|
|
127
123
|
end
|
|
128
|
-
|
|
129
|
-
def log(message)
|
|
130
|
-
Docyard.logger.info(message)
|
|
131
|
-
end
|
|
132
124
|
end
|
|
133
125
|
end
|
|
134
126
|
end
|
|
@@ -50,7 +50,6 @@ module Docyard
|
|
|
50
50
|
def generate_llms_txt(pages)
|
|
51
51
|
output_path = File.join(config.build.output, "llms.txt")
|
|
52
52
|
File.write(output_path, build_llms_txt_content(pages))
|
|
53
|
-
Docyard.logger.info("[✓] Generated llms.txt (#{pages.size} pages)")
|
|
54
53
|
end
|
|
55
54
|
|
|
56
55
|
def build_llms_txt_content(pages)
|
|
@@ -70,7 +69,6 @@ module Docyard
|
|
|
70
69
|
def generate_llms_full_txt(pages)
|
|
71
70
|
output_path = File.join(config.build.output, "llms-full.txt")
|
|
72
71
|
File.write(output_path, build_llms_full_txt_content(pages))
|
|
73
|
-
Docyard.logger.info("[✓] Generated llms-full.txt")
|
|
74
72
|
end
|
|
75
73
|
|
|
76
74
|
def build_llms_full_txt_content(pages)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module Build
|
|
5
|
+
module SocialCards
|
|
6
|
+
class CardRenderer
|
|
7
|
+
VIPS_INSTALL_INSTRUCTIONS = <<~MSG.strip
|
|
8
|
+
Social cards require libvips to be installed.
|
|
9
|
+
|
|
10
|
+
Install libvips:
|
|
11
|
+
macOS: brew install vips
|
|
12
|
+
Ubuntu: sudo apt install libvips-dev
|
|
13
|
+
Fedora: sudo dnf install vips-devel
|
|
14
|
+
|
|
15
|
+
Then run: bundle install
|
|
16
|
+
|
|
17
|
+
Or disable social cards in docyard.yml:
|
|
18
|
+
social_cards:
|
|
19
|
+
enabled: false
|
|
20
|
+
MSG
|
|
21
|
+
WIDTH = 1200
|
|
22
|
+
HEIGHT = 630
|
|
23
|
+
BACKGROUND_COLOR = "#121212"
|
|
24
|
+
DEFAULT_BRAND_COLOR = "#22D3EE"
|
|
25
|
+
WHITE = "#FFFFFF"
|
|
26
|
+
GRAY = "#71717A"
|
|
27
|
+
|
|
28
|
+
PADDING = 88
|
|
29
|
+
LOGO_BOTTOM_OFFSET = 88
|
|
30
|
+
|
|
31
|
+
LOGO_ICON_WIDTH = 40
|
|
32
|
+
LOGO_ICON_HEIGHT = 58
|
|
33
|
+
LOGO_TEXT_SIZE = 28
|
|
34
|
+
LOGO_GAP = 16
|
|
35
|
+
|
|
36
|
+
attr_reader :config
|
|
37
|
+
|
|
38
|
+
def initialize(config)
|
|
39
|
+
@config = config
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def render(output_path)
|
|
43
|
+
svg_content = build_svg
|
|
44
|
+
save_as_png(svg_content, output_path)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
protected
|
|
48
|
+
|
|
49
|
+
def build_svg
|
|
50
|
+
<<~SVG
|
|
51
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="#{WIDTH}" height="#{HEIGHT}" viewBox="0 0 #{WIDTH} #{HEIGHT}">
|
|
52
|
+
<defs>
|
|
53
|
+
<style>
|
|
54
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap');
|
|
55
|
+
.inter { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
|
|
56
|
+
</style>
|
|
57
|
+
</defs>
|
|
58
|
+
<rect width="#{WIDTH}" height="#{HEIGHT}" fill="#{BACKGROUND_COLOR}"/>
|
|
59
|
+
#{content_svg}
|
|
60
|
+
#{logo_svg}
|
|
61
|
+
</svg>
|
|
62
|
+
SVG
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def content_svg
|
|
66
|
+
raise NotImplementedError, "Subclasses must implement content_svg"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def logo_svg
|
|
70
|
+
site_title = escape_xml(config.title || "Docyard")
|
|
71
|
+
logo_y = HEIGHT - LOGO_BOTTOM_OFFSET
|
|
72
|
+
logo_x, text_anchor = logo_position_and_anchor
|
|
73
|
+
|
|
74
|
+
<<~SVG
|
|
75
|
+
<g transform="translate(#{logo_x}, #{logo_y})">
|
|
76
|
+
#{logo_icon_svg(0, -LOGO_ICON_HEIGHT)}
|
|
77
|
+
<text x="#{LOGO_ICON_WIDTH + LOGO_GAP}" y="-#{(LOGO_ICON_HEIGHT / 2) - 10}" class="inter" font-size="#{LOGO_TEXT_SIZE}" font-weight="700" fill="#{WHITE}" text-anchor="#{text_anchor}">#{site_title}</text>
|
|
78
|
+
</g>
|
|
79
|
+
SVG
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def logo_position_and_anchor
|
|
83
|
+
[PADDING, "start"]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def logo_icon_svg(x_offset, y_offset)
|
|
87
|
+
color = brand_color
|
|
88
|
+
scale = LOGO_ICON_WIDTH.to_f / 531
|
|
89
|
+
<<~SVG
|
|
90
|
+
<g transform="translate(#{x_offset}, #{y_offset}) scale(#{scale})">
|
|
91
|
+
<path fill="#{color}" d="M359.643 59.1798C402.213 89.4398 449.713 123.6 502.063 160.99C510.793 167.23 515.873 170.31 519.293 178.05C523.253 187.02 521.733 198.11 515.883 205.77C513.77 208.536 510.93 211.2 507.363 213.76C379.643 305.353 309.413 355.73 296.673 364.89C287.987 371.136 282.07 374.8 278.923 375.88C269.703 379.026 260.263 378.636 250.603 374.71C248.243 373.75 244.497 371.416 239.363 367.71C199.963 339.29 177.32 322.99 171.433 318.81C128.863 288.54 81.3733 254.39 29.0233 216.99C20.2833 210.75 15.2033 207.67 11.7833 199.93C7.82332 190.96 9.34332 179.87 15.1933 172.21C17.3067 169.443 20.1467 166.78 23.7133 164.22C151.433 72.6264 221.663 22.2498 234.403 13.0898C243.09 6.84309 249.007 3.17976 252.153 2.09976C261.373 -1.04691 270.813 -0.656912 280.473 3.26976C282.833 4.22976 286.58 6.56309 291.713 10.2698C331.113 38.6898 353.757 54.9931 359.643 59.1798Z"/>
|
|
92
|
+
<path fill="#{WHITE}" d="M467.383 298.01C483.943 286.23 505.033 289.93 519.063 303.51C524.457 308.723 528.033 314.713 529.793 321.48C530.433 323.92 530.733 330.946 530.693 342.56C530.647 356.206 530.657 427.233 530.723 555.64C530.723 566.633 530.513 573 530.093 574.74C527.033 587.29 518.333 592.61 506.693 601.06C504.313 602.786 430.877 656.346 286.383 761.74C275.623 769.59 261.793 770.79 250.113 764.36C249.18 763.846 245.86 761.513 240.153 757.36C150.56 692.066 74.8667 637.046 13.0733 592.3C6.70001 587.68 2.65667 581.73 0.943337 574.45C0.316671 571.783 0.00333476 564.803 0.00333476 553.51C-0.00333191 421.323 -4.06895e-06 348.98 0.0133293 336.48C0.0133293 332.84 -0.0766665 327.18 0.783334 323.18C4.59333 305.51 20.1033 293.29 37.4533 291.15C42.9467 290.476 48.8667 291.276 55.2133 293.55C58.28 294.643 63.3533 297.8 70.4333 303.02C75.98 307.113 82.4433 311.78 89.8233 317.02C128.563 344.526 178.703 380.303 240.243 424.35C242.73 426.13 245.853 428.246 249.613 430.7C257.443 435.8 268.453 436.24 277.213 433.14C279.8 432.22 284.54 429.283 291.433 424.33C394.46 350.276 453.11 308.17 467.383 298.01Z"/>
|
|
93
|
+
</g>
|
|
94
|
+
SVG
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def brand_color
|
|
98
|
+
color = config.branding.color
|
|
99
|
+
if color.is_a?(Hash)
|
|
100
|
+
color["dark"] || color["light"] || DEFAULT_BRAND_COLOR
|
|
101
|
+
elsif color.is_a?(String) && !color.strip.empty?
|
|
102
|
+
color.strip
|
|
103
|
+
else
|
|
104
|
+
DEFAULT_BRAND_COLOR
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def escape_xml(text)
|
|
109
|
+
text.to_s
|
|
110
|
+
.gsub("&", "&")
|
|
111
|
+
.gsub("<", "<")
|
|
112
|
+
.gsub(">", ">")
|
|
113
|
+
.gsub('"', """)
|
|
114
|
+
.gsub("'", "'")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def save_as_png(svg_content, output_path)
|
|
118
|
+
require_vips!
|
|
119
|
+
FileUtils.mkdir_p(File.dirname(output_path))
|
|
120
|
+
image = Vips::Image.svgload_buffer(svg_content, dpi: 96)
|
|
121
|
+
image.write_to_file(output_path, compression: 9, palette: true)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def require_vips!
|
|
125
|
+
require "vips"
|
|
126
|
+
rescue LoadError
|
|
127
|
+
raise Docyard::Error, VIPS_INSTALL_INSTRUCTIONS
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "card_renderer"
|
|
4
|
+
|
|
5
|
+
module Docyard
|
|
6
|
+
module Build
|
|
7
|
+
module SocialCards
|
|
8
|
+
class DocCard < CardRenderer
|
|
9
|
+
SECTION_LABEL_SIZE = 26
|
|
10
|
+
TITLE_SIZE = 92
|
|
11
|
+
DESCRIPTION_SIZE = 30
|
|
12
|
+
|
|
13
|
+
SECTION_TO_TITLE_GAP = 16
|
|
14
|
+
TITLE_TO_DESC_GAP = 24
|
|
15
|
+
|
|
16
|
+
LOGO_AREA_HEIGHT = 160
|
|
17
|
+
|
|
18
|
+
TITLE_MAX_CHARS = 22
|
|
19
|
+
DESCRIPTION_MAX_CHARS = 70
|
|
20
|
+
|
|
21
|
+
def initialize(config, title:, section: nil, description: nil)
|
|
22
|
+
super(config)
|
|
23
|
+
@title = truncate_text(title, TITLE_MAX_CHARS)
|
|
24
|
+
@section = section
|
|
25
|
+
@description = truncate_text(description, DESCRIPTION_MAX_CHARS)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
protected
|
|
29
|
+
|
|
30
|
+
def content_svg
|
|
31
|
+
start_y = calculate_start_y
|
|
32
|
+
elements = []
|
|
33
|
+
y_pos = start_y
|
|
34
|
+
|
|
35
|
+
if @section && !@section.empty?
|
|
36
|
+
elements << section_svg(y_pos)
|
|
37
|
+
y_pos += SECTION_TO_TITLE_GAP + TITLE_SIZE
|
|
38
|
+
else
|
|
39
|
+
y_pos += TITLE_SIZE
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
elements << title_svg(y_pos)
|
|
43
|
+
|
|
44
|
+
if @description && !@description.empty?
|
|
45
|
+
y_pos += TITLE_TO_DESC_GAP + DESCRIPTION_SIZE
|
|
46
|
+
elements << description_svg(y_pos)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
elements.join("\n")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def calculate_start_y
|
|
55
|
+
total_height = calculate_content_height
|
|
56
|
+
available_height = HEIGHT - LOGO_AREA_HEIGHT
|
|
57
|
+
(available_height - total_height) / 2
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def calculate_content_height
|
|
61
|
+
height = TITLE_SIZE
|
|
62
|
+
height += SECTION_LABEL_SIZE + SECTION_TO_TITLE_GAP if @section && !@section.empty?
|
|
63
|
+
height += TITLE_TO_DESC_GAP + DESCRIPTION_SIZE if @description && !@description.empty?
|
|
64
|
+
height
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def section_svg(y_pos)
|
|
68
|
+
section_text = escape_xml(@section.upcase)
|
|
69
|
+
<<~SVG
|
|
70
|
+
<text x="#{PADDING}" y="#{y_pos}" class="inter" font-size="#{SECTION_LABEL_SIZE}" font-weight="600" fill="#{brand_color}" letter-spacing="0.5">#{section_text}</text>
|
|
71
|
+
SVG
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def title_svg(y_pos)
|
|
75
|
+
title_text = escape_xml(@title)
|
|
76
|
+
<<~SVG
|
|
77
|
+
<text x="#{PADDING}" y="#{y_pos}" class="inter" font-size="#{TITLE_SIZE}" font-weight="800" fill="#{WHITE}" letter-spacing="-0.02em">#{title_text}</text>
|
|
78
|
+
SVG
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def description_svg(y_pos)
|
|
82
|
+
desc_text = escape_xml(@description)
|
|
83
|
+
<<~SVG
|
|
84
|
+
<text x="#{PADDING}" y="#{y_pos}" class="inter" font-size="#{DESCRIPTION_SIZE}" font-weight="400" fill="#{GRAY}">#{desc_text}</text>
|
|
85
|
+
SVG
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def truncate_text(text, max_chars)
|
|
89
|
+
return nil if text.nil?
|
|
90
|
+
return text if text.length <= max_chars
|
|
91
|
+
|
|
92
|
+
"#{text[0, max_chars - 3].strip}..."
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|