jekyll-theme-zer0 1.26.0 → 1.28.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 +558 -5
- data/README.md +10 -27
- data/_data/authors.yml +4 -3
- data/_data/backlog.yml +28 -0
- data/_data/consumers.yml +147 -0
- data/_data/features.yml +142 -23
- data/_data/i18n/fr.yml +98 -0
- data/_data/i18n/languages.yml +36 -0
- data/_data/i18n/manifest.yml +1502 -0
- data/_data/navigation/docs.yml +2 -0
- data/_data/navigation/main.yml +6 -0
- data/_data/series.yml +19 -0
- data/_data/theme-manifest.yml +348 -268
- data/_data/ui-text.yml +36 -246
- data/_includes/README.md +25 -2
- data/_includes/analytics/google-tag-manager-body.html +11 -2
- data/_includes/analytics/google-tag-manager-head.html +16 -6
- data/_includes/analytics/posthog.html +33 -4
- data/_includes/components/abc-letter.html +43 -0
- data/_includes/components/author-avatar-url.html +4 -2
- data/_includes/components/book-card.html +42 -0
- data/_includes/components/book-nav.html +80 -0
- data/_includes/components/book-plate.html +31 -0
- data/_includes/components/book-toc.html +47 -0
- data/_includes/components/bookshelf.html +68 -0
- data/_includes/components/card-grid.html +60 -0
- data/_includes/components/data-card.html +95 -0
- data/_includes/components/env-switcher.html +3 -1
- data/_includes/components/halfmoon.html +5 -1
- data/_includes/components/language-toggle.html +81 -0
- data/_includes/components/page-feedback.html +65 -2
- data/_includes/components/search-modal.html +2 -2
- data/_includes/components/shortcuts-modal.html +1 -1
- data/_includes/components/theme-controls-bar.html +10 -2
- data/_includes/components/theme-customizer.html +8 -2
- data/_includes/components/translation-notice.html +27 -0
- data/_includes/content/intro.html +16 -15
- data/_includes/content/seo.html +9 -3
- data/_includes/core/color-mode-init.html +13 -4
- data/_includes/core/favicon.html +46 -0
- data/_includes/core/footer.html +9 -4
- data/_includes/core/head.html +18 -0
- data/_includes/core/header.html +9 -4
- data/_includes/core/hreflang.html +33 -0
- data/_includes/core/i18n.html +36 -0
- data/_includes/custom/body-end.html +18 -0
- data/_includes/custom/body-start.html +17 -0
- data/_includes/custom/footer.html +18 -0
- data/_includes/custom/head.html +18 -0
- data/_includes/navigation/breadcrumbs.html +1 -1
- data/_includes/navigation/local-graph.html +28 -2
- data/_includes/navigation/nav-tree.html +3 -3
- data/_includes/navigation/navbar.html +5 -4
- data/_includes/navigation/sidebar-config.html +21 -0
- data/_includes/navigation/sidebar-nav.html +4 -0
- data/_includes/navigation/sidebar-pagetree.html +150 -0
- data/_includes/navigation/sidebar-right.html +3 -2
- data/_includes/navigation/unified-drawer.html +9 -3
- data/_includes/obsidian/full-graph.html +165 -136
- data/_layouts/404.html +260 -0
- data/_layouts/README.md +2 -0
- data/_layouts/article.html +7 -1
- data/_layouts/book-abc.html +106 -0
- data/_layouts/book-story.html +91 -0
- data/_layouts/book.html +113 -0
- data/_layouts/collection.html +17 -5
- data/_layouts/default.html +5 -3
- data/_layouts/home.html +5 -2
- data/_layouts/landing.html +9 -0
- data/_layouts/news.html +159 -33
- data/_layouts/root.html +29 -9
- data/_layouts/section.html +61 -17
- data/_sass/components/_book.scss +423 -0
- data/_sass/core/_navbar.scss +13 -0
- data/_sass/core/_obsidian.scss +295 -7
- data/_sass/layouts/_navbar-extras.scss +6 -1
- data/_sass/theme/_backgrounds.scss +21 -8
- data/assets/css/main.scss +1 -0
- data/assets/js/auto-hide-nav.js +5 -1
- data/assets/js/halfmoon.js +26 -0
- data/assets/js/obsidian-graph.js +707 -265
- data/assets/js/obsidian-local-graph.js +161 -54
- data/assets/js/search-modal.js +4 -1
- data/scripts/README.md +20 -26
- data/scripts/bin/audit-consumer +1 -1
- data/scripts/bin/manifest +31 -5
- data/scripts/bin/sync-plugins +0 -1
- data/scripts/bin/validate +5 -1
- data/scripts/dev/rasterize-svg.js +65 -0
- data/scripts/features/generate-preview-images +49 -1390
- data/scripts/features/install-preview-generator +55 -33
- data/scripts/install/README.md +55 -25
- data/scripts/install/ai/client.sh +302 -93
- data/scripts/install/ai/prompts/spec.schema.json +1 -1
- data/scripts/install/ai/prompts/wizard.system.md +8 -17
- data/scripts/install/ai/wizard.sh +10 -5
- data/scripts/install/apply.sh +7 -3
- data/scripts/install/cli.sh +54 -4
- data/scripts/install/config.sh +167 -0
- data/scripts/install/doctor.sh +38 -0
- data/scripts/install/plan.sh +10 -0
- data/scripts/install/spec.sh +15 -7
- data/scripts/install/template.sh +4 -0
- data/scripts/lib/README.md +1 -5
- data/scripts/lib/install/deploy/README.md +3 -9
- data/scripts/lib/preview_generator.py +2261 -1341
- data/scripts/propagate.rb +277 -0
- data/scripts/translate.rb +1204 -0
- metadata +34 -3
- data/_plugins/preview_image_generator.rb +0 -351
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-theme-zer0
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.28.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Amr Abdel
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: jekyll
|
|
@@ -81,6 +81,7 @@ files:
|
|
|
81
81
|
- _data/ai.yml
|
|
82
82
|
- _data/authors.yml
|
|
83
83
|
- _data/backlog.yml
|
|
84
|
+
- _data/consumers.yml
|
|
84
85
|
- _data/content_statistics.yml
|
|
85
86
|
- _data/features.yml
|
|
86
87
|
- _data/feedback_types.yml
|
|
@@ -88,6 +89,9 @@ files:
|
|
|
88
89
|
- _data/generate_statistics.sh
|
|
89
90
|
- _data/github-actions-example.yml
|
|
90
91
|
- _data/glossary.yml
|
|
92
|
+
- _data/i18n/fr.yml
|
|
93
|
+
- _data/i18n/languages.yml
|
|
94
|
+
- _data/i18n/manifest.yml
|
|
91
95
|
- _data/landing.yml
|
|
92
96
|
- _data/navigation/README.md
|
|
93
97
|
- _data/navigation/about.yml
|
|
@@ -102,6 +106,7 @@ files:
|
|
|
102
106
|
- _data/roadmap.yml
|
|
103
107
|
- _data/roadmap_plan.yml
|
|
104
108
|
- _data/routing.yml
|
|
109
|
+
- _data/series.yml
|
|
105
110
|
- _data/statistics_config.yml
|
|
106
111
|
- _data/theme-manifest.yml
|
|
107
112
|
- _data/theme_backgrounds.yml
|
|
@@ -114,6 +119,7 @@ files:
|
|
|
114
119
|
- _includes/analytics/google-tag-manager-head.html
|
|
115
120
|
- _includes/analytics/posthog.html
|
|
116
121
|
- _includes/components/README.md
|
|
122
|
+
- _includes/components/abc-letter.html
|
|
117
123
|
- _includes/components/admin-links.html
|
|
118
124
|
- _includes/components/admin-tabs.html
|
|
119
125
|
- _includes/components/ai-chat.html
|
|
@@ -124,13 +130,20 @@ files:
|
|
|
124
130
|
- _includes/components/author-eeat.html
|
|
125
131
|
- _includes/components/background-customizer.html
|
|
126
132
|
- _includes/components/background-settings.html
|
|
133
|
+
- _includes/components/book-card.html
|
|
134
|
+
- _includes/components/book-nav.html
|
|
135
|
+
- _includes/components/book-plate.html
|
|
136
|
+
- _includes/components/book-toc.html
|
|
137
|
+
- _includes/components/bookshelf.html
|
|
127
138
|
- _includes/components/callout.html
|
|
139
|
+
- _includes/components/card-grid.html
|
|
128
140
|
- _includes/components/collection-manager.html
|
|
129
141
|
- _includes/components/component-showcase.html
|
|
130
142
|
- _includes/components/config-editor.html
|
|
131
143
|
- _includes/components/config-viewer.html
|
|
132
144
|
- _includes/components/cookie-consent.html
|
|
133
145
|
- _includes/components/cta-button.html
|
|
146
|
+
- _includes/components/data-card.html
|
|
134
147
|
- _includes/components/dev-shortcuts.html
|
|
135
148
|
- _includes/components/env-dashboard.html
|
|
136
149
|
- _includes/components/env-detect.html
|
|
@@ -139,6 +152,7 @@ files:
|
|
|
139
152
|
- _includes/components/halfmoon.html
|
|
140
153
|
- _includes/components/info-section.html
|
|
141
154
|
- _includes/components/js-cdn.html
|
|
155
|
+
- _includes/components/language-toggle.html
|
|
142
156
|
- _includes/components/mermaid.html
|
|
143
157
|
- _includes/components/nanobar.html
|
|
144
158
|
- _includes/components/nav-editor.html
|
|
@@ -163,6 +177,7 @@ files:
|
|
|
163
177
|
- _includes/components/theme-customizer.html
|
|
164
178
|
- _includes/components/theme-info.html
|
|
165
179
|
- _includes/components/theme-preview-gallery.html
|
|
180
|
+
- _includes/components/translation-notice.html
|
|
166
181
|
- _includes/components/zer0-env-var.html
|
|
167
182
|
- _includes/content/backlinks.html
|
|
168
183
|
- _includes/content/giscus.html
|
|
@@ -176,11 +191,18 @@ files:
|
|
|
176
191
|
- _includes/core/branding.html
|
|
177
192
|
- _includes/core/color-mode-init.html
|
|
178
193
|
- _includes/core/console-capture.html
|
|
194
|
+
- _includes/core/favicon.html
|
|
179
195
|
- _includes/core/footer-fabs.html
|
|
180
196
|
- _includes/core/footer.html
|
|
181
197
|
- _includes/core/head.html
|
|
182
198
|
- _includes/core/header.html
|
|
199
|
+
- _includes/core/hreflang.html
|
|
200
|
+
- _includes/core/i18n.html
|
|
183
201
|
- _includes/core/tokens-inline.html
|
|
202
|
+
- _includes/custom/body-end.html
|
|
203
|
+
- _includes/custom/body-start.html
|
|
204
|
+
- _includes/custom/footer.html
|
|
205
|
+
- _includes/custom/head.html
|
|
184
206
|
- _includes/docs/bootstrap-docs.html
|
|
185
207
|
- _includes/landing/landing-install-cards.html
|
|
186
208
|
- _includes/landing/landing-quick-links.html
|
|
@@ -198,6 +220,7 @@ files:
|
|
|
198
220
|
- _includes/navigation/sidebar-folders.html
|
|
199
221
|
- _includes/navigation/sidebar-left.html
|
|
200
222
|
- _includes/navigation/sidebar-nav.html
|
|
223
|
+
- _includes/navigation/sidebar-pagetree.html
|
|
201
224
|
- _includes/navigation/sidebar-right.html
|
|
202
225
|
- _includes/navigation/toc-fab.html
|
|
203
226
|
- _includes/navigation/unified-drawer.html
|
|
@@ -211,11 +234,15 @@ files:
|
|
|
211
234
|
- _includes/stats/stats-no-data.html
|
|
212
235
|
- _includes/stats/stats-overview.html
|
|
213
236
|
- _includes/stats/stats-tags.html
|
|
237
|
+
- _layouts/404.html
|
|
214
238
|
- _layouts/README.md
|
|
215
239
|
- _layouts/admin.html
|
|
216
240
|
- _layouts/article.html
|
|
217
241
|
- _layouts/author.html
|
|
218
242
|
- _layouts/authors.html
|
|
243
|
+
- _layouts/book-abc.html
|
|
244
|
+
- _layouts/book-story.html
|
|
245
|
+
- _layouts/book.html
|
|
219
246
|
- _layouts/collection.html
|
|
220
247
|
- _layouts/default.html
|
|
221
248
|
- _layouts/home.html
|
|
@@ -235,12 +262,12 @@ files:
|
|
|
235
262
|
- _plugins/author_pages_generator.rb
|
|
236
263
|
- _plugins/content_statistics_generator.rb
|
|
237
264
|
- _plugins/obsidian_links.rb
|
|
238
|
-
- _plugins/preview_image_generator.rb
|
|
239
265
|
- _plugins/sanitize_config_filter.rb
|
|
240
266
|
- _plugins/search_and_sitemap_generator.rb
|
|
241
267
|
- _plugins/theme_version.rb
|
|
242
268
|
- _sass/components/_author.scss
|
|
243
269
|
- _sass/components/_back-to-top.scss
|
|
270
|
+
- _sass/components/_book.scss
|
|
244
271
|
- _sass/components/_callout.scss
|
|
245
272
|
- _sass/components/_content-tables.scss
|
|
246
273
|
- _sass/components/_cookie-banner.scss
|
|
@@ -397,6 +424,7 @@ files:
|
|
|
397
424
|
- scripts/content-review.rb
|
|
398
425
|
- scripts/convert-notebooks.sh
|
|
399
426
|
- scripts/dev/css-diff.sh
|
|
427
|
+
- scripts/dev/rasterize-svg.js
|
|
400
428
|
- scripts/dev/shot.js
|
|
401
429
|
- scripts/docker-publish
|
|
402
430
|
- scripts/docs/check-freshness.sh
|
|
@@ -429,6 +457,7 @@ files:
|
|
|
429
457
|
- scripts/install/ai/wizard.sh
|
|
430
458
|
- scripts/install/apply.sh
|
|
431
459
|
- scripts/install/cli.sh
|
|
460
|
+
- scripts/install/config.sh
|
|
432
461
|
- scripts/install/diff.sh
|
|
433
462
|
- scripts/install/doctor.sh
|
|
434
463
|
- scripts/install/fs.sh
|
|
@@ -504,6 +533,7 @@ files:
|
|
|
504
533
|
- scripts/platform/setup-macos.sh
|
|
505
534
|
- scripts/platform/setup-wsl.sh
|
|
506
535
|
- scripts/post-template-setup.sh
|
|
536
|
+
- scripts/propagate.rb
|
|
507
537
|
- scripts/release
|
|
508
538
|
- scripts/requirements.txt
|
|
509
539
|
- scripts/setup.sh
|
|
@@ -530,6 +560,7 @@ files:
|
|
|
530
560
|
- scripts/test/lib/test_validation.sh
|
|
531
561
|
- scripts/test/lib/test_version.sh
|
|
532
562
|
- scripts/test/theme/validate
|
|
563
|
+
- scripts/translate.rb
|
|
533
564
|
- scripts/update-preview-paths.sh
|
|
534
565
|
- scripts/utils/analyze-commits
|
|
535
566
|
- scripts/utils/fix-markdown
|
|
@@ -1,351 +0,0 @@
|
|
|
1
|
-
# Feature: ZER0-004
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
#
|
|
5
|
-
# File: preview_image_generator.rb
|
|
6
|
-
# Path: _plugins/preview_image_generator.rb
|
|
7
|
-
# Purpose: Jekyll plugin for AI-powered preview image generation
|
|
8
|
-
#
|
|
9
|
-
# This plugin provides Jekyll integration for the preview image generator,
|
|
10
|
-
# including Liquid tags, filters, and hooks for automatic image generation.
|
|
11
|
-
#
|
|
12
|
-
# Usage in templates:
|
|
13
|
-
# {% preview_image_status %} - Shows missing preview count
|
|
14
|
-
# {{ page | preview_image_path }} - Returns the preview image path
|
|
15
|
-
# {{ page | has_preview_image }} - Returns true/false
|
|
16
|
-
#
|
|
17
|
-
# Configuration in _config.yml:
|
|
18
|
-
# preview_images:
|
|
19
|
-
# enabled: true
|
|
20
|
-
# provider: openai
|
|
21
|
-
# auto_generate: false # Set to true to generate during build (slow!)
|
|
22
|
-
# style: "retro pixel art, 8-bit video game aesthetic"
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
module Jekyll
|
|
26
|
-
module PreviewImageGenerator
|
|
27
|
-
# Configuration defaults
|
|
28
|
-
DEFAULTS = {
|
|
29
|
-
'enabled' => true,
|
|
30
|
-
'provider' => 'openai',
|
|
31
|
-
'model' => 'dall-e-3',
|
|
32
|
-
'size' => '1792x1024',
|
|
33
|
-
'quality' => 'standard',
|
|
34
|
-
'style' => 'retro pixel art, 8-bit video game aesthetic, vibrant colors, nostalgic, clean pixel graphics',
|
|
35
|
-
'style_modifiers' => 'pixelated, retro gaming style, CRT screen glow effect, limited color palette',
|
|
36
|
-
'output_dir' => 'assets/images/previews',
|
|
37
|
-
'assets_prefix' => '/assets',
|
|
38
|
-
'auto_prefix' => true,
|
|
39
|
-
'auto_generate' => false,
|
|
40
|
-
'collections' => ['posts', 'docs', 'quickstart']
|
|
41
|
-
}.freeze
|
|
42
|
-
|
|
43
|
-
# Get configuration with defaults
|
|
44
|
-
def self.config(site)
|
|
45
|
-
site_config = site.config['preview_images'] || {}
|
|
46
|
-
DEFAULTS.merge(site_config)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Check if a document has a preview image defined
|
|
50
|
-
def self.has_preview?(doc)
|
|
51
|
-
cached = cached_preview_entry(doc)
|
|
52
|
-
return cached['has_preview'] unless cached.nil?
|
|
53
|
-
|
|
54
|
-
preview = doc.data['preview']
|
|
55
|
-
return false if preview.nil? || preview.to_s.strip.empty?
|
|
56
|
-
|
|
57
|
-
# Reject non-path values (text descriptions used as preview)
|
|
58
|
-
return false unless preview.match?(/\.(png|jpe?g|gif|svg|webp)$/i) || preview.start_with?('http')
|
|
59
|
-
|
|
60
|
-
# Check if the preview file actually exists
|
|
61
|
-
site = doc.site
|
|
62
|
-
config = self.config(site)
|
|
63
|
-
|
|
64
|
-
# External URL — assume it exists
|
|
65
|
-
return true if preview.start_with?('http')
|
|
66
|
-
|
|
67
|
-
# Normalize the preview path using assets_prefix
|
|
68
|
-
normalized_preview = normalize_preview_path(preview, config)
|
|
69
|
-
|
|
70
|
-
# Build candidate file paths to check
|
|
71
|
-
candidates = []
|
|
72
|
-
if normalized_preview.start_with?('/')
|
|
73
|
-
candidates << File.join(site.source, normalized_preview.sub(/^\//, ''))
|
|
74
|
-
candidates << File.join(site.source, 'assets', normalized_preview.sub(/^\//, ''))
|
|
75
|
-
else
|
|
76
|
-
candidates << File.join(site.source, 'assets', normalized_preview)
|
|
77
|
-
candidates << File.join(site.source, normalized_preview)
|
|
78
|
-
candidates << File.join(site.source, config['output_dir'], normalized_preview)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
candidates.any? { |path| File.exist?(path) }
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Normalize a preview path by adding assets_prefix if needed
|
|
85
|
-
def self.normalize_preview_path(preview, config)
|
|
86
|
-
return preview if preview.nil? || preview.to_s.strip.empty?
|
|
87
|
-
return preview if preview.start_with?('http')
|
|
88
|
-
|
|
89
|
-
assets_prefix = config['assets_prefix'] || '/assets'
|
|
90
|
-
auto_prefix = config['auto_prefix'] != false # Default to true
|
|
91
|
-
|
|
92
|
-
# If auto_prefix is enabled and path doesn't already contain assets_prefix
|
|
93
|
-
if auto_prefix && !preview.include?(assets_prefix)
|
|
94
|
-
"#{assets_prefix}#{preview}"
|
|
95
|
-
else
|
|
96
|
-
preview
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
# Get the preview image path for a document
|
|
101
|
-
def self.preview_path(doc)
|
|
102
|
-
cached = cached_preview_entry(doc)
|
|
103
|
-
return cached['path'] unless cached.nil?
|
|
104
|
-
|
|
105
|
-
preview = doc.data['preview']
|
|
106
|
-
return nil if preview.nil? || preview.to_s.strip.empty?
|
|
107
|
-
|
|
108
|
-
# Reject non-path values
|
|
109
|
-
return nil unless preview.match?(/\.(png|jpe?g|gif|svg|webp)$/i) || preview.start_with?('http')
|
|
110
|
-
|
|
111
|
-
site = doc.site
|
|
112
|
-
config = self.config(site)
|
|
113
|
-
|
|
114
|
-
# If it's an external URL, return as-is
|
|
115
|
-
return preview if preview.start_with?('http')
|
|
116
|
-
|
|
117
|
-
# Normalize path with assets_prefix
|
|
118
|
-
normalize_preview_path(preview, config)
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# Get list of documents missing preview images
|
|
122
|
-
def self.missing_previews(site)
|
|
123
|
-
build_index(site)['missing']
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def self.build_index(site)
|
|
127
|
-
cached = site.data['preview_image_index']
|
|
128
|
-
return cached if cached
|
|
129
|
-
|
|
130
|
-
config = self.config(site)
|
|
131
|
-
index = {}
|
|
132
|
-
missing = []
|
|
133
|
-
|
|
134
|
-
preview_documents(site, config).each do |doc, collection_name|
|
|
135
|
-
key = preview_cache_key(doc)
|
|
136
|
-
next if index.key?(key)
|
|
137
|
-
|
|
138
|
-
preview = doc.data['preview']
|
|
139
|
-
normalized_path = nil
|
|
140
|
-
has_preview = false
|
|
141
|
-
|
|
142
|
-
if preview && !preview.to_s.strip.empty? && (preview.match?(/\.(png|jpe?g|gif|svg|webp)$/i) || preview.start_with?('http'))
|
|
143
|
-
normalized_path = preview.start_with?('http') ? preview : normalize_preview_path(preview, config)
|
|
144
|
-
has_preview = preview.start_with?('http') || preview_file_exists?(site, normalized_path, config)
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
index[key] = {
|
|
148
|
-
'has_preview' => has_preview,
|
|
149
|
-
'path' => normalized_path,
|
|
150
|
-
'collection' => collection_name
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
next if has_preview
|
|
154
|
-
|
|
155
|
-
missing << {
|
|
156
|
-
'path' => doc.relative_path,
|
|
157
|
-
'title' => doc.data['title'] || File.basename(doc.relative_path),
|
|
158
|
-
'collection' => collection_name
|
|
159
|
-
}
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
site.data['preview_image_index'] = {
|
|
163
|
-
'documents' => index,
|
|
164
|
-
'missing' => missing
|
|
165
|
-
}
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
def self.cached_preview_entry(doc)
|
|
169
|
-
return nil unless doc.respond_to?(:site) && doc.site && doc.site.data['preview_image_index']
|
|
170
|
-
|
|
171
|
-
doc.site.data['preview_image_index']['documents'][preview_cache_key(doc)]
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
def self.preview_documents(site, config)
|
|
175
|
-
docs = []
|
|
176
|
-
Array(config['collections']).each do |collection_name|
|
|
177
|
-
collection = site.collections[collection_name]
|
|
178
|
-
next unless collection
|
|
179
|
-
|
|
180
|
-
collection.docs.each { |doc| docs << [doc, collection_name] }
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
site.posts.docs.each { |doc| docs << [doc, 'posts'] } if site.respond_to?(:posts) && site.posts
|
|
184
|
-
docs
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
def self.preview_cache_key(doc)
|
|
188
|
-
doc.respond_to?(:relative_path) ? doc.relative_path : doc.path
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def self.preview_file_exists?(site, normalized_preview, config)
|
|
192
|
-
candidates = []
|
|
193
|
-
if normalized_preview.start_with?('/')
|
|
194
|
-
candidates << File.join(site.source, normalized_preview.sub(/^\//, ''))
|
|
195
|
-
candidates << File.join(site.source, 'assets', normalized_preview.sub(/^\//, ''))
|
|
196
|
-
else
|
|
197
|
-
candidates << File.join(site.source, 'assets', normalized_preview)
|
|
198
|
-
candidates << File.join(site.source, normalized_preview)
|
|
199
|
-
candidates << File.join(site.source, config['output_dir'], normalized_preview)
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
candidates.any? { |path| File.exist?(path) }
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
# Generate a preview image filename from document
|
|
206
|
-
def self.generate_filename(doc)
|
|
207
|
-
# Use the document's slug or generate from title
|
|
208
|
-
slug = doc.data['slug'] || doc.basename_without_ext
|
|
209
|
-
|
|
210
|
-
# Sanitize the slug for use as filename
|
|
211
|
-
sanitized = slug.to_s.downcase
|
|
212
|
-
.gsub(/[^a-z0-9\-_]/, '-')
|
|
213
|
-
.gsub(/-+/, '-')
|
|
214
|
-
.gsub(/^-|-$/, '')
|
|
215
|
-
|
|
216
|
-
"#{sanitized}-preview.png"
|
|
217
|
-
end
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
# ==========================================================================
|
|
221
|
-
# Liquid Filters
|
|
222
|
-
# ==========================================================================
|
|
223
|
-
|
|
224
|
-
module PreviewImageFilters
|
|
225
|
-
# Check if a page/document has a preview image
|
|
226
|
-
# Usage: {{ page | has_preview_image }}
|
|
227
|
-
def has_preview_image(doc)
|
|
228
|
-
return false unless doc.is_a?(Hash) || doc.respond_to?(:data)
|
|
229
|
-
|
|
230
|
-
# Handle both Hash (from assign) and Document objects
|
|
231
|
-
if doc.is_a?(Hash)
|
|
232
|
-
preview = doc['preview']
|
|
233
|
-
return false if preview.nil? || preview.to_s.strip.empty?
|
|
234
|
-
# Only consider values that look like image paths or URLs
|
|
235
|
-
preview.to_s.match?(/\.(png|jpe?g|gif|svg|webp)$/i) || preview.to_s.start_with?('http')
|
|
236
|
-
else
|
|
237
|
-
PreviewImageGenerator.has_preview?(doc)
|
|
238
|
-
end
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
# Get the preview image path
|
|
242
|
-
# Usage: {{ page | preview_image_path }}
|
|
243
|
-
def preview_image_path(doc)
|
|
244
|
-
return nil unless doc.is_a?(Hash) || doc.respond_to?(:data)
|
|
245
|
-
|
|
246
|
-
if doc.is_a?(Hash)
|
|
247
|
-
preview = doc['preview']
|
|
248
|
-
return nil if preview.nil? || preview.to_s.strip.empty?
|
|
249
|
-
return nil unless preview.to_s.match?(/\.(png|jpe?g|gif|svg|webp)$/i) || preview.to_s.start_with?('http')
|
|
250
|
-
|
|
251
|
-
return preview if preview.start_with?('/') || preview.start_with?('http')
|
|
252
|
-
|
|
253
|
-
# Get config from context if available
|
|
254
|
-
site_config = @context.registers[:site].config['preview_images'] || {}
|
|
255
|
-
output_dir = site_config['output_dir'] || 'assets/images/previews'
|
|
256
|
-
"#{output_dir}/#{preview}"
|
|
257
|
-
else
|
|
258
|
-
PreviewImageGenerator.preview_path(doc)
|
|
259
|
-
end
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
# Get suggested filename for a preview image
|
|
263
|
-
# Usage: {{ page | preview_filename }}
|
|
264
|
-
def preview_filename(doc)
|
|
265
|
-
return nil unless doc.respond_to?(:data) || doc.respond_to?(:basename_without_ext)
|
|
266
|
-
PreviewImageGenerator.generate_filename(doc)
|
|
267
|
-
end
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
# ==========================================================================
|
|
271
|
-
# Liquid Tags
|
|
272
|
-
# ==========================================================================
|
|
273
|
-
|
|
274
|
-
# Tag to display preview image status/count
|
|
275
|
-
# Usage: {% preview_image_status %}
|
|
276
|
-
class PreviewImageStatusTag < Liquid::Tag
|
|
277
|
-
def render(context)
|
|
278
|
-
site = context.registers[:site]
|
|
279
|
-
missing = PreviewImageGenerator.missing_previews(site)
|
|
280
|
-
|
|
281
|
-
if missing.empty?
|
|
282
|
-
"<span class=\"badge bg-success\">All preview images present</span>"
|
|
283
|
-
else
|
|
284
|
-
"<span class=\"badge bg-warning text-dark\">#{missing.length} missing preview images</span>"
|
|
285
|
-
end
|
|
286
|
-
end
|
|
287
|
-
end
|
|
288
|
-
|
|
289
|
-
# Tag to list missing preview images
|
|
290
|
-
# Usage: {% preview_images_missing %}
|
|
291
|
-
class PreviewImagesMissingTag < Liquid::Tag
|
|
292
|
-
def render(context)
|
|
293
|
-
site = context.registers[:site]
|
|
294
|
-
missing = PreviewImageGenerator.missing_previews(site)
|
|
295
|
-
|
|
296
|
-
return "<p>All documents have preview images!</p>" if missing.empty?
|
|
297
|
-
|
|
298
|
-
html = "<ul class=\"list-group\">\n"
|
|
299
|
-
missing.each do |item|
|
|
300
|
-
html += " <li class=\"list-group-item d-flex justify-content-between align-items-center\">\n"
|
|
301
|
-
html += " <span>#{item['title']}</span>\n"
|
|
302
|
-
html += " <span class=\"badge bg-secondary\">#{item['collection']}</span>\n"
|
|
303
|
-
html += " </li>\n"
|
|
304
|
-
end
|
|
305
|
-
html += "</ul>"
|
|
306
|
-
html
|
|
307
|
-
end
|
|
308
|
-
end
|
|
309
|
-
|
|
310
|
-
# ==========================================================================
|
|
311
|
-
# Generator Hook (optional auto-generation during build)
|
|
312
|
-
# ==========================================================================
|
|
313
|
-
|
|
314
|
-
class PreviewImageGeneratorHook < Generator
|
|
315
|
-
safe true
|
|
316
|
-
priority :low
|
|
317
|
-
|
|
318
|
-
def generate(site)
|
|
319
|
-
config = PreviewImageGenerator.config(site)
|
|
320
|
-
|
|
321
|
-
return unless config['enabled']
|
|
322
|
-
|
|
323
|
-
# Store missing previews in site data for access in templates
|
|
324
|
-
preview_index = PreviewImageGenerator.build_index(site)
|
|
325
|
-
site.data['preview_images_missing'] = preview_index['missing']
|
|
326
|
-
site.data['preview_images_config'] = config
|
|
327
|
-
|
|
328
|
-
# Log status
|
|
329
|
-
missing_count = site.data['preview_images_missing'].length
|
|
330
|
-
if missing_count > 0
|
|
331
|
-
Jekyll.logger.info "Preview Images:", "#{missing_count} documents missing preview images"
|
|
332
|
-
Jekyll.logger.info "Preview Images:", "Run 'scripts/generate-preview-images.sh' to generate them"
|
|
333
|
-
end
|
|
334
|
-
|
|
335
|
-
# Auto-generate is disabled by default (it's slow and requires API calls)
|
|
336
|
-
# Users should run the shell script manually or via CI/CD
|
|
337
|
-
if config['auto_generate']
|
|
338
|
-
Jekyll.logger.warn "Preview Images:", "Auto-generation is enabled but not implemented in plugin"
|
|
339
|
-
Jekyll.logger.warn "Preview Images:", "Use 'scripts/generate-preview-images.sh' instead"
|
|
340
|
-
end
|
|
341
|
-
end
|
|
342
|
-
end
|
|
343
|
-
|
|
344
|
-
# ==========================================================================
|
|
345
|
-
# Register Liquid components
|
|
346
|
-
# ==========================================================================
|
|
347
|
-
|
|
348
|
-
Liquid::Template.register_filter(PreviewImageFilters)
|
|
349
|
-
Liquid::Template.register_tag('preview_image_status', PreviewImageStatusTag)
|
|
350
|
-
Liquid::Template.register_tag('preview_images_missing', PreviewImagesMissingTag)
|
|
351
|
-
end
|