jekyll-theme-zer0 0.15.2 → 0.16.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 62704de5aed76c7000c418b93977002ded56b588a49183c4548c315bd2b40fdd
4
- data.tar.gz: acfd3a0d47a33a99af964fe2019e0128bbfb50903640658671bb6090b3b38495
3
+ metadata.gz: 82d722f8c9249249da9634e5e774dddda47d6ae3888e60e8f87269d697fc2752
4
+ data.tar.gz: 54c07e714da9f2e718f1c057354745adb0b1490562052d3ffec0a4fcf125430d
5
5
  SHA512:
6
- metadata.gz: 8db972fbd75ed902880434ab1e8a79ba24ae276492cebce8987cda3ad085b0aeea26fc2174e8cf72594896884c5d1b3b08f816b97a0d1536044d51afa52c2d5e
7
- data.tar.gz: 839bbeb76675da98c6136c952722154249264fe4998003b4c5e2152401855dcea16ac31864f89e66a30236589e41ecc789361378fd3308a8c529cd8315c67a0f
6
+ metadata.gz: a85d20e56b617f547beeee6e9d12571f110e606bf85e68c5773830d9ecc7f9b24c7d632f326db902792a7bf507ef3bb5eead8faff40a659112b481414293c13e
7
+ data.tar.gz: de2aa109131a6067d6ae045992ebca0fb0eb31cf048b336516aa426a3ab46be58d7d51b979923431bb4a8f0f42e73b110b5a60192ea094ec82ebbdf6afa14441
data/CHANGELOG.md CHANGED
@@ -1,5 +1,61 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.16.0] - 2025-12-20
4
+
5
+ ### Added
6
+ - **Configurable Assets Prefix**: New `assets_prefix` and `auto_prefix` configuration options for preview images
7
+ - Allows shorter frontmatter paths like `/images/previews/image.png` instead of `/assets/images/previews/image.png`
8
+ - Automatic path normalization in Liquid templates detects external URLs vs local paths
9
+ - Configured via `_config.yml` under `preview_images.assets_prefix` and `preview_images.auto_prefix`
10
+ - **xAI Grok Image Provider**: Added xAI as a new AI provider for preview image generation
11
+ - Uses `grok-2-image` model at `https://api.x.ai/v1/images/generations`
12
+ - Set `XAI_API_KEY` environment variable and use `--provider xai` flag
13
+ - **Preview Path Migration Script**: New `scripts/update-preview-paths.sh` for migrating existing frontmatter
14
+ - Supports dry-run and apply modes
15
+ - Removes `/assets/` prefix from preview paths in markdown files
16
+
17
+ ### Changed
18
+ - Updated `preview-image.html` include with path normalization logic
19
+ - Updated `intro.html` include with assets prefix support for hero backgrounds
20
+ - Updated `seo.html` include with assets prefix normalization for og:image meta tags
21
+ - Enhanced `preview_image_generator.rb` Ruby plugin with `normalize_preview_path` method
22
+ - Enhanced `preview_generator.py` with xAI provider, `--assets-prefix`, and `--no-auto-prefix` CLI options
23
+ - Migrated 24 markdown files to use shorter preview paths (without `/assets/` prefix)
24
+ - Updated scripts/README.md documentation with new provider and prefix options
25
+
26
+ ### Fixed
27
+ - **Critical: Liquid syntax error in `seo.html`** - Fixed invalid nested curly braces on line 27
28
+
29
+ ## [0.15.5] - 2025-12-20
30
+
31
+ ### Changed
32
+ - Version bump: patch release
33
+
34
+ ### Commits in this release
35
+ - d46cdb1 Merge branch 'main' of https://github.com/bamr87/zer0-mistakes
36
+ - 36767cd fix: update last modified timestamp in quick start guide
37
+ - f606096 fix: update quick start guide for clarity and consistency
38
+
39
+
40
+ ## [0.15.4] - 2025-12-20
41
+
42
+ ### Changed
43
+ - Version bump: patch release
44
+
45
+ ### Commits in this release
46
+ - bafb5ea fix: resolve CI quality check failures for preview images
47
+
48
+
49
+ ## [0.15.3] - 2025-12-20
50
+
51
+ ### Changed
52
+ - Version bump: patch release
53
+
54
+ ### Commits in this release
55
+ - 2f8b580 chore: merge branch and version bump to 0.15.2
56
+ - a239892 chore: bump version to 0.15.2
57
+
58
+
3
59
  ## [0.15.2] - 2025-12-19
4
60
 
5
61
  ### Changed
@@ -9,8 +9,12 @@
9
9
  style="height: 180px; object-fit: cover;"
10
10
  -%}
11
11
 
12
- Convention: All preview paths should be absolute (e.g., /assets/images/preview.png)
13
- External URLs (https://...) are also supported.
12
+ Convention: Preview paths can omit the /assets/ prefix.
13
+ The component will auto-prepend the assets_prefix from _config.yml.
14
+ Examples:
15
+ - /images/previews/my-image.png → /assets/images/previews/my-image.png
16
+ - /assets/images/previews/my-image.png → unchanged
17
+ - https://example.com/image.png → unchanged (external URL)
14
18
  {% endcomment %}
15
19
  {% assign img = include.src | default: site.teaser %}
16
20
  {% assign alt = include.alt | default: "Preview image" %}
@@ -18,9 +22,22 @@
18
22
  {% assign style = include.style %}
19
23
  {% assign loading = include.loading | default: "lazy" %}
20
24
 
21
- {% comment %} External URL or internal path {% endcomment %}
25
+ {% comment %} Get assets prefix configuration {% endcomment %}
26
+ {% assign assets_prefix = site.preview_images.assets_prefix | default: '/assets' %}
27
+ {% assign auto_prefix = site.preview_images.auto_prefix | default: true %}
28
+
29
+ {% comment %} Determine final image source {% endcomment %}
22
30
  {% if img contains '://' %}
31
+ {% comment %} External URL - use as-is {% endcomment %}
23
32
  {% assign final_src = img %}
33
+ {% elsif auto_prefix and img != blank %}
34
+ {% comment %} Check if path already has the assets prefix {% endcomment %}
35
+ {% if img contains assets_prefix %}
36
+ {% assign final_src = img | relative_url %}
37
+ {% else %}
38
+ {% comment %} Prepend assets prefix to the path {% endcomment %}
39
+ {% assign final_src = assets_prefix | append: img | relative_url %}
40
+ {% endif %}
24
41
  {% else %}
25
42
  {% assign final_src = img | relative_url %}
26
43
  {% endif %}
@@ -16,12 +16,21 @@
16
16
  <!-- Intro Section -->
17
17
 
18
18
  {% comment %}
19
- Determine the preview image path. All preview paths should be absolute
20
- starting with /assets/ or external URLs starting with ://
19
+ Determine the preview image path. Preview paths can omit the /assets/ prefix.
20
+ The component will auto-prepend the assets_prefix from _config.yml.
21
21
  {% endcomment %}
22
22
  {% assign preview_image = page.preview | default: site.info_banner %}
23
+ {% assign assets_prefix = site.preview_images.assets_prefix | default: '/assets' %}
24
+ {% assign auto_prefix = site.preview_images.auto_prefix | default: true %}
25
+
23
26
  {% if preview_image contains '://' %}
24
27
  {% assign preview_path = preview_image %}
28
+ {% elsif auto_prefix and preview_image != blank %}
29
+ {% if preview_image contains assets_prefix %}
30
+ {% assign preview_path = preview_image | relative_url %}
31
+ {% else %}
32
+ {% assign preview_path = assets_prefix | append: preview_image | relative_url %}
33
+ {% endif %}
25
34
  {% else %}
26
35
  {% assign preview_path = preview_image | relative_url %}
27
36
  {% endif %}
@@ -23,8 +23,19 @@
23
23
  {%- assign page_large_image = page.header.og_image | default: page.header.overlay_image | default: page.header.image | absolute_url -%}
24
24
  {%- assign page_large_image = page_large_image | escape -%}
25
25
 
26
+ {%- comment -%} Handle preview image with configurable assets prefix {%- endcomment -%}
26
27
  {%- assign page_teaser_image = page.preview | default: site.og_image -%}
27
- {%- assign page_teaser_image = {{ site.public_folder }}/page_teaser_image | escape -%}
28
+ {%- assign assets_prefix = site.preview_images.assets_prefix | default: '/assets' -%}
29
+ {%- assign auto_prefix = site.preview_images.auto_prefix | default: true -%}
30
+
31
+ {%- if page_teaser_image contains '://' -%}
32
+ {%- comment -%} External URL - use as-is {%- endcomment -%}
33
+ {%- elsif auto_prefix and page_teaser_image != blank -%}
34
+ {%- unless page_teaser_image contains assets_prefix -%}
35
+ {%- assign page_teaser_image = assets_prefix | append: page_teaser_image -%}
36
+ {%- endunless -%}
37
+ {%- endif -%}
38
+ {%- assign page_teaser_image = page_teaser_image | absolute_url | escape -%}
28
39
 
29
40
  {%- assign site_og_image = site.og_image | absolute_url -%}
30
41
  {%- assign site_og_image = site_og_image | escape -%}
@@ -33,6 +33,8 @@ module Jekyll
33
33
  'style' => 'retro pixel art, 8-bit video game aesthetic, vibrant colors, nostalgic, clean pixel graphics',
34
34
  'style_modifiers' => 'pixelated, retro gaming style, CRT screen glow effect, limited color palette',
35
35
  'output_dir' => 'assets/images/previews',
36
+ 'assets_prefix' => '/assets',
37
+ 'auto_prefix' => true,
36
38
  'auto_generate' => false,
37
39
  'collections' => ['posts', 'docs', 'quickstart']
38
40
  }.freeze
@@ -52,18 +54,37 @@ module Jekyll
52
54
  site = doc.site
53
55
  config = self.config(site)
54
56
 
57
+ # Normalize the preview path using assets_prefix
58
+ normalized_preview = normalize_preview_path(preview, config)
59
+
55
60
  # Build the full path
56
- preview_path = if preview.start_with?('/')
57
- File.join(site.source, preview)
58
- elsif preview.start_with?('http')
61
+ preview_path = if normalized_preview.start_with?('/')
62
+ File.join(site.source, normalized_preview.sub(/^\//, ''))
63
+ elsif normalized_preview.start_with?('http')
59
64
  return true # External URL, assume it exists
60
65
  else
61
- File.join(site.source, config['output_dir'], preview)
66
+ File.join(site.source, config['output_dir'], normalized_preview)
62
67
  end
63
68
 
64
69
  File.exist?(preview_path)
65
70
  end
66
71
 
72
+ # Normalize a preview path by adding assets_prefix if needed
73
+ def self.normalize_preview_path(preview, config)
74
+ return preview if preview.nil? || preview.to_s.strip.empty?
75
+ return preview if preview.start_with?('http')
76
+
77
+ assets_prefix = config['assets_prefix'] || '/assets'
78
+ auto_prefix = config['auto_prefix'] != false # Default to true
79
+
80
+ # If auto_prefix is enabled and path doesn't already contain assets_prefix
81
+ if auto_prefix && !preview.include?(assets_prefix)
82
+ "#{assets_prefix}#{preview}"
83
+ else
84
+ preview
85
+ end
86
+ end
87
+
67
88
  # Get the preview image path for a document
68
89
  def self.preview_path(doc)
69
90
  preview = doc.data['preview']
@@ -72,11 +93,11 @@ module Jekyll
72
93
  site = doc.site
73
94
  config = self.config(site)
74
95
 
75
- # If it's already a full path or URL, return as-is
76
- return preview if preview.start_with?('/') || preview.start_with?('http')
96
+ # If it's an external URL, return as-is
97
+ return preview if preview.start_with?('http')
77
98
 
78
- # Build relative path from output_dir
79
- "#{config['output_dir']}/#{preview}"
99
+ # Normalize path with assets_prefix
100
+ normalize_preview_path(preview, config)
80
101
  end
81
102
 
82
103
  # Get list of documents missing preview images
data/scripts/README.md CHANGED
@@ -91,7 +91,14 @@ Options:
91
91
  --dry-run Preview without changes
92
92
  --collection TYPE Generate for specific collection (posts, docs, etc.)
93
93
  -f, --file PATH Process specific file
94
- --provider PROVIDER Use specific AI provider (openai)
94
+ --provider PROVIDER Use specific AI provider (openai, stability, xai)
95
+ --assets-prefix Custom assets path prefix (default: /assets)
96
+ --no-auto-prefix Disable automatic path prefixing
97
+
98
+ AI Providers:
99
+ openai - OpenAI DALL-E (requires OPENAI_API_KEY)
100
+ stability - Stability AI (requires STABILITY_API_KEY)
101
+ xai - xAI Grok image generation (requires XAI_API_KEY)
95
102
  ```
96
103
 
97
104
  #### `install-preview-generator`
@@ -101,6 +101,8 @@ class PreviewGenerator:
101
101
  output_dir: str = "assets/images/previews",
102
102
  image_style: str = "digital art, professional blog illustration",
103
103
  image_size: str = "1024x1024",
104
+ assets_prefix: str = "/assets",
105
+ auto_prefix: bool = True,
104
106
  dry_run: bool = False,
105
107
  verbose: bool = False,
106
108
  force: bool = False,
@@ -110,6 +112,8 @@ class PreviewGenerator:
110
112
  self.output_dir = project_root / output_dir
111
113
  self.image_style = image_style
112
114
  self.image_size = image_size
115
+ self.assets_prefix = assets_prefix
116
+ self.auto_prefix = auto_prefix
113
117
  self.dry_run = dry_run
114
118
  self.verbose = verbose
115
119
  self.force = force
@@ -129,6 +133,27 @@ class PreviewGenerator:
129
133
  if self.verbose:
130
134
  log(msg, "debug")
131
135
 
136
+ def normalize_preview_path(self, preview_path: Optional[str]) -> Optional[str]:
137
+ """Normalize a preview path by adding assets_prefix if needed.
138
+
139
+ This allows users to omit the /assets/ prefix in frontmatter:
140
+ - /images/previews/my-image.png -> /assets/images/previews/my-image.png
141
+ - /assets/images/previews/my-image.png -> unchanged
142
+ - https://example.com/image.png -> unchanged (external URL)
143
+ """
144
+ if not preview_path:
145
+ return preview_path
146
+
147
+ # External URLs pass through unchanged
148
+ if preview_path.startswith('http://') or preview_path.startswith('https://'):
149
+ return preview_path
150
+
151
+ # If auto_prefix is enabled and path doesn't contain assets_prefix
152
+ if self.auto_prefix and self.assets_prefix not in preview_path:
153
+ return f"{self.assets_prefix}{preview_path}"
154
+
155
+ return preview_path
156
+
132
157
  def parse_front_matter(self, file_path: Path) -> Optional[ContentFile]:
133
158
  """Parse front matter and content from a markdown file."""
134
159
  try:
@@ -178,19 +203,19 @@ class PreviewGenerator:
178
203
  if not preview_path:
179
204
  return False
180
205
 
206
+ # Normalize the path first (adds assets_prefix if needed)
207
+ normalized_path = self.normalize_preview_path(preview_path)
208
+ if not normalized_path:
209
+ return False
210
+
181
211
  # Handle absolute and relative paths
182
- clean_path = preview_path.lstrip('/')
212
+ clean_path = normalized_path.lstrip('/')
183
213
 
184
214
  # Check direct path
185
215
  full_path = self.project_root / clean_path
186
216
  if full_path.exists():
187
217
  return True
188
218
 
189
- # Check in assets directory
190
- assets_path = self.project_root / 'assets' / clean_path
191
- if assets_path.exists():
192
- return True
193
-
194
219
  return False
195
220
 
196
221
  def generate_prompt(self, content: ContentFile) -> str:
@@ -395,12 +420,131 @@ class PreviewGenerator:
395
420
  prompt_used=prompt,
396
421
  )
397
422
 
423
+ def generate_image_xai(self, prompt: str, output_path: Path) -> GenerationResult:
424
+ """Generate image using xAI Grok API."""
425
+ if not HAS_REQUESTS:
426
+ return GenerationResult(
427
+ success=False,
428
+ image_path=None,
429
+ preview_url=None,
430
+ error="requests package not installed. Run: pip install requests",
431
+ prompt_used=prompt,
432
+ )
433
+
434
+ api_key = os.environ.get('XAI_API_KEY')
435
+ if not api_key:
436
+ return GenerationResult(
437
+ success=False,
438
+ image_path=None,
439
+ preview_url=None,
440
+ error="XAI_API_KEY environment variable not set",
441
+ prompt_used=prompt,
442
+ )
443
+
444
+ try:
445
+ # xAI has a max prompt length of 1024 characters
446
+ truncated_prompt = prompt[:1000] if len(prompt) > 1000 else prompt
447
+ self.debug(f"Generating with xAI Grok, prompt: {truncated_prompt[:200]}...")
448
+
449
+ # xAI uses OpenAI-compatible API format
450
+ response = requests.post(
451
+ "https://api.x.ai/v1/images/generations",
452
+ headers={
453
+ "Authorization": f"Bearer {api_key}",
454
+ "Content-Type": "application/json",
455
+ },
456
+ json={
457
+ "model": "grok-2-image",
458
+ "prompt": truncated_prompt,
459
+ "n": 1,
460
+ },
461
+ timeout=120, # 2 minute timeout for image generation
462
+ )
463
+ response.raise_for_status()
464
+
465
+ data = response.json()
466
+
467
+ # xAI returns base64-encoded images
468
+ if 'data' not in data or not data['data']:
469
+ return GenerationResult(
470
+ success=False,
471
+ image_path=None,
472
+ preview_url=None,
473
+ error="No image data in response",
474
+ prompt_used=prompt,
475
+ )
476
+
477
+ image_data = data['data'][0]
478
+
479
+ # Check if it's a URL or base64
480
+ if 'url' in image_data:
481
+ # Download from URL
482
+ img_response = requests.get(image_data['url'], timeout=60)
483
+ img_response.raise_for_status()
484
+ output_path.write_bytes(img_response.content)
485
+ elif 'b64_json' in image_data:
486
+ # Decode base64
487
+ import base64
488
+ image_bytes = base64.b64decode(image_data['b64_json'])
489
+ output_path.write_bytes(image_bytes)
490
+ else:
491
+ return GenerationResult(
492
+ success=False,
493
+ image_path=None,
494
+ preview_url=None,
495
+ error="Unexpected response format from xAI",
496
+ prompt_used=prompt,
497
+ )
498
+
499
+ return GenerationResult(
500
+ success=True,
501
+ image_path=str(output_path),
502
+ preview_url=str(output_path.relative_to(self.project_root)),
503
+ error=None,
504
+ prompt_used=prompt,
505
+ )
506
+
507
+ except requests.exceptions.HTTPError as e:
508
+ error_msg = str(e)
509
+ try:
510
+ error_data = e.response.json()
511
+ self.debug(f"xAI error response: {error_data}")
512
+ if 'error' in error_data:
513
+ error_msg = error_data['error'].get('message', str(error_data['error']))
514
+ elif 'detail' in error_data:
515
+ error_msg = str(error_data['detail'])
516
+ else:
517
+ error_msg = str(error_data)
518
+ except:
519
+ # Try to get raw text
520
+ try:
521
+ error_msg = e.response.text[:500]
522
+ except:
523
+ pass
524
+ return GenerationResult(
525
+ success=False,
526
+ image_path=None,
527
+ preview_url=None,
528
+ error=f"xAI API error: {error_msg}",
529
+ prompt_used=prompt,
530
+ )
531
+ except Exception as e:
532
+ return GenerationResult(
533
+ success=False,
534
+ image_path=None,
535
+ preview_url=None,
536
+ error=str(e),
537
+ prompt_used=prompt,
538
+ )
539
+
398
540
  def generate_image(self, prompt: str, output_path: Path) -> GenerationResult:
399
541
  """Generate image using configured provider."""
400
542
  if self.provider == "openai":
401
543
  return self.generate_image_openai(prompt, output_path)
402
544
  elif self.provider == "stability":
403
545
  return self.generate_image_stability(prompt, output_path)
546
+ elif self.provider == "xai":
547
+ return self.generate_image_xai(prompt, output_path)
404
548
  else:
405
549
  return GenerationResult(
406
550
  success=False,
@@ -559,9 +703,9 @@ def main():
559
703
  )
560
704
  parser.add_argument(
561
705
  '-p', '--provider',
562
- choices=['openai', 'stability'],
706
+ choices=['openai', 'stability', 'xai'],
563
707
  default='openai',
564
- help="AI provider for image generation"
708
+ help="AI provider for image generation (openai, stability, xai)"
565
709
  )
566
710
  parser.add_argument(
567
711
  '-d', '--dry-run',
@@ -593,6 +737,16 @@ def main():
593
737
  default='digital art, professional blog illustration, clean design',
594
738
  help="Image style prompt"
595
739
  )
740
+ parser.add_argument(
741
+ '--assets-prefix',
742
+ default='/assets',
743
+ help="Prefix to prepend to relative preview paths (default: /assets)"
744
+ )
745
+ parser.add_argument(
746
+ '--no-auto-prefix',
747
+ action='store_true',
748
+ help="Disable automatic assets prefix prepending"
749
+ )
596
750
 
597
751
  args = parser.parse_args()
598
752
 
@@ -606,6 +760,8 @@ def main():
606
760
  provider=args.provider,
607
761
  output_dir=args.output_dir,
608
762
  image_style=args.style,
763
+ assets_prefix=args.assets_prefix,
764
+ auto_prefix=not args.no_auto_prefix,
609
765
  dry_run=args.dry_run,
610
766
  verbose=args.verbose,
611
767
  force=args.force,
@@ -0,0 +1,145 @@
1
+ #!/bin/bash
2
+ #
3
+ # update-preview-paths.sh
4
+ #
5
+ # Updates all preview paths in markdown frontmatter to remove the /assets/ prefix.
6
+ # This aligns with the new auto_prefix feature that automatically prepends /assets/.
7
+ #
8
+ # Usage:
9
+ # ./scripts/update-preview-paths.sh # Dry run (preview changes)
10
+ # ./scripts/update-preview-paths.sh --apply # Apply changes
11
+ #
12
+ # Example transformation:
13
+ # preview: /assets/images/previews/my-image.png
14
+ # becomes:
15
+ # preview: /images/previews/my-image.png
16
+ #
17
+
18
+ set -euo pipefail
19
+
20
+ # Colors for output
21
+ RED='\033[0;31m'
22
+ GREEN='\033[0;32m'
23
+ YELLOW='\033[1;33m'
24
+ BLUE='\033[0;34m'
25
+ CYAN='\033[0;36m'
26
+ NC='\033[0m' # No Color
27
+
28
+ # Script directory
29
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
30
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
31
+
32
+ # Counters
33
+ TOTAL_FILES=0
34
+ MODIFIED_FILES=0
35
+ SKIPPED_FILES=0
36
+
37
+ # Mode
38
+ DRY_RUN=true
39
+
40
+ # Parse arguments
41
+ for arg in "$@"; do
42
+ case $arg in
43
+ --apply)
44
+ DRY_RUN=false
45
+ shift
46
+ ;;
47
+ --help|-h)
48
+ echo "Usage: $0 [--apply]"
49
+ echo ""
50
+ echo "Updates preview paths in markdown frontmatter to remove /assets/ prefix."
51
+ echo ""
52
+ echo "Options:"
53
+ echo " --apply Apply changes (default is dry run)"
54
+ echo " --help Show this help message"
55
+ exit 0
56
+ ;;
57
+ esac
58
+ done
59
+
60
+ log_info() {
61
+ echo -e "${BLUE}[INFO]${NC} $1"
62
+ }
63
+
64
+ log_success() {
65
+ echo -e "${GREEN}[SUCCESS]${NC} $1"
66
+ }
67
+
68
+ log_warning() {
69
+ echo -e "${YELLOW}[WARNING]${NC} $1"
70
+ }
71
+
72
+ log_change() {
73
+ echo -e "${CYAN}[CHANGE]${NC} $1"
74
+ }
75
+
76
+ echo ""
77
+ echo -e "${BLUE}========================================${NC}"
78
+ echo -e "${BLUE}🔄 Preview Path Updater${NC}"
79
+ echo -e "${BLUE}========================================${NC}"
80
+ echo ""
81
+
82
+ if [ "$DRY_RUN" = true ]; then
83
+ log_warning "DRY RUN MODE - No files will be modified"
84
+ log_info "Run with --apply to make changes"
85
+ else
86
+ log_warning "APPLY MODE - Files will be modified"
87
+ fi
88
+ echo ""
89
+
90
+ # Find all markdown files in pages directory
91
+ find "$PROJECT_ROOT/pages" -name "*.md" -type f | while read -r file; do
92
+ TOTAL_FILES=$((TOTAL_FILES + 1))
93
+
94
+ # Check if file has a preview field with /assets/ prefix
95
+ if grep -q "^preview: /assets/" "$file" 2>/dev/null; then
96
+ # Extract current preview path
97
+ current_path=$(grep "^preview:" "$file" | head -1 | sed 's/^preview: //')
98
+
99
+ # Remove /assets/ prefix
100
+ new_path=$(echo "$current_path" | sed 's|^/assets/|/|')
101
+
102
+ log_change "$file"
103
+ echo " Old: $current_path"
104
+ echo " New: $new_path"
105
+
106
+ if [ "$DRY_RUN" = false ]; then
107
+ # Use sed to replace the preview line
108
+ if [[ "$OSTYPE" == "darwin"* ]]; then
109
+ # macOS sed requires empty string for -i
110
+ sed -i '' "s|^preview: /assets/|preview: /|" "$file"
111
+ else
112
+ # Linux sed
113
+ sed -i "s|^preview: /assets/|preview: /|" "$file"
114
+ fi
115
+ log_success "Updated: $file"
116
+ fi
117
+
118
+ MODIFIED_FILES=$((MODIFIED_FILES + 1))
119
+ else
120
+ SKIPPED_FILES=$((SKIPPED_FILES + 1))
121
+ fi
122
+ done
123
+
124
+ echo ""
125
+ echo -e "${CYAN}========================================${NC}"
126
+ echo -e "${CYAN}📊 Summary${NC}"
127
+ echo -e "${CYAN}========================================${NC}"
128
+
129
+ # Re-count since subshell doesn't persist variables
130
+ MODIFIED_COUNT=$(find "$PROJECT_ROOT/pages" -name "*.md" -type f -exec grep -l "^preview: /assets/" {} \; 2>/dev/null | wc -l | tr -d ' ')
131
+ TOTAL_COUNT=$(find "$PROJECT_ROOT/pages" -name "*.md" -type f | wc -l | tr -d ' ')
132
+
133
+ if [ "$DRY_RUN" = true ]; then
134
+ echo " Files to update: $MODIFIED_COUNT"
135
+ echo " Total markdown files: $TOTAL_COUNT"
136
+ echo ""
137
+ log_info "Run with --apply to make these changes"
138
+ else
139
+ # After applying, count should be 0
140
+ REMAINING=$(find "$PROJECT_ROOT/pages" -name "*.md" -type f -exec grep -l "^preview: /assets/" {} \; 2>/dev/null | wc -l | tr -d ' ')
141
+ echo " Files updated: $MODIFIED_COUNT"
142
+ echo " Files remaining: $REMAINING"
143
+ echo " Total markdown files: $TOTAL_COUNT"
144
+ fi
145
+ echo ""
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-theme-zer0
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.2
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Amr Abdel
@@ -183,6 +183,7 @@ files:
183
183
  - assets/images/previews/published-documentation-library.png
184
184
  - assets/images/previews/quantum-computing-explained-from-qubits-to-quantum.png
185
185
  - assets/images/previews/science.png
186
+ - assets/images/previews/site-personalization-configuration.png
186
187
  - assets/images/previews/technology.png
187
188
  - assets/images/previews/the-complete-guide-to-startup-funding-in-2025.png
188
189
  - assets/images/previews/the-remote-work-revolution-how-global-teams-are-re.png
@@ -244,6 +245,7 @@ files:
244
245
  - scripts/test/lib/test_validation.sh
245
246
  - scripts/test/lib/test_version.sh
246
247
  - scripts/test/theme/validate
248
+ - scripts/update-preview-paths.sh
247
249
  - scripts/utils/analyze-commits
248
250
  - scripts/utils/fix-markdown
249
251
  - scripts/utils/setup