jekyll-imgflow 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/jekyll-imgflow/html_generator.rb +4 -2
- data/lib/jekyll-imgflow/imgflow_tag.rb +45 -2
- data/lib/jekyll-imgflow/modal_assets.rb +71 -10
- data/lib/jekyll-imgflow/picture_tag_adaptor.rb +30 -4
- data/lib/jekyll-imgflow/providers/sharp.rb +9 -3
- 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: fa56ff31b7cfe312dfe202726e049c99db5511dc35b2e82d721abde41c76488e
|
|
4
|
+
data.tar.gz: 88ff3c89e44a1dfe0fe022a016bbeba79185c137bca201c4e2c66c9d06cc5a92
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c1f92e12c629652780356a972969ee435992b29dd62dea03ea263582084e8b512b35eb25e8bb1845c111834a5506cd1c3ffd5c9b4c5392ff6e17e20d3ea5c4ae
|
|
7
|
+
data.tar.gz: 92b73297116fd7dd7d38a5aeb349c81aeab97709f3a85886469dc0443e3fa9e5e35c2c971b1c01bd6682db2c59776c54112682a31e3610c64a6254b3d4067441
|
|
@@ -83,7 +83,8 @@ module JekyllImgFlow
|
|
|
83
83
|
parent: attributes[:parent] || {},
|
|
84
84
|
alt: attributes[:alt],
|
|
85
85
|
link: attributes[:link],
|
|
86
|
-
modal: attributes[:modal]
|
|
86
|
+
modal: attributes[:modal],
|
|
87
|
+
modal_results: attributes[:modal_results] || []
|
|
87
88
|
}
|
|
88
89
|
end
|
|
89
90
|
|
|
@@ -96,7 +97,8 @@ module JekyllImgFlow
|
|
|
96
97
|
parent: {},
|
|
97
98
|
alt: nil,
|
|
98
99
|
link: nil,
|
|
99
|
-
modal: nil
|
|
100
|
+
modal: nil,
|
|
101
|
+
modal_results: []
|
|
100
102
|
}
|
|
101
103
|
end
|
|
102
104
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "shellwords"
|
|
4
|
+
require "fastimage"
|
|
4
5
|
|
|
5
6
|
module Jekyll
|
|
6
7
|
class ImgflowTag < Liquid::Tag
|
|
@@ -60,7 +61,8 @@ module Jekyll
|
|
|
60
61
|
# Combine image path with preset markup. Keep the path quoted because
|
|
61
62
|
# filenames may contain spaces.
|
|
62
63
|
image_markup = if image_path.include?(" ")
|
|
63
|
-
"
|
|
64
|
+
escaped_path = image_path.gsub("\\") { "\\\\" }.gsub('"', '\\"')
|
|
65
|
+
"\"#{escaped_path}\""
|
|
64
66
|
else
|
|
65
67
|
image_path
|
|
66
68
|
end
|
|
@@ -98,11 +100,51 @@ module Jekyll
|
|
|
98
100
|
page_path)
|
|
99
101
|
end
|
|
100
102
|
|
|
103
|
+
modal_results = if operations.empty?
|
|
104
|
+
[]
|
|
105
|
+
else
|
|
106
|
+
process_modal_variants(components, operations.first, parsed, original_name,
|
|
107
|
+
input_path, page_path)
|
|
108
|
+
end
|
|
101
109
|
relative_results = results.uniq.map { |result| relative_result_path(result, site) }
|
|
110
|
+
relative_modal_results = modal_results.uniq.map do |result|
|
|
111
|
+
relative_result_path(result, site)
|
|
112
|
+
end
|
|
102
113
|
parsed = parsed.merge(markup_format: "picture") if relative_results.length > 1
|
|
114
|
+
parsed = parsed.merge(modal_results: relative_modal_results)
|
|
103
115
|
generate_html(relative_results, parsed, context)
|
|
104
116
|
end
|
|
105
117
|
|
|
118
|
+
def process_modal_variants(components, operation, parsed, original_name, input_path, page_path)
|
|
119
|
+
config = components[:config]
|
|
120
|
+
return [] unless modal_requested?(parsed, config)
|
|
121
|
+
|
|
122
|
+
original_width = FastImage.size(input_path)&.first
|
|
123
|
+
return [] unless original_width
|
|
124
|
+
|
|
125
|
+
params = operation[:params].dup
|
|
126
|
+
formats = Array(params.delete(:formats) || params[:format])
|
|
127
|
+
formats = config.formats if formats.empty?
|
|
128
|
+
modal_width = [original_width, config.sizes.values.max].min
|
|
129
|
+
|
|
130
|
+
formats.map do |format|
|
|
131
|
+
modal_params = params.merge(width: modal_width, format: format)
|
|
132
|
+
process_variant(components, operation, modal_params, original_name, input_path, page_path)
|
|
133
|
+
end
|
|
134
|
+
rescue FastImage::ImageFetchError, FastImage::UnknownImageType
|
|
135
|
+
[]
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def modal_requested?(parsed, config)
|
|
139
|
+
return false if %w[direct_url naked_srcset].include?(parsed[:markup_format])
|
|
140
|
+
|
|
141
|
+
attrs = parsed[:html_attributes] || {}
|
|
142
|
+
return false if attrs[:link]
|
|
143
|
+
|
|
144
|
+
modal = attrs[:modal]
|
|
145
|
+
modal.nil? ? (config.respond_to?(:image_modal) && config.image_modal) : modal.to_s == "true"
|
|
146
|
+
end
|
|
147
|
+
|
|
106
148
|
def process_variants(components, operation, original_name, input_path, page_path)
|
|
107
149
|
params = operation[:params].dup
|
|
108
150
|
formats = Array(params.delete(:formats) || params[:format])
|
|
@@ -194,7 +236,8 @@ module Jekyll
|
|
|
194
236
|
parent: extract_prefixed_attrs(base_attrs, "parent-"),
|
|
195
237
|
alt: base_attrs[:alt],
|
|
196
238
|
link: base_attrs[:link],
|
|
197
|
-
modal: base_attrs[:modal]
|
|
239
|
+
modal: base_attrs[:modal],
|
|
240
|
+
modal_results: parsed[:modal_results] || []
|
|
198
241
|
}
|
|
199
242
|
end
|
|
200
243
|
|
|
@@ -20,15 +20,24 @@ module JekyllImgFlow
|
|
|
20
20
|
|
|
21
21
|
JS = <<~JS
|
|
22
22
|
(function(){
|
|
23
|
-
function openModal(
|
|
23
|
+
function openModal(trigger,alt){
|
|
24
24
|
var o=document.createElement('div');
|
|
25
25
|
o.className='imgflow-modal-overlay';
|
|
26
|
+
var p=document.createElement('picture');
|
|
27
|
+
var href=trigger.getAttribute('href');
|
|
28
|
+
var types={avif:'image/avif',webp:'image/webp',png:'image/png',jpg:'image/jpeg',jpeg:'image/jpeg'};
|
|
29
|
+
var formats=(trigger.getAttribute('data-imgflow-modal-formats')||'').split(',');
|
|
30
|
+
formats.forEach(function(format){
|
|
31
|
+
if(!format)return;
|
|
32
|
+
var s=document.createElement('source');
|
|
33
|
+
s.srcset=href.replace(/.[^.]+$/,'.'+format);s.type=types[format];p.appendChild(s);});
|
|
26
34
|
var i=document.createElement('img');
|
|
27
|
-
i.src=
|
|
35
|
+
i.src=href;
|
|
36
|
+
i.alt=alt||'';i.className='imgflow-modal-image';p.appendChild(i);
|
|
28
37
|
var b=document.createElement('button');
|
|
29
38
|
b.className='imgflow-modal-close';b.setAttribute('aria-label','Close');
|
|
30
39
|
b.innerHTML='×';
|
|
31
|
-
o.appendChild(b);o.appendChild(
|
|
40
|
+
o.appendChild(b);o.appendChild(p);document.body.appendChild(o);
|
|
32
41
|
function c(){document.body.removeChild(o);
|
|
33
42
|
document.removeEventListener('keydown',k);}
|
|
34
43
|
function k(e){if(e.key==='Escape')c();}
|
|
@@ -38,7 +47,7 @@ module JekyllImgFlow
|
|
|
38
47
|
document.addEventListener('click',function(e){
|
|
39
48
|
var t=e.target.closest('[data-imgflow-modal]');
|
|
40
49
|
if(!t)return;e.preventDefault();
|
|
41
|
-
openModal(t
|
|
50
|
+
openModal(t,t.getAttribute('data-imgflow-alt'));});
|
|
42
51
|
})();
|
|
43
52
|
JS
|
|
44
53
|
|
|
@@ -84,20 +93,72 @@ module JekyllImgFlow
|
|
|
84
93
|
@config&.image_modal ? true : false
|
|
85
94
|
end
|
|
86
95
|
|
|
87
|
-
# Pick the
|
|
88
|
-
# (e.g. jpg) for maximum browser compatibility
|
|
96
|
+
# Pick the largest existing fallback-format variant for the modal.
|
|
89
97
|
def modal_image_path
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
98
|
+
variants = modal_variants
|
|
99
|
+
variants[modal_fallback_format] || @results.last
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def modal_variants
|
|
103
|
+
candidates = @attributes[:modal_results] + @results + generated_modal_results
|
|
104
|
+
max_width = modal_max_width
|
|
105
|
+
|
|
106
|
+
candidates.each_with_object({}) do |result, variants|
|
|
107
|
+
parsed = JekyllImgFlow::FilenameGenerator.new.parse_filename(File.basename(result))
|
|
108
|
+
next if parsed.empty? || (max_width && parsed[:width] > max_width)
|
|
109
|
+
next if variants[parsed[:format]] && modal_width(variants[parsed[:format]]) >= parsed[:width]
|
|
110
|
+
|
|
111
|
+
variants[parsed[:format]] = result
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def modal_fallback_format
|
|
116
|
+
@config&.fallback_format.to_s
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def modal_max_width
|
|
120
|
+
return unless @config.respond_to?(:sizes)
|
|
121
|
+
|
|
122
|
+
@config.sizes.values.max
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def generated_modal_results
|
|
126
|
+
return [] unless @config && @context&.registers&.key?(:site)
|
|
127
|
+
|
|
128
|
+
result = @results.first
|
|
129
|
+
parsed = JekyllImgFlow::FilenameGenerator.new.parse_filename(File.basename(result))
|
|
130
|
+
return [] if parsed.empty?
|
|
131
|
+
|
|
132
|
+
site = @context.registers[:site]
|
|
133
|
+
directory = File.join(site.source, File.dirname(result.delete_prefix("/")))
|
|
134
|
+
return [] unless File.directory?(directory)
|
|
135
|
+
|
|
136
|
+
max_width = @config.sizes.values.max
|
|
137
|
+
Dir.children(directory).filter_map do |filename|
|
|
138
|
+
candidate = JekyllImgFlow::FilenameGenerator.new.parse_filename(filename)
|
|
139
|
+
next unless candidate[:base_name] == parsed[:base_name]
|
|
140
|
+
next unless candidate[:hash] == parsed[:hash]
|
|
141
|
+
next if max_width && candidate[:width] > max_width
|
|
142
|
+
|
|
143
|
+
File.join(File.dirname(result), filename)
|
|
144
|
+
end
|
|
145
|
+
rescue Errno::ENOENT
|
|
146
|
+
[]
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def modal_width(result)
|
|
150
|
+
JekyllImgFlow::FilenameGenerator.new.parse_filename(File.basename(result))[:width] || 0
|
|
93
151
|
end
|
|
94
152
|
|
|
95
153
|
# Wrap HTML in modal trigger anchor
|
|
96
154
|
def wrap_with_modal(html)
|
|
97
|
-
|
|
155
|
+
variants = modal_variants
|
|
156
|
+
modal_href = html_path(variants[modal_fallback_format] || @results.last)
|
|
98
157
|
return html unless modal_href
|
|
99
158
|
|
|
159
|
+
source_formats = variants.keys.reject { |format| format == modal_fallback_format }
|
|
100
160
|
data_attrs = " data-imgflow-modal"
|
|
161
|
+
data_attrs += " data-imgflow-modal-formats=\"#{source_formats.join(',')}\"" unless source_formats.empty?
|
|
101
162
|
alt = @attributes[:alt]
|
|
102
163
|
data_attrs += " data-imgflow-alt=\"#{escape_attr_value(alt)}\"" if alt
|
|
103
164
|
|
|
@@ -42,11 +42,11 @@ module JekyllImgFlow
|
|
|
42
42
|
# @param picture_markup [String] Picture Tag markup like "{% picture hero image.jpg 16:9 --alt Text %}"
|
|
43
43
|
# @return [Hash] Translation result with markup and attributes
|
|
44
44
|
def translate_to_imgflow(picture_markup)
|
|
45
|
-
# Extract content between {% picture ... %}
|
|
46
|
-
|
|
47
|
-
return { markup: "", attributes: {} } unless
|
|
45
|
+
# Extract content between {% picture ... %} without a backtracking regex
|
|
46
|
+
content = extract_picture_content(picture_markup)
|
|
47
|
+
return { markup: "", attributes: {} } unless content
|
|
48
48
|
|
|
49
|
-
content =
|
|
49
|
+
content = content.strip
|
|
50
50
|
return { markup: "", attributes: {} } if content.empty?
|
|
51
51
|
|
|
52
52
|
# Parse arguments
|
|
@@ -75,6 +75,32 @@ module JekyllImgFlow
|
|
|
75
75
|
|
|
76
76
|
private
|
|
77
77
|
|
|
78
|
+
def extract_picture_content(markup)
|
|
79
|
+
offset = 0
|
|
80
|
+
while (opening = markup.index("{%", offset))
|
|
81
|
+
cursor = skip_whitespace(markup, opening + 2)
|
|
82
|
+
if markup[cursor, 7] == "picture"
|
|
83
|
+
cursor += 7
|
|
84
|
+
if cursor < markup.length && whitespace?(markup[cursor])
|
|
85
|
+
cursor = skip_whitespace(markup, cursor)
|
|
86
|
+
closing = markup.index("%}", cursor)
|
|
87
|
+
return markup[cursor...closing] if closing
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
offset = opening + 2
|
|
91
|
+
end
|
|
92
|
+
nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def skip_whitespace(markup, cursor)
|
|
96
|
+
cursor += 1 while cursor < markup.length && whitespace?(markup[cursor])
|
|
97
|
+
cursor
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def whitespace?(character)
|
|
101
|
+
[" ", "\t", "\r", "\n"].include?(character)
|
|
102
|
+
end
|
|
103
|
+
|
|
78
104
|
# Parse arguments handling quoted paths and attributes
|
|
79
105
|
# @param content [String] Raw content
|
|
80
106
|
# @return [Array] Parsed arguments
|
|
@@ -79,7 +79,7 @@ module JekyllImgFlow
|
|
|
79
79
|
|
|
80
80
|
format_op = @operations.find { |op| op[:type] == :format }
|
|
81
81
|
quality_op = @operations.find { |op| op[:type] == :quality }
|
|
82
|
-
command_parts += ["-f", format_op[:format]] if format_op
|
|
82
|
+
command_parts += ["-f", sharp_format(format_op[:format])] if format_op
|
|
83
83
|
command_parts += ["-q#{quality_op[:quality]}"] if quality_op
|
|
84
84
|
|
|
85
85
|
if opacity && opacity < 1.0
|
|
@@ -142,7 +142,7 @@ module JekyllImgFlow
|
|
|
142
142
|
quality_op = @operations.find { |op| op[:type] == :quality }
|
|
143
143
|
alpha_op = @operations.find { |op| op[:type] == :alpha_opacity }
|
|
144
144
|
|
|
145
|
-
command_parts += ["-f", format_op[:format]] if format_op
|
|
145
|
+
command_parts += ["-f", sharp_format(format_op[:format])] if format_op
|
|
146
146
|
command_parts += ["-q#{quality_op[:quality]}"] if quality_op
|
|
147
147
|
if alpha_op
|
|
148
148
|
alpha_value = (alpha_op[:opacity] * 255).round
|
|
@@ -200,7 +200,7 @@ module JekyllImgFlow
|
|
|
200
200
|
quality_op = @operations.find { |op| op[:type] == :quality }
|
|
201
201
|
alpha_op = @operations.find { |op| op[:type] == :alpha_opacity }
|
|
202
202
|
|
|
203
|
-
command_parts += ["-f", format_op[:format]] if format_op
|
|
203
|
+
command_parts += ["-f", sharp_format(format_op[:format])] if format_op
|
|
204
204
|
command_parts += ["-q#{quality_op[:quality]}"] if quality_op
|
|
205
205
|
if alpha_op
|
|
206
206
|
alpha_value = (alpha_op[:opacity] * 255).round
|
|
@@ -209,6 +209,12 @@ module JekyllImgFlow
|
|
|
209
209
|
|
|
210
210
|
command_parts.join(" ")
|
|
211
211
|
end
|
|
212
|
+
|
|
213
|
+
# Sharp CLI calls JPEG "jpeg", while ImgFlow uses the standard "jpg"
|
|
214
|
+
# extension in generated filenames and public configuration.
|
|
215
|
+
def sharp_format(format)
|
|
216
|
+
format.to_s == "jpg" ? "jpeg" : format
|
|
217
|
+
end
|
|
212
218
|
end
|
|
213
219
|
end
|
|
214
220
|
end
|