jekyll-imgflow 0.1.6 → 0.1.8
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/README.md +18 -7
- data/lib/jekyll-imgflow/batch_manager.rb +7 -2
- data/lib/jekyll-imgflow/build_time_processor.rb +13 -6
- data/lib/jekyll-imgflow/config.rb +28 -15
- data/lib/jekyll-imgflow/html_generator.rb +35 -8
- data/lib/jekyll-imgflow/imgflow_tag.rb +18 -9
- data/lib/jekyll-imgflow/manifest_manager.rb +2 -7
- data/lib/jekyll-imgflow/operation_processor.rb +45 -6
- data/lib/jekyll-imgflow/path_resolver.rb +9 -6
- data/lib/jekyll-imgflow/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '096d8389110e5128cb043db130059fed14d477e859068074645aa2d915453d78'
|
|
4
|
+
data.tar.gz: 47808ceb1562c5445bb3d6b6acbca1477a19ab9477e414c712bce676c8dd579e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6a22f23719023088512647af702af94e220f7697016cac7ee85d1d894e49be7e2f35c67439e1e6701d14d3a1be20d5cc52cf1961d9dd64da39ae06431b868994
|
|
7
|
+
data.tar.gz: e25a2fb82d17e77791776935adfe333cd2f127396702902988b3956b10d41e4a4e3101ec9a50a09aaf44a876c7baa42c32135c37b1fdb785179a946c2472c793
|
data/README.md
CHANGED
|
@@ -5,6 +5,7 @@ A [Jekyll](https://jekyllrb.com/) plugin for automatic image optimization with m
|
|
|
5
5
|
[](https://deepwiki.com/gundestrup/jekyll-imgflow)
|
|
6
6
|
[](https://github.com/gundestrup/jekyll-imgflow)
|
|
7
7
|
[](https://codecov.io/gh/gundestrup/jekyll-imgflow)
|
|
8
|
+
[](https://github.com/gundestrup/jekyll-imgflow-vscode)
|
|
8
9
|
[](LICENSE)
|
|
9
10
|
|
|
10
11
|
## 🚀 Quick Start
|
|
@@ -16,17 +17,28 @@ gem 'jekyll-imgflow'
|
|
|
16
17
|
# Install
|
|
17
18
|
bundle install
|
|
18
19
|
|
|
19
|
-
# Configure (_config.yml)
|
|
20
|
+
# Configure (_config.yml) — only originals and output are required,
|
|
21
|
+
# everything else has sensible defaults:
|
|
20
22
|
imgflow:
|
|
21
23
|
originals: "assets/images/originals"
|
|
22
24
|
output: "assets/images/optimized"
|
|
23
|
-
sizes: {sm: 400, md: 800, lg: 1200}
|
|
24
|
-
formats: [webp, avif, jpg]
|
|
25
25
|
|
|
26
26
|
# Use in templates
|
|
27
27
|
{% imgflow photo.jpg resize width:800 %}
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
> **Full configuration options:** `quality`, `backend_priority`, `formats`,
|
|
31
|
+
> `fallback_format`, `sizes`, provider URLs, and more — see the
|
|
32
|
+
> [Installation Guide](docs/installation.md#4-add-to-_configyml)
|
|
33
|
+
> and [Configuration Reference](docs/ARCHITECTURE.md).
|
|
34
|
+
|
|
35
|
+
> **Note:** Image references require exact filenames (or paths relative to
|
|
36
|
+
> `originals`). There is no fuzzy matching during the build yet — if you type
|
|
37
|
+
> `photo.jpg` but the file is `photo.jpeg`, the build will fail with an error.
|
|
38
|
+
>
|
|
39
|
+
> For editor autocomplete while writing `{% imgflow %}` tags, install the
|
|
40
|
+
> [VS Code companion extension](https://github.com/gundestrup/jekyll-imgflow-vscode).
|
|
41
|
+
|
|
30
42
|
## ✨ Key Features
|
|
31
43
|
|
|
32
44
|
- **Multiple Providers**: [Sharp](https://github.com/lovell/sharp), [ImageMagick](https://github.com/ImageMagick/ImageMagick), [LibVips](https://github.com/libvips/libvips), [Imgproxy](https://github.com/imgproxy/imgproxy), [Weserv](https://github.com/weserv/images), [Flyimg](https://github.com/flyimg/flyimg)
|
|
@@ -112,18 +124,17 @@ gem 'jekyll-imgflow'
|
|
|
112
124
|
# Install
|
|
113
125
|
bundle install
|
|
114
126
|
|
|
115
|
-
# Configure (_config.yml)
|
|
127
|
+
# Configure (_config.yml) — only originals and output required:
|
|
116
128
|
imgflow:
|
|
117
129
|
originals: "assets/images/originals"
|
|
118
130
|
output: "assets/images/optimized"
|
|
119
|
-
sizes: {sm: 400, md: 800, lg: 1200}
|
|
120
|
-
formats: [webp, avif, jpg]
|
|
121
131
|
|
|
122
132
|
# Use in templates
|
|
123
133
|
{% imgflow photo.jpg resize width:800 %}
|
|
124
134
|
```
|
|
125
135
|
|
|
126
|
-
**See:** [installation.md](docs/installation.md) for detailed installation
|
|
136
|
+
**See:** [installation.md](docs/installation.md) for detailed installation and
|
|
137
|
+
[all configuration options](docs/installation.md#4-add-to-_configyml)
|
|
127
138
|
|
|
128
139
|
## 🐳 Docker Setup (Recommended)
|
|
129
140
|
|
|
@@ -213,8 +213,13 @@ module JekyllImgFlow
|
|
|
213
213
|
# Use FilenameGenerator to generate proper filename
|
|
214
214
|
operations = { width: width, format: format, quality: config.quality }
|
|
215
215
|
filename = filename_generator.generate_filename(original_name, operations)
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
|
|
217
|
+
# Preserve original directory structure under output
|
|
218
|
+
subdir = File.dirname(original_name)
|
|
219
|
+
subdir = nil if subdir == "."
|
|
220
|
+
|
|
221
|
+
# Write to source directory so Jekyll copies files to _site during write phase
|
|
222
|
+
output_path = path_resolver.resolve_source_output_path(filename, subdir)
|
|
218
223
|
|
|
219
224
|
tasks << {
|
|
220
225
|
original_name: original_name,
|
|
@@ -42,12 +42,14 @@ module JekyllImgFlow
|
|
|
42
42
|
original_paths = find_original_images
|
|
43
43
|
Jekyll.logger.info "📸 Found #{original_paths.length} original images"
|
|
44
44
|
|
|
45
|
+
originals_dir = File.join(@site.source, @config.originals)
|
|
46
|
+
|
|
45
47
|
# Build tasks for each original image
|
|
46
48
|
original_paths.each do |path|
|
|
47
|
-
original_name =
|
|
49
|
+
original_name = path.sub("#{originals_dir}/", "")
|
|
48
50
|
|
|
49
51
|
# Check if needs processing (file changed or provider changed)
|
|
50
|
-
if needs_processing?(path)
|
|
52
|
+
if needs_processing?(original_name, path)
|
|
51
53
|
Jekyll.logger.info "🔧 Queuing default versions for: #{original_name}"
|
|
52
54
|
|
|
53
55
|
# Build default tasks
|
|
@@ -84,8 +86,10 @@ module JekyllImgFlow
|
|
|
84
86
|
provider_name = @registry.current_provider.class.name.split("::").last.downcase
|
|
85
87
|
|
|
86
88
|
# Convert absolute path to relative for manifest storage
|
|
89
|
+
# Default versions are written to the source directory so Jekyll can
|
|
90
|
+
# copy them to _site during the write phase.
|
|
87
91
|
absolute_path = task[:result]
|
|
88
|
-
relative_path = absolute_path.sub(@site.
|
|
92
|
+
relative_path = absolute_path.sub(@site.source, "")
|
|
89
93
|
|
|
90
94
|
# Register the version in manifest
|
|
91
95
|
@manifest.register_version(
|
|
@@ -113,10 +117,13 @@ module JekyllImgFlow
|
|
|
113
117
|
end
|
|
114
118
|
|
|
115
119
|
# Check if image needs processing
|
|
116
|
-
# @param
|
|
120
|
+
# @param original_name [String] Path to original image relative to originals directory
|
|
121
|
+
# @param path [String, nil] Full path to original image (defaults to original_name)
|
|
117
122
|
# @return [Boolean] True if processing needed
|
|
118
|
-
def needs_processing?(path)
|
|
119
|
-
|
|
123
|
+
def needs_processing?(original_name, path = nil)
|
|
124
|
+
path ||= original_name
|
|
125
|
+
# For single-argument calls, derive a flat original_name from the full path
|
|
126
|
+
original_name = File.basename(original_name) if original_name == path
|
|
120
127
|
|
|
121
128
|
# If no versions exist, needs processing
|
|
122
129
|
return true unless @manifest.versions?(original_name)
|
|
@@ -2,10 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
module JekyllImgFlow
|
|
4
4
|
class Config
|
|
5
|
+
# Sensible defaults — users only need to configure `originals` and `output`
|
|
6
|
+
# (or even nothing at all). All other values fall back to these.
|
|
7
|
+
DEFAULT_ORIGINALS = "assets/images/originals"
|
|
8
|
+
DEFAULT_OUTPUT = "assets/images/optimized"
|
|
9
|
+
DEFAULT_INPUT_FORMATS = %w[jpg jpeg png webp avif gif tiff tif svg].freeze
|
|
10
|
+
# Output format priority: avif > webp > png > jpg (fallback).
|
|
11
|
+
# The browser picks the first <source> format it supports, so avif is
|
|
12
|
+
# served to modern browsers, webp as a fallback for avif, png for
|
|
13
|
+
# transparency, and jpg as the universal <img> fallback.
|
|
14
|
+
DEFAULT_FORMATS = %w[avif webp png jpg].freeze
|
|
15
|
+
DEFAULT_SIZES = { "sm" => 400, "md" => 800, "lg" => 1200, "xl" => 2000 }.freeze
|
|
16
|
+
DEFAULT_QUALITY = 85
|
|
17
|
+
# Ordered by speed (see docs/providers.md benchmark):
|
|
18
|
+
# sharp (14s) → libvips (22s) → imagemagick (31s) → imgproxy (31s) → weserv (30s) → flyimg
|
|
19
|
+
DEFAULT_BACKEND_PRIORITY = %w[sharp libvips imagemagick imgproxy weserv flyimg].freeze
|
|
20
|
+
# Format used for the <img> fallback in <picture> elements.
|
|
21
|
+
# All other formats in `formats` get <source> tags for browsers that support them.
|
|
22
|
+
DEFAULT_FALLBACK_FORMAT = "jpg"
|
|
23
|
+
|
|
5
24
|
attr_reader :site, :originals, :output, :input_formats, :sizes, :formats,
|
|
6
25
|
:quality, :backend_priority, :imgproxy_url,
|
|
7
26
|
:image_compressor_url, :weserv_url, :flyimg_url,
|
|
8
|
-
:optimize_qualities
|
|
27
|
+
:optimize_qualities, :fallback_format
|
|
9
28
|
|
|
10
29
|
def initialize(site)
|
|
11
30
|
shared = site.config["shared_images_configs"] || {}
|
|
@@ -13,20 +32,14 @@ module JekyllImgFlow
|
|
|
13
32
|
cfg = shared.merge(overrides)
|
|
14
33
|
|
|
15
34
|
@site = site
|
|
16
|
-
@originals = cfg["originals"]
|
|
17
|
-
@output = cfg["output"]
|
|
18
|
-
@input_formats = cfg["input_formats"]
|
|
19
|
-
@sizes = cfg["sizes"]
|
|
20
|
-
@formats = cfg["formats"]
|
|
21
|
-
@quality = cfg["quality"]
|
|
22
|
-
@backend_priority = cfg["backend_priority"]
|
|
23
|
-
|
|
24
|
-
# Validate required config fields
|
|
25
|
-
raise ArgumentError, "No originals configured in unified config" unless @originals
|
|
26
|
-
raise ArgumentError, "No output configured in unified config" unless @output
|
|
27
|
-
raise ArgumentError, "No input_formats configured in unified config" unless @input_formats
|
|
28
|
-
raise ArgumentError, "No formats configured in unified config" unless @formats
|
|
29
|
-
raise ArgumentError, "No sizes configured in unified config" unless @sizes
|
|
35
|
+
@originals = cfg["originals"] || DEFAULT_ORIGINALS
|
|
36
|
+
@output = cfg["output"] || DEFAULT_OUTPUT
|
|
37
|
+
@input_formats = cfg["input_formats"] || DEFAULT_INPUT_FORMATS
|
|
38
|
+
@sizes = cfg["sizes"] || DEFAULT_SIZES
|
|
39
|
+
@formats = cfg["formats"] || DEFAULT_FORMATS
|
|
40
|
+
@quality = cfg["quality"] || DEFAULT_QUALITY
|
|
41
|
+
@backend_priority = cfg["backend_priority"] || DEFAULT_BACKEND_PRIORITY
|
|
42
|
+
@fallback_format = cfg["fallback_format"] || DEFAULT_FALLBACK_FORMAT
|
|
30
43
|
|
|
31
44
|
@imgproxy_url = cfg["imgproxy_url"]
|
|
32
45
|
@image_compressor_url = cfg["image_compressor_url"]
|
|
@@ -58,13 +58,13 @@ module JekyllImgFlow
|
|
|
58
58
|
|
|
59
59
|
private
|
|
60
60
|
|
|
61
|
-
# Get fallback formats
|
|
61
|
+
# Get fallback formats — only the configured fallback format (e.g. jpg).
|
|
62
|
+
# These are skipped in the <source> loop and used for the <img> fallback.
|
|
63
|
+
# All other formats (webp, avif, etc.) get <source> tags.
|
|
62
64
|
def fallback_formats
|
|
63
|
-
if @config&.
|
|
64
|
-
|
|
65
|
-
@config.formats.map { |fmt| ".#{fmt}" }
|
|
65
|
+
if @config&.fallback_format
|
|
66
|
+
[".#{@config.fallback_format}"]
|
|
66
67
|
else
|
|
67
|
-
# Fallback to common formats if no config available
|
|
68
68
|
%w[.jpg .jpeg .png]
|
|
69
69
|
end
|
|
70
70
|
end
|
|
@@ -242,13 +242,40 @@ module JekyllImgFlow
|
|
|
242
242
|
"<div#{parent_attrs}>#{html}</div>"
|
|
243
243
|
end
|
|
244
244
|
|
|
245
|
-
# Group results by file extension
|
|
245
|
+
# Group results by file extension, ordered by config format priority
|
|
246
|
+
# (avif → webp → png → jpg). The browser picks the first <source> format
|
|
247
|
+
# it supports, so the order of <source> tags determines format priority.
|
|
246
248
|
def group_by_format(results)
|
|
247
|
-
results.group_by { |r| File.extname(r).downcase }
|
|
249
|
+
grouped = results.group_by { |r| File.extname(r).downcase }
|
|
250
|
+
sort_format_groups(grouped)
|
|
248
251
|
end
|
|
249
252
|
|
|
250
|
-
#
|
|
253
|
+
# Sort format groups by config format priority (first = highest priority).
|
|
254
|
+
# Formats not in config.formats are appended at the end alphabetically.
|
|
255
|
+
def sort_format_groups(grouped)
|
|
256
|
+
priority = build_format_priority
|
|
257
|
+
grouped.sort_by { |ext, _| priority.index(ext) || Float::INFINITY }
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Build a lookup of format extensions ordered by config.formats priority.
|
|
261
|
+
# E.g. ["avif", "webp", "png", "jpg"] → [".avif", ".webp", ".png", ".jpg"]
|
|
262
|
+
def build_format_priority
|
|
263
|
+
return %w[.avif .webp .png .jpg] unless @config&.formats
|
|
264
|
+
|
|
265
|
+
@config.formats.map { |fmt| ".#{fmt.downcase}" }
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Find fallback image — prefer the configured fallback format,
|
|
269
|
+
# then any legacy format (jpg/jpeg/png), then first result.
|
|
251
270
|
def find_fallback_image(results)
|
|
271
|
+
return results.first if results.empty?
|
|
272
|
+
|
|
273
|
+
preferred = @config&.fallback_format
|
|
274
|
+
if preferred
|
|
275
|
+
found = results.find { |r| r.end_with?(".#{preferred}") }
|
|
276
|
+
return found if found
|
|
277
|
+
end
|
|
278
|
+
|
|
252
279
|
results.find { |r| r.match?(/\.(jpe?g|png)$/i) } || results.first
|
|
253
280
|
end
|
|
254
281
|
|
|
@@ -64,7 +64,15 @@ module Jekyll
|
|
|
64
64
|
|
|
65
65
|
site = context.registers[:site]
|
|
66
66
|
page = context.registers[:page]
|
|
67
|
-
|
|
67
|
+
|
|
68
|
+
# Resolve input path
|
|
69
|
+
input_path = resolve_image_path(parsed[:image_path], site, components[:config])
|
|
70
|
+
raise ArgumentError, "Input image file not found: #{input_path}" unless File.file?(input_path)
|
|
71
|
+
|
|
72
|
+
# Store original name relative to the configured originals directory
|
|
73
|
+
# so output can mirror the original directory structure
|
|
74
|
+
originals_dir = File.join(site.source, components[:config].originals)
|
|
75
|
+
original_name = input_path.sub("#{originals_dir}/", "")
|
|
68
76
|
|
|
69
77
|
# Get page path for manifest tracking
|
|
70
78
|
# Try multiple attributes to get the page identifier
|
|
@@ -74,10 +82,6 @@ module Jekyll
|
|
|
74
82
|
"unknown"
|
|
75
83
|
end
|
|
76
84
|
|
|
77
|
-
# Resolve input path
|
|
78
|
-
input_path = resolve_image_path(parsed[:image_path], site, components[:config])
|
|
79
|
-
raise ArgumentError, "Input image file not found: #{input_path}" unless File.file?(input_path)
|
|
80
|
-
|
|
81
85
|
# Process operations using clean architecture
|
|
82
86
|
operations = parsed[:operations]
|
|
83
87
|
|
|
@@ -96,8 +100,12 @@ module Jekyll
|
|
|
96
100
|
params[:quality] ||= components[:config].quality # Use default quality
|
|
97
101
|
end
|
|
98
102
|
|
|
103
|
+
# Preserve original directory structure under output
|
|
104
|
+
subdir = File.dirname(original_name)
|
|
105
|
+
subdir = nil if subdir == "."
|
|
106
|
+
|
|
99
107
|
filename = components[:filename_generator].generate_filename(input_path, params)
|
|
100
|
-
output_path = components[:path_resolver].
|
|
108
|
+
output_path = components[:path_resolver].resolve_source_output_path(filename, subdir)
|
|
101
109
|
|
|
102
110
|
# Determine version type
|
|
103
111
|
version_type = determine_version_type(params, components[:config])
|
|
@@ -199,11 +207,12 @@ module Jekyll
|
|
|
199
207
|
path_resolver = JekyllImgFlow::PathResolver.new(config)
|
|
200
208
|
|
|
201
209
|
# Handle different path formats
|
|
202
|
-
if image_path.start_with?("/")
|
|
203
|
-
# Absolute
|
|
210
|
+
if image_path.start_with?("/")
|
|
211
|
+
# Absolute site-root path: /assets/images/originals/valdemar/photo.jpg
|
|
204
212
|
File.join(site.source, image_path)
|
|
205
213
|
else
|
|
206
|
-
#
|
|
214
|
+
# Relative path: photo.jpg or valdemar/photo.jpg
|
|
215
|
+
# Always resolve against the configured originals directory
|
|
207
216
|
path_resolver.cli_path(image_path)
|
|
208
217
|
end
|
|
209
218
|
end
|
|
@@ -114,7 +114,7 @@ module JekyllImgFlow
|
|
|
114
114
|
|
|
115
115
|
# Get optimized directory path
|
|
116
116
|
config = JekyllImgFlow::Config.new(@site)
|
|
117
|
-
optimized_dir = File.join(@site.
|
|
117
|
+
optimized_dir = File.join(@site.source, config.output)
|
|
118
118
|
|
|
119
119
|
# Safety guard: prevent deletion of _site itself
|
|
120
120
|
raise "Refusing to delete optimized directory: path resolves to site dest or is empty" if optimized_dir == @site.dest || optimized_dir.empty?
|
|
@@ -261,12 +261,7 @@ module JekyllImgFlow
|
|
|
261
261
|
cleaned = []
|
|
262
262
|
|
|
263
263
|
orphans.each do |orphan|
|
|
264
|
-
output_file =
|
|
265
|
-
orphan["output"]
|
|
266
|
-
else
|
|
267
|
-
File.join(@site.dest,
|
|
268
|
-
orphan["output"])
|
|
269
|
-
end
|
|
264
|
+
output_file = File.join(@site.source, orphan["output"])
|
|
270
265
|
if File.exist?(output_file)
|
|
271
266
|
File.delete(output_file)
|
|
272
267
|
cleaned << orphan["output"]
|
|
@@ -42,13 +42,18 @@ module JekyllImgFlow
|
|
|
42
42
|
type = operation[:type]
|
|
43
43
|
params = operation[:params]
|
|
44
44
|
|
|
45
|
+
# Determine version type
|
|
46
|
+
version_type = determine_version_type(params)
|
|
47
|
+
|
|
48
|
+
# Preserve original directory structure under output
|
|
49
|
+
subdir = File.dirname(original_name)
|
|
50
|
+
subdir = nil if subdir == "."
|
|
51
|
+
|
|
45
52
|
# Generate filename using FilenameGenerator (JPT compatible)
|
|
46
53
|
filename = @filename_generator.generate_filename(input_path, params)
|
|
47
|
-
# Write to _site during build (after Jekyll copies assets)
|
|
48
|
-
actual_output_path = @path_resolver.resolve_output_path(filename)
|
|
49
54
|
|
|
50
|
-
#
|
|
51
|
-
|
|
55
|
+
# Write to source directory so Jekyll copies files to _site during the write phase
|
|
56
|
+
actual_output_path = @path_resolver.resolve_source_output_path(filename, subdir)
|
|
52
57
|
|
|
53
58
|
# Ensure output directory exists before processing
|
|
54
59
|
FileUtils.mkdir_p(File.dirname(actual_output_path))
|
|
@@ -68,8 +73,8 @@ module JekyllImgFlow
|
|
|
68
73
|
|
|
69
74
|
# Register in manifest
|
|
70
75
|
if @manifest
|
|
71
|
-
#
|
|
72
|
-
relative_path =
|
|
76
|
+
# Store relative path (with leading /) for manifest storage
|
|
77
|
+
relative_path = "/#{@path_resolver.resolve_relative_output_path(filename, subdir)}"
|
|
73
78
|
|
|
74
79
|
provider_name = @provider.class.provider_name
|
|
75
80
|
@manifest.register_version(
|
|
@@ -83,9 +88,43 @@ module JekyllImgFlow
|
|
|
83
88
|
)
|
|
84
89
|
end
|
|
85
90
|
|
|
91
|
+
# Register as Jekyll static file so Jekyll copies it to _site during
|
|
92
|
+
# the write phase. Without this, files created during pre_render (or
|
|
93
|
+
# during render via imgflow tags) are not picked up by Jekyll's static
|
|
94
|
+
# file reader, which runs before pre_render. This would leave _site
|
|
95
|
+
# without optimized images until a second build.
|
|
96
|
+
register_jekyll_static_file(actual_output_path)
|
|
97
|
+
|
|
86
98
|
actual_output_path
|
|
87
99
|
end
|
|
88
100
|
|
|
101
|
+
# Register a generated file as a Jekyll::StaticFile so Jekyll copies it
|
|
102
|
+
# to _site during the write phase. No-op for mock/test sites.
|
|
103
|
+
# @param file_path [String] Absolute path to the generated file in source
|
|
104
|
+
def register_jekyll_static_file(file_path)
|
|
105
|
+
site = @config&.site
|
|
106
|
+
return unless site.is_a?(Jekyll::Site)
|
|
107
|
+
|
|
108
|
+
relative = file_path.delete_prefix("#{site.source}/")
|
|
109
|
+
return if relative == file_path # not under site source
|
|
110
|
+
|
|
111
|
+
add_static_file(site, relative) unless static_file_registered?(site, relative)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# @param site [Jekyll::Site] Jekyll site object
|
|
115
|
+
# @param relative [String] Path relative to site source
|
|
116
|
+
def static_file_registered?(site, relative)
|
|
117
|
+
site.static_files.any? { |sf| sf.relative_path == "/#{relative}" }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# @param site [Jekyll::Site] Jekyll site object
|
|
121
|
+
# @param relative [String] Path relative to site source
|
|
122
|
+
def add_static_file(site, relative)
|
|
123
|
+
site.static_files << Jekyll::StaticFile.new(
|
|
124
|
+
site, site.source, File.dirname(relative), File.basename(relative)
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
|
|
89
128
|
# Process multiple operations on an image in sequence (batch)
|
|
90
129
|
# @param operations [Array<Hash>] Array of operations to process
|
|
91
130
|
# @param input_path [String] Path to input image
|
|
@@ -61,26 +61,29 @@ module JekyllImgFlow
|
|
|
61
61
|
|
|
62
62
|
# Resolve output path for generated filename
|
|
63
63
|
# @param filename [String] Generated filename
|
|
64
|
+
# @param subdir [String, nil] Optional subdirectory under output
|
|
64
65
|
# @return [String] Full path to output file in _site (for specialized versions during rendering)
|
|
65
|
-
def resolve_output_path(filename)
|
|
66
|
+
def resolve_output_path(filename, subdir = nil)
|
|
66
67
|
# Resolve output path relative to site destination
|
|
67
|
-
File.join(@config.site.dest, @config.output, filename)
|
|
68
|
+
File.join([@config.site.dest, @config.output, subdir, filename].compact)
|
|
68
69
|
end
|
|
69
70
|
|
|
70
71
|
# Resolve output path to source directory for default images
|
|
71
72
|
# @param filename [String] Generated filename
|
|
73
|
+
# @param subdir [String, nil] Optional subdirectory under output
|
|
72
74
|
# @return [String] Full path to output file in source (for default versions during pre_render)
|
|
73
|
-
def resolve_source_output_path(filename)
|
|
75
|
+
def resolve_source_output_path(filename, subdir = nil)
|
|
74
76
|
# Default images go to source directory so Jekyll can copy them to _site
|
|
75
|
-
File.join(@config.site.source, @config.output, filename)
|
|
77
|
+
File.join([@config.site.source, @config.output, subdir, filename].compact)
|
|
76
78
|
end
|
|
77
79
|
|
|
78
80
|
# Resolve relative output path for manifest storage
|
|
79
81
|
# @param filename [String] Generated filename
|
|
82
|
+
# @param subdir [String, nil] Optional subdirectory under output
|
|
80
83
|
# @return [String] Relative path from site root
|
|
81
|
-
def resolve_relative_output_path(filename)
|
|
84
|
+
def resolve_relative_output_path(filename, subdir = nil)
|
|
82
85
|
# Return path relative to site root (without leading /)
|
|
83
|
-
File.join(@config.output, filename)
|
|
86
|
+
File.join([@config.output, subdir, filename].compact)
|
|
84
87
|
end
|
|
85
88
|
|
|
86
89
|
# Get site destination directory
|