better_image_tag 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +311 -0
- data/Rakefile +12 -0
- data/app/controllers/concerns/better_image_tag/image_taggable.rb +48 -0
- data/better_image_tag.gemspec +46 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/inlineable +10 -0
- data/lib/better_image_tag/base_image_tag.rb +37 -0
- data/lib/better_image_tag/commands/clear_inline_cache.rb +29 -0
- data/lib/better_image_tag/commands/convert_jpg_to_avif.rb +52 -0
- data/lib/better_image_tag/commands/convert_jpg_to_webp.rb +52 -0
- data/lib/better_image_tag/engine.rb +6 -0
- data/lib/better_image_tag/errors.rb +13 -0
- data/lib/better_image_tag/image_tag.rb +172 -0
- data/lib/better_image_tag/inline_data.rb +102 -0
- data/lib/better_image_tag/picture_tag.rb +103 -0
- data/lib/better_image_tag/railtie.rb +15 -0
- data/lib/better_image_tag/rspec.rb +24 -0
- data/lib/better_image_tag/svg_tag.rb +36 -0
- data/lib/better_image_tag/tasks/clear_inline_cache.rake +10 -0
- data/lib/better_image_tag/tasks/convert_jpgs.rake +16 -0
- data/lib/better_image_tag/version.rb +3 -0
- data/lib/better_image_tag.rb +56 -0
- metadata +207 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterImageTag
|
4
|
+
module Commands
|
5
|
+
class ConvertJpgToAvif
|
6
|
+
def self.call
|
7
|
+
new.call
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@jpgs_converted = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def call
|
15
|
+
ensure_avif_present!
|
16
|
+
|
17
|
+
jpg_assets.each do |jpg|
|
18
|
+
avif = jpg.gsub(/\.jpe?g\z/i, '.avif')
|
19
|
+
next if File.exist? avif
|
20
|
+
|
21
|
+
@jpgs_converted += 1 if system("avif -q 32 -e #{jpg} -o #{avif}")
|
22
|
+
end
|
23
|
+
|
24
|
+
puts "#{@jpgs_converted} jpgs converted to avif."
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def ensure_avif_present!
|
30
|
+
return if avif_exists?
|
31
|
+
|
32
|
+
raise(
|
33
|
+
BetterImageTag::Errors::AvifNotFound,
|
34
|
+
"'avif' not found. Please install go-avif."
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def avif_exists?
|
39
|
+
`which avif`
|
40
|
+
$CHILD_STATUS.success?
|
41
|
+
end
|
42
|
+
|
43
|
+
def jpg_assets
|
44
|
+
Dir.glob "#{asset_path}/**/*.{jpg,jpeg}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def asset_path
|
48
|
+
BetterImageTag.configuration.images_path
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterImageTag
|
4
|
+
module Commands
|
5
|
+
class ConvertJpgToWebp
|
6
|
+
def self.call
|
7
|
+
new.call
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@jpgs_converted = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def call
|
15
|
+
ensure_convert_present!
|
16
|
+
|
17
|
+
jpg_assets.each do |jpg|
|
18
|
+
webp = jpg.gsub(/\.jpe?g\z/i, '.webp')
|
19
|
+
next if File.exist? webp
|
20
|
+
|
21
|
+
@jpgs_converted += 1 if system("convert #{jpg} #{webp}")
|
22
|
+
end
|
23
|
+
|
24
|
+
puts "#{@jpgs_converted} jpgs converted to webp."
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def ensure_convert_present!
|
30
|
+
return if convert_exists?
|
31
|
+
|
32
|
+
raise(
|
33
|
+
BetterImageTag::Errors::ConvertNotFound,
|
34
|
+
"'convert' not found. Please install ImageMagick."
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def convert_exists?
|
39
|
+
`which convert`
|
40
|
+
$CHILD_STATUS.success?
|
41
|
+
end
|
42
|
+
|
43
|
+
def jpg_assets
|
44
|
+
Dir.glob "#{asset_path}/**/*.{jpg,jpeg}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def asset_path
|
48
|
+
BetterImageTag.configuration.images_path
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterImageTag
|
4
|
+
module Errors
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
class MissingAltTag < Error; end
|
8
|
+
class EarlyLazyLoad < Error; end
|
9
|
+
class ConvertNotFound < Error; end
|
10
|
+
class AvifNotFound < Error; end
|
11
|
+
class FileNotFound < Error; end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fastimage'
|
4
|
+
|
5
|
+
module BetterImageTag
|
6
|
+
class ImageTag
|
7
|
+
TRANSPARENT_GIF = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='
|
8
|
+
|
9
|
+
attr_reader :view_context, :options, :images, :tablet_options, :desktop_options
|
10
|
+
attr_accessor :image
|
11
|
+
|
12
|
+
def initialize(view_context, image, options = {})
|
13
|
+
@view_context = view_context
|
14
|
+
@image = with_protocol(image)
|
15
|
+
@images = []
|
16
|
+
@options = options.symbolize_keys
|
17
|
+
|
18
|
+
avif(@options[:avif]) if @options[:avif]
|
19
|
+
webp(@options[:webp]) if @options[:webp]
|
20
|
+
enforce_requirements
|
21
|
+
end
|
22
|
+
|
23
|
+
def with_size
|
24
|
+
return self if options[:width].present? || options[:height].present?
|
25
|
+
return self unless BetterImageTag.configuration.sizing_enabled
|
26
|
+
|
27
|
+
dims = cache("image_tag:with_size:#{image}") { FastImage.size(asset) }
|
28
|
+
options[:width] = dims&.first
|
29
|
+
options[:height] = dims&.last
|
30
|
+
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
# rubocop:disable Metrics/AbcSize
|
35
|
+
def lazy_load(enabled: true)
|
36
|
+
return self unless enabled
|
37
|
+
|
38
|
+
options[:class] = Array(options.fetch(:class, [])).join(' ')
|
39
|
+
options[:class] = "#{options[:class]} lazyload".strip
|
40
|
+
options[:data] = options[:data]
|
41
|
+
.to_h
|
42
|
+
.merge(src: view_context.image_path(image))
|
43
|
+
|
44
|
+
@image = TRANSPARENT_GIF
|
45
|
+
|
46
|
+
self
|
47
|
+
end
|
48
|
+
# rubocop:enable Metrics/AbcSize
|
49
|
+
|
50
|
+
def webp(url = nil)
|
51
|
+
lazy_load_last!
|
52
|
+
@images << (url || image.gsub(/\.[a-z]{2,}*\z/, '.webp'))
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
def avif(url = nil)
|
57
|
+
lazy_load_last!
|
58
|
+
@images << (url || image.gsub(/\.[a-z]{2,}*\z/, '.avif'))
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
def inline
|
63
|
+
@image = InlineData.new(@image).inline_data
|
64
|
+
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_s
|
69
|
+
(svg_string || image_tag_string || picture_tag_string).html_safe
|
70
|
+
end
|
71
|
+
|
72
|
+
def picture_tag
|
73
|
+
result = view_context.image_tag(
|
74
|
+
image,
|
75
|
+
options.except(:webp, :avif).merge(use_super: true)
|
76
|
+
)
|
77
|
+
PictureTag.new(self, result)
|
78
|
+
end
|
79
|
+
|
80
|
+
def tablet_up(*tablet_options)
|
81
|
+
@tablet_options = tablet_options
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
def desktop_up(*desktop_options)
|
86
|
+
@desktop_options = desktop_options
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def svg_string
|
93
|
+
return unless svg?
|
94
|
+
|
95
|
+
SvgTag.new(self).to_s
|
96
|
+
end
|
97
|
+
|
98
|
+
def image_tag_string
|
99
|
+
return if images.any? || tablet_options&.any? || desktop_options&.any?
|
100
|
+
|
101
|
+
view_context.image_tag(
|
102
|
+
image,
|
103
|
+
options.except(:webp, :avif).merge(super_options)
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
def picture_tag_string
|
108
|
+
picture_tag.to_s
|
109
|
+
end
|
110
|
+
|
111
|
+
def svg?
|
112
|
+
MimeMagic.by_magic(@image)&.type == 'image/svg+xml'
|
113
|
+
end
|
114
|
+
|
115
|
+
def super_options
|
116
|
+
if images.any?
|
117
|
+
{ use_picture: true }
|
118
|
+
else
|
119
|
+
{ use_super: true }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def lazy_load_last!
|
124
|
+
return unless image.match?(/^data:/)
|
125
|
+
|
126
|
+
raise EarlyLazyLoad, 'Run lazy_load as the last method in chain'
|
127
|
+
end
|
128
|
+
|
129
|
+
def with_protocol(image)
|
130
|
+
image.match?(%r{^//}) ? "https:#{image}" : image
|
131
|
+
end
|
132
|
+
|
133
|
+
def asset
|
134
|
+
@_asset ||= begin
|
135
|
+
if image.match?(%r{https?://}) || !Object.const_defined?(:Rails)
|
136
|
+
image
|
137
|
+
elsif not_compiled?
|
138
|
+
Rails.application.assets[image].filename
|
139
|
+
else
|
140
|
+
file = Rails.application.assets_manifest.assets[image]
|
141
|
+
|
142
|
+
if file.nil?
|
143
|
+
raise(
|
144
|
+
BetterImageTag::Errors::FileNotFound,
|
145
|
+
"Not found in asset manifest: #{image}"
|
146
|
+
)
|
147
|
+
end
|
148
|
+
|
149
|
+
File.join(Rails.application.assets_manifest.directory, file)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def not_compiled?
|
155
|
+
!!Rails.application.assets
|
156
|
+
end
|
157
|
+
|
158
|
+
def enforce_requirements
|
159
|
+
if BetterImageTag.configuration.require_alt_tags && options[:alt].blank?
|
160
|
+
raise Errors::MissingAltTag, "#{image} is missing an alt tag"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def cache(tag, &block)
|
165
|
+
return unless block
|
166
|
+
|
167
|
+
return block.call unless BetterImageTag.configuration.cache_sizing_enabled
|
168
|
+
|
169
|
+
Rails.cache.fetch tag, &block
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mimemagic'
|
4
|
+
require 'base64'
|
5
|
+
require 'open-uri'
|
6
|
+
|
7
|
+
module BetterImageTag
|
8
|
+
class InlineData
|
9
|
+
HTTP_ERRORS = [
|
10
|
+
EOFError,
|
11
|
+
Errno::ECONNRESET,
|
12
|
+
Errno::EINVAL,
|
13
|
+
Net::HTTPBadResponse,
|
14
|
+
Net::HTTPHeaderSyntaxError,
|
15
|
+
Net::ProtocolError,
|
16
|
+
Timeout::Error,
|
17
|
+
OpenSSL::SSL::SSLError,
|
18
|
+
OpenURI::HTTPError
|
19
|
+
].freeze
|
20
|
+
|
21
|
+
CACHE_PREFIX = 'inline_data'
|
22
|
+
|
23
|
+
def self.inline_data(*args)
|
24
|
+
new(*args).inline_data
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_reader :image
|
28
|
+
|
29
|
+
def initialize(image, local_file: false)
|
30
|
+
@image = image
|
31
|
+
@local_file = local_file
|
32
|
+
end
|
33
|
+
|
34
|
+
def inline_data
|
35
|
+
return image unless BetterImageTag.configuration.inlining_enabled
|
36
|
+
|
37
|
+
cache "#{CACHE_PREFIX}:#{image}" do
|
38
|
+
svg? ? contents : "data:#{content_type};base64,#{base64_contents}"
|
39
|
+
end
|
40
|
+
rescue *HTTP_ERRORS
|
41
|
+
image
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def cache(tag, &block)
|
47
|
+
return unless block
|
48
|
+
|
49
|
+
unless BetterImageTag.configuration.cache_inlining_enabled
|
50
|
+
return block.call
|
51
|
+
end
|
52
|
+
|
53
|
+
Rails.cache.fetch tag, &block
|
54
|
+
end
|
55
|
+
|
56
|
+
def svg?
|
57
|
+
content_type == "image/svg+xml"
|
58
|
+
end
|
59
|
+
|
60
|
+
def content_type
|
61
|
+
MimeMagic.by_magic(contents).type
|
62
|
+
end
|
63
|
+
|
64
|
+
def base64_contents
|
65
|
+
Base64.strict_encode64 contents
|
66
|
+
end
|
67
|
+
|
68
|
+
# rubocop:disable Security/Open
|
69
|
+
def contents
|
70
|
+
@_contents ||= begin
|
71
|
+
if image.match?(%r{https?://})
|
72
|
+
URI.open(image).read
|
73
|
+
elsif local_file?
|
74
|
+
File.read(image)
|
75
|
+
elsif not_compiled?
|
76
|
+
Rails.application.assets[image].to_s
|
77
|
+
else
|
78
|
+
file = Rails.application.assets_manifest.assets[image]
|
79
|
+
|
80
|
+
if file.nil?
|
81
|
+
raise(
|
82
|
+
BetterImageTag::Errors::FileNotFound,
|
83
|
+
"Not found in asset manifest: #{image}"
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
path = File.join(Rails.application.assets_manifest.directory, file)
|
88
|
+
File.read(path)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
# rubocop:enable Security/Open
|
93
|
+
|
94
|
+
def not_compiled?
|
95
|
+
!!Rails.application.assets
|
96
|
+
end
|
97
|
+
|
98
|
+
def local_file?
|
99
|
+
@local_file
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module BetterImageTag
|
6
|
+
class PictureTag
|
7
|
+
attr_reader :image_tag, :default_image_tag, :sources, :srcset
|
8
|
+
|
9
|
+
extend Forwardable
|
10
|
+
def_delegators :image_tag, :image, :images, :view_context
|
11
|
+
def_delegators :view_context, :image_path
|
12
|
+
def_delegators :config, :tablet_breakpoint, :desktop_breakpoint
|
13
|
+
|
14
|
+
def initialize(image_tag, default_image_tag)
|
15
|
+
@image_tag = image_tag
|
16
|
+
@default_image_tag = default_image_tag
|
17
|
+
@sources = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
output(:lazily) || output(:normally)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def config
|
27
|
+
BetterImageTag.configuration
|
28
|
+
end
|
29
|
+
|
30
|
+
def output(loading_style)
|
31
|
+
return if loading_style == :lazily && image != ImageTag::TRANSPARENT_GIF
|
32
|
+
|
33
|
+
@srcset = loading_style == :lazily ? 'data-srcset' : 'srcset'
|
34
|
+
css_class = css_class? ? %( class="#{css_classes}") : ''
|
35
|
+
|
36
|
+
populate_responsive_and_format_sources
|
37
|
+
populate_responsive_sources
|
38
|
+
populate_format_sources
|
39
|
+
|
40
|
+
<<~EOPICTURE
|
41
|
+
<picture#{css_class}>
|
42
|
+
<!--[if IE 9]><video style="display: none;"><![endif]-->
|
43
|
+
#{sources.join("\n ")}
|
44
|
+
<!--[if IE 9]></video><![endif]-->
|
45
|
+
#{default_image_tag}
|
46
|
+
</picture>
|
47
|
+
EOPICTURE
|
48
|
+
end
|
49
|
+
|
50
|
+
def css_class?
|
51
|
+
image_tag.options[:class].present?
|
52
|
+
end
|
53
|
+
|
54
|
+
def css_classes
|
55
|
+
image_tag.options[:class].split(' ').map do |css_class|
|
56
|
+
css_class == 'lazyload' ? 'lazyload' : "#{css_class}--picture"
|
57
|
+
end.join(" ")
|
58
|
+
end
|
59
|
+
|
60
|
+
def populate_responsive_and_format_sources
|
61
|
+
if image_tag.tablet_options&.second
|
62
|
+
if image_tag.tablet_options.second[:avif]
|
63
|
+
@sources << %(<source media="(min-width: #{tablet_breakpoint})" type="image/avif" #{srcset}="#{image_path image_tag.tablet_options.second[:avif]}">)
|
64
|
+
end
|
65
|
+
|
66
|
+
if image_tag.tablet_options.second[:webp]
|
67
|
+
@sources << %(<source media="(min-width: #{tablet_breakpoint})" type="image/webp" #{srcset}="#{image_path image_tag.tablet_options.second[:webp]}">)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
if image_tag.desktop_options&.second
|
72
|
+
if image_tag.desktop_options.second[:avif]
|
73
|
+
@sources << %(<source media="(min-width: #{desktop_breakpoint})" type="image/avif" #{srcset}="#{image_path image_tag.desktop_options.second[:avif]}">)
|
74
|
+
end
|
75
|
+
|
76
|
+
if image_tag.desktop_options.second[:webp]
|
77
|
+
@sources << %(<source media="(min-width: #{desktop_breakpoint})" type="image/webp" #{srcset}="#{image_path image_tag.desktop_options.second[:webp]}">)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def populate_format_sources
|
83
|
+
return if images.empty?
|
84
|
+
|
85
|
+
@sources += images.map do |image|
|
86
|
+
type = image.match?(/webp$/) ? 'webp' : 'avif'
|
87
|
+
%(<source type="image/#{type}" #{srcset}="#{image_path image}">)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def populate_responsive_sources
|
92
|
+
return if image_tag.tablet_options.blank? && image_tag.desktop_options.blank?
|
93
|
+
|
94
|
+
if image_tag.tablet_options
|
95
|
+
@sources << %(<source media="(min-width: #{tablet_breakpoint})" #{srcset}="#{image_path image_tag.tablet_options.first}">)
|
96
|
+
end
|
97
|
+
|
98
|
+
if image_tag.desktop_options
|
99
|
+
@sources << %(<source media="(min-width: #{desktop_breakpoint})" #{srcset}="#{image_path image_tag.desktop_options.first}">)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'better_image_tag'
|
4
|
+
require 'rails'
|
5
|
+
|
6
|
+
module BetterImageTag
|
7
|
+
class Railtie < Rails::Railtie
|
8
|
+
railtie_name :better_image_tag
|
9
|
+
|
10
|
+
rake_tasks do
|
11
|
+
path = File.expand_path(__dir__)
|
12
|
+
Dir.glob("#{path}/tasks/**/*.rake").each { |f| load f }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module BetterImageTag
|
2
|
+
module SpecHelpers
|
3
|
+
def disable_better_image_tag_sizing!
|
4
|
+
BetterImageTag.configuration.sizing_enabled = false
|
5
|
+
end
|
6
|
+
|
7
|
+
def disable_better_image_tag_inlining!
|
8
|
+
BetterImageTag.configuration.inlining_enabled = false
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ViewSpecHelpers
|
13
|
+
def better_image_tag(options = {})
|
14
|
+
view.send(:extend, BetterImageTag::ImageTaggable)
|
15
|
+
|
16
|
+
without_partial_double_verification do
|
17
|
+
allow(view.class).
|
18
|
+
to receive(:better_image_tag_options).
|
19
|
+
and_return(options)
|
20
|
+
allow(view).to receive(:view_context).and_return(view)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterImageTag
|
4
|
+
class SvgTag
|
5
|
+
attr_reader :image_tag
|
6
|
+
|
7
|
+
def initialize(image_tag)
|
8
|
+
@image_tag = image_tag
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
image_tag.image.gsub!(/^\<svg /, %(<svg height="#{height}" )) if height
|
13
|
+
image_tag.image.gsub!(/^\<svg /, %(<svg width="#{width}" )) if width
|
14
|
+
|
15
|
+
if css_class
|
16
|
+
image_tag.image.gsub!(/^\<svg /, %(<svg class="#{css_class}" ))
|
17
|
+
end
|
18
|
+
|
19
|
+
image_tag.image
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def width
|
25
|
+
image_tag.options[:width]
|
26
|
+
end
|
27
|
+
|
28
|
+
def height
|
29
|
+
image_tag.options[:height]
|
30
|
+
end
|
31
|
+
|
32
|
+
def css_class
|
33
|
+
image_tag.options[:class]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'better_image_tag/commands/convert_jpg_to_webp'
|
4
|
+
require 'better_image_tag/commands/convert_jpg_to_avif'
|
5
|
+
|
6
|
+
namespace :better_image_tag do
|
7
|
+
desc 'Convert jpgs to webp'
|
8
|
+
task :convert_jpgs_to_webp do
|
9
|
+
BetterImageTag::Commands::ConvertJpgToWebp.call
|
10
|
+
end
|
11
|
+
|
12
|
+
desc 'Convert jpgs to avif'
|
13
|
+
task :convert_jpgs_to_avif do
|
14
|
+
BetterImageTag::Commands::ConvertJpgToAvif.call
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'better_image_tag/version'
|
4
|
+
require 'better_image_tag/errors'
|
5
|
+
require 'better_image_tag/picture_tag'
|
6
|
+
require 'better_image_tag/svg_tag'
|
7
|
+
require 'better_image_tag/image_tag'
|
8
|
+
require 'better_image_tag/base_image_tag'
|
9
|
+
require 'better_image_tag/inline_data'
|
10
|
+
require_relative '../app/controllers/concerns/better_image_tag/image_taggable'
|
11
|
+
require 'better_image_tag/railtie' if Object.const_defined?(:Rails)
|
12
|
+
|
13
|
+
module BetterImageTag
|
14
|
+
class << self
|
15
|
+
attr_writer :configuration
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.configuration
|
19
|
+
@configuration ||= Configuration.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.configure
|
23
|
+
self.configuration ||= Configuration.new
|
24
|
+
yield(configuration)
|
25
|
+
end
|
26
|
+
|
27
|
+
class Configuration
|
28
|
+
attr_accessor(
|
29
|
+
:cache_inlining_enabled,
|
30
|
+
:cache_sizing_enabled,
|
31
|
+
:inlining_enabled,
|
32
|
+
:require_alt_tags,
|
33
|
+
:sizing_enabled,
|
34
|
+
:images_path,
|
35
|
+
:tablet_breakpoint,
|
36
|
+
:desktop_breakpoint
|
37
|
+
)
|
38
|
+
|
39
|
+
def initialize
|
40
|
+
@require_alt_tags = false
|
41
|
+
@cache_sizing_enabled = false
|
42
|
+
@cache_inlining_enabled = false
|
43
|
+
@inlining_enabled = true
|
44
|
+
@sizing_enabled = true
|
45
|
+
@tablet_breakpoint = '768px'
|
46
|
+
@desktop_breakpoint = '1280px'
|
47
|
+
@images_path = "#{rails_root}/app/assets/images"
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def rails_root
|
53
|
+
Object.const_defined?(:Rails) ? Rails.root : '.'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|