actionview 7.0.8.6 → 7.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +235 -387
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/app/assets/javascripts/rails-ujs.esm.js +668 -0
- data/app/assets/javascripts/rails-ujs.js +606 -0
- data/lib/action_view/base.rb +28 -7
- data/lib/action_view/buffers.rb +106 -8
- data/lib/action_view/cache_expiry.rb +40 -43
- data/lib/action_view/context.rb +1 -1
- data/lib/action_view/deprecator.rb +7 -0
- data/lib/action_view/digestor.rb +1 -1
- data/lib/action_view/gem_version.rb +4 -4
- data/lib/action_view/helpers/active_model_helper.rb +1 -1
- data/lib/action_view/helpers/asset_tag_helper.rb +130 -46
- data/lib/action_view/helpers/asset_url_helper.rb +6 -5
- data/lib/action_view/helpers/atom_feed_helper.rb +5 -5
- data/lib/action_view/helpers/cache_helper.rb +3 -9
- data/lib/action_view/helpers/capture_helper.rb +24 -10
- data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
- data/lib/action_view/helpers/controller_helper.rb +6 -0
- data/lib/action_view/helpers/csp_helper.rb +2 -2
- data/lib/action_view/helpers/csrf_helper.rb +2 -2
- data/lib/action_view/helpers/date_helper.rb +17 -19
- data/lib/action_view/helpers/debug_helper.rb +3 -3
- data/lib/action_view/helpers/form_helper.rb +43 -18
- data/lib/action_view/helpers/form_options_helper.rb +2 -1
- data/lib/action_view/helpers/form_tag_helper.rb +43 -9
- data/lib/action_view/helpers/javascript_helper.rb +1 -0
- data/lib/action_view/helpers/number_helper.rb +2 -1
- data/lib/action_view/helpers/output_safety_helper.rb +2 -2
- data/lib/action_view/helpers/rendering_helper.rb +1 -1
- data/lib/action_view/helpers/sanitize_helper.rb +33 -14
- data/lib/action_view/helpers/tag_helper.rb +5 -27
- data/lib/action_view/helpers/tags/base.rb +11 -52
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +1 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -0
- data/lib/action_view/helpers/tags/collection_select.rb +3 -0
- data/lib/action_view/helpers/tags/date_field.rb +1 -1
- data/lib/action_view/helpers/tags/date_select.rb +2 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +14 -6
- data/lib/action_view/helpers/tags/datetime_local_field.rb +11 -2
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -0
- data/lib/action_view/helpers/tags/month_field.rb +1 -1
- data/lib/action_view/helpers/tags/select.rb +3 -0
- data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
- data/lib/action_view/helpers/tags/time_field.rb +1 -1
- data/lib/action_view/helpers/tags/time_zone_select.rb +3 -0
- data/lib/action_view/helpers/tags/week_field.rb +1 -1
- data/lib/action_view/helpers/tags/weekday_select.rb +3 -0
- data/lib/action_view/helpers/tags.rb +2 -0
- data/lib/action_view/helpers/text_helper.rb +32 -16
- data/lib/action_view/helpers/translation_helper.rb +3 -3
- data/lib/action_view/helpers/url_helper.rb +41 -14
- data/lib/action_view/helpers.rb +2 -0
- data/lib/action_view/layouts.rb +4 -2
- data/lib/action_view/log_subscriber.rb +49 -32
- data/lib/action_view/lookup_context.rb +29 -13
- data/lib/action_view/path_registry.rb +57 -0
- data/lib/action_view/path_set.rb +13 -14
- data/lib/action_view/railtie.rb +26 -3
- data/lib/action_view/record_identifier.rb +15 -8
- data/lib/action_view/renderer/abstract_renderer.rb +1 -1
- data/lib/action_view/renderer/collection_renderer.rb +9 -1
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +2 -1
- data/lib/action_view/renderer/partial_renderer.rb +2 -1
- data/lib/action_view/renderer/renderer.rb +2 -0
- data/lib/action_view/renderer/streaming_template_renderer.rb +3 -2
- data/lib/action_view/renderer/template_renderer.rb +3 -2
- data/lib/action_view/rendering.rb +22 -4
- data/lib/action_view/ripper_ast_parser.rb +6 -6
- data/lib/action_view/template/error.rb +14 -1
- data/lib/action_view/template/handlers/builder.rb +4 -4
- data/lib/action_view/template/handlers/erb/erubi.rb +23 -27
- data/lib/action_view/template/handlers/erb.rb +73 -1
- data/lib/action_view/template/handlers.rb +1 -1
- data/lib/action_view/template/html.rb +1 -1
- data/lib/action_view/template/raw_file.rb +1 -1
- data/lib/action_view/template/renderable.rb +1 -1
- data/lib/action_view/template/resolver.rb +10 -2
- data/lib/action_view/template/text.rb +1 -1
- data/lib/action_view/template/types.rb +25 -34
- data/lib/action_view/template.rb +179 -52
- data/lib/action_view/template_path.rb +2 -0
- data/lib/action_view/test_case.rb +8 -5
- data/lib/action_view/unbound_template.rb +15 -5
- data/lib/action_view/version.rb +1 -1
- data/lib/action_view/view_paths.rb +15 -24
- data/lib/action_view.rb +4 -1
- metadata +29 -29
data/lib/action_view/buffers.rb
CHANGED
@@ -18,24 +18,91 @@ module ActionView
|
|
18
18
|
# sbuf << 5
|
19
19
|
# puts sbuf # => "hello\u0005"
|
20
20
|
#
|
21
|
-
class OutputBuffer
|
22
|
-
def initialize(
|
23
|
-
|
24
|
-
encode!
|
21
|
+
class OutputBuffer # :nodoc:
|
22
|
+
def initialize(buffer = "")
|
23
|
+
@raw_buffer = String.new(buffer)
|
24
|
+
@raw_buffer.encode!
|
25
|
+
end
|
26
|
+
|
27
|
+
delegate :length, :empty?, :blank?, :encoding, :encode!, :force_encoding, to: :@raw_buffer
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
@raw_buffer.html_safe
|
31
|
+
end
|
32
|
+
alias_method :html_safe, :to_s
|
33
|
+
|
34
|
+
def to_str
|
35
|
+
@raw_buffer.dup
|
36
|
+
end
|
37
|
+
|
38
|
+
def html_safe?
|
39
|
+
true
|
25
40
|
end
|
26
41
|
|
27
42
|
def <<(value)
|
28
|
-
|
29
|
-
|
43
|
+
unless value.nil?
|
44
|
+
value = value.to_s
|
45
|
+
@raw_buffer << if value.html_safe?
|
46
|
+
value
|
47
|
+
else
|
48
|
+
CGI.escapeHTML(value)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
self
|
30
52
|
end
|
53
|
+
alias :concat :<<
|
31
54
|
alias :append= :<<
|
32
55
|
|
56
|
+
def safe_concat(value)
|
57
|
+
@raw_buffer << value
|
58
|
+
self
|
59
|
+
end
|
60
|
+
alias :safe_append= :safe_concat
|
61
|
+
|
33
62
|
def safe_expr_append=(val)
|
34
63
|
return self if val.nil?
|
35
|
-
|
64
|
+
@raw_buffer << val.to_s
|
65
|
+
self
|
36
66
|
end
|
37
67
|
|
38
|
-
|
68
|
+
def initialize_copy(other)
|
69
|
+
@raw_buffer = other.to_str
|
70
|
+
end
|
71
|
+
|
72
|
+
def capture(*args)
|
73
|
+
new_buffer = +""
|
74
|
+
old_buffer, @raw_buffer = @raw_buffer, new_buffer
|
75
|
+
yield(*args)
|
76
|
+
new_buffer.html_safe
|
77
|
+
ensure
|
78
|
+
@raw_buffer = old_buffer
|
79
|
+
end
|
80
|
+
|
81
|
+
def ==(other)
|
82
|
+
other.class == self.class && @raw_buffer == other.to_str
|
83
|
+
end
|
84
|
+
|
85
|
+
def raw
|
86
|
+
RawOutputBuffer.new(self)
|
87
|
+
end
|
88
|
+
|
89
|
+
attr_reader :raw_buffer
|
90
|
+
end
|
91
|
+
|
92
|
+
class RawOutputBuffer # :nodoc:
|
93
|
+
def initialize(buffer)
|
94
|
+
@buffer = buffer
|
95
|
+
end
|
96
|
+
|
97
|
+
def <<(value)
|
98
|
+
unless value.nil?
|
99
|
+
@buffer.raw_buffer << value.to_s
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def raw
|
104
|
+
self
|
105
|
+
end
|
39
106
|
end
|
40
107
|
|
41
108
|
class StreamingBuffer # :nodoc:
|
@@ -56,6 +123,15 @@ module ActionView
|
|
56
123
|
end
|
57
124
|
alias :safe_append= :safe_concat
|
58
125
|
|
126
|
+
def capture
|
127
|
+
buffer = +""
|
128
|
+
old_block, @block = @block, ->(value) { buffer << value }
|
129
|
+
yield
|
130
|
+
buffer.html_safe
|
131
|
+
ensure
|
132
|
+
@block = old_block
|
133
|
+
end
|
134
|
+
|
59
135
|
def html_safe?
|
60
136
|
true
|
61
137
|
end
|
@@ -63,5 +139,27 @@ module ActionView
|
|
63
139
|
def html_safe
|
64
140
|
self
|
65
141
|
end
|
142
|
+
|
143
|
+
def raw
|
144
|
+
RawStreamingBuffer.new(self)
|
145
|
+
end
|
146
|
+
|
147
|
+
attr_reader :block
|
148
|
+
end
|
149
|
+
|
150
|
+
class RawStreamingBuffer # :nodoc:
|
151
|
+
def initialize(buffer)
|
152
|
+
@buffer = buffer
|
153
|
+
end
|
154
|
+
|
155
|
+
def <<(value)
|
156
|
+
unless value.nil?
|
157
|
+
@buffer.block.call(value.to_s)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def raw
|
162
|
+
self
|
163
|
+
end
|
66
164
|
end
|
67
165
|
end
|
@@ -1,65 +1,62 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActionView
|
4
|
-
|
5
|
-
class
|
6
|
-
def initialize(watcher
|
7
|
-
@
|
8
|
-
@
|
9
|
-
|
10
|
-
|
11
|
-
|
4
|
+
module CacheExpiry # :nodoc: all
|
5
|
+
class ViewReloader
|
6
|
+
def initialize(watcher:, &block)
|
7
|
+
@mutex = Mutex.new
|
8
|
+
@watcher_class = watcher
|
9
|
+
@watched_dirs = nil
|
10
|
+
@watcher = nil
|
11
|
+
@previous_change = false
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
@execution_lock.acquire_read_lock
|
17
|
-
end
|
13
|
+
rebuild_watcher
|
14
|
+
|
15
|
+
ActionView::PathRegistry.file_system_resolver_hooks << method(:rebuild_watcher)
|
18
16
|
end
|
19
17
|
|
20
|
-
def
|
21
|
-
@
|
18
|
+
def updated?
|
19
|
+
@previous_change || @watcher.updated?
|
22
20
|
end
|
23
21
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
22
|
+
def execute
|
23
|
+
watcher = nil
|
24
|
+
@mutex.synchronize do
|
25
|
+
@previous_change = false
|
26
|
+
watcher = @watcher
|
29
27
|
end
|
30
|
-
|
31
|
-
|
32
|
-
class ViewModificationWatcher
|
33
|
-
def initialize(watcher:, &block)
|
34
|
-
@watched_dirs = nil
|
35
|
-
@watcher_class = watcher
|
36
|
-
@watcher = nil
|
37
|
-
@mutex = Mutex.new
|
38
|
-
@block = block
|
28
|
+
watcher.execute
|
39
29
|
end
|
40
30
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
31
|
+
private
|
32
|
+
def reload!
|
33
|
+
ActionView::LookupContext::DetailsKey.clear
|
34
|
+
end
|
45
35
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
36
|
+
def rebuild_watcher
|
37
|
+
@mutex.synchronize do
|
38
|
+
old_watcher = @watcher
|
39
|
+
|
40
|
+
if @watched_dirs != dirs_to_watch
|
41
|
+
@watched_dirs = dirs_to_watch
|
42
|
+
new_watcher = @watcher_class.new([], @watched_dirs) do
|
43
|
+
reload!
|
44
|
+
end
|
45
|
+
@watcher = new_watcher
|
46
|
+
|
47
|
+
# We must check the old watcher after initializing the new one to
|
48
|
+
# ensure we don't miss any events
|
49
|
+
@previous_change ||= old_watcher&.updated?
|
50
|
+
end
|
52
51
|
end
|
53
52
|
end
|
54
|
-
end
|
55
53
|
|
56
|
-
private
|
57
54
|
def dirs_to_watch
|
58
|
-
all_view_paths.
|
55
|
+
all_view_paths.uniq.sort
|
59
56
|
end
|
60
57
|
|
61
58
|
def all_view_paths
|
62
|
-
ActionView::
|
59
|
+
ActionView::PathRegistry.all_file_system_resolvers.map(&:path)
|
63
60
|
end
|
64
61
|
end
|
65
62
|
end
|
data/lib/action_view/context.rb
CHANGED
data/lib/action_view/digestor.rb
CHANGED
@@ -11,7 +11,7 @@ module ActionView
|
|
11
11
|
#
|
12
12
|
# * <tt>name</tt> - Template name
|
13
13
|
# * <tt>format</tt> - Template format
|
14
|
-
# *
|
14
|
+
# * +finder+ - An instance of ActionView::LookupContext
|
15
15
|
# * <tt>dependencies</tt> - An array of dependent views
|
16
16
|
def digest(name:, format: nil, finder:, dependencies: nil)
|
17
17
|
if dependencies.nil? || dependencies.empty?
|
@@ -1,16 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActionView
|
4
|
-
# Returns the currently loaded version of Action View as a
|
4
|
+
# Returns the currently loaded version of Action View as a +Gem::Version+.
|
5
5
|
def self.gem_version
|
6
6
|
Gem::Version.new VERSION::STRING
|
7
7
|
end
|
8
8
|
|
9
9
|
module VERSION
|
10
10
|
MAJOR = 7
|
11
|
-
MINOR =
|
12
|
-
TINY =
|
13
|
-
PRE = "
|
11
|
+
MINOR = 1
|
12
|
+
TINY = 0
|
13
|
+
PRE = "beta1"
|
14
14
|
|
15
15
|
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
|
16
16
|
end
|
@@ -4,11 +4,11 @@ require "active_support/core_ext/module/attribute_accessors"
|
|
4
4
|
require "active_support/core_ext/enumerable"
|
5
5
|
|
6
6
|
module ActionView
|
7
|
-
# = Active Model Helpers
|
8
7
|
module Helpers # :nodoc:
|
9
8
|
module ActiveModelHelper
|
10
9
|
end
|
11
10
|
|
11
|
+
# = Active \Model Instance Tag \Helpers
|
12
12
|
module ActiveModelInstanceTag
|
13
13
|
def object
|
14
14
|
@active_model_object ||= begin
|
@@ -7,8 +7,9 @@ require "action_view/helpers/asset_url_helper"
|
|
7
7
|
require "action_view/helpers/tag_helper"
|
8
8
|
|
9
9
|
module ActionView
|
10
|
-
# = Action View Asset Tag Helpers
|
11
10
|
module Helpers # :nodoc:
|
11
|
+
# = Action View Asset Tag \Helpers
|
12
|
+
#
|
12
13
|
# This module provides methods for generating HTML that links views to assets such
|
13
14
|
# as images, JavaScripts, stylesheets, and feeds. These methods do not verify
|
14
15
|
# the assets exist before linking to them:
|
@@ -41,13 +42,14 @@ module ActionView
|
|
41
42
|
# When the Asset Pipeline is enabled, you can pass the name of your manifest as
|
42
43
|
# source, and include other JavaScript or CoffeeScript files inside the manifest.
|
43
44
|
#
|
44
|
-
# If the server supports Early Hints,
|
45
|
-
#
|
45
|
+
# If the server supports HTTP Early Hints, and the +defer+ option is not
|
46
|
+
# enabled, \Rails will push a <tt>103 Early Hints</tt> response that links
|
47
|
+
# to the assets.
|
46
48
|
#
|
47
49
|
# ==== Options
|
48
50
|
#
|
49
51
|
# When the last parameter is a hash you can add HTML attributes using that
|
50
|
-
# parameter.
|
52
|
+
# parameter. This includes but is not limited to the following options:
|
51
53
|
#
|
52
54
|
# * <tt>:extname</tt> - Append an extension to the generated URL unless the extension
|
53
55
|
# already exists. This only applies for relative URLs.
|
@@ -59,6 +61,20 @@ module ActionView
|
|
59
61
|
# when it is set to true.
|
60
62
|
# * <tt>:nonce</tt> - When set to true, adds an automatic nonce value if
|
61
63
|
# you have Content Security Policy enabled.
|
64
|
+
# * <tt>:async</tt> - When set to +true+, adds the +async+ HTML
|
65
|
+
# attribute, allowing the script to be fetched in parallel to be parsed
|
66
|
+
# and evaluated as soon as possible.
|
67
|
+
# * <tt>:defer</tt> - When set to +true+, adds the +defer+ HTML
|
68
|
+
# attribute, which indicates to the browser that the script is meant to
|
69
|
+
# be executed after the document has been parsed. Additionally, prevents
|
70
|
+
# sending the Preload Links header.
|
71
|
+
#
|
72
|
+
# Any other specified options will be treated as HTML attributes for the
|
73
|
+
# +script+ tag.
|
74
|
+
#
|
75
|
+
# For more information regarding how the <tt>:async</tt> and <tt>:defer</tt>
|
76
|
+
# options affect the <tt><script></tt> tag, please refer to the
|
77
|
+
# {MDN docs}[https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script].
|
62
78
|
#
|
63
79
|
# ==== Examples
|
64
80
|
#
|
@@ -86,10 +102,17 @@ module ActionView
|
|
86
102
|
#
|
87
103
|
# javascript_include_tag "http://www.example.com/xmlhr.js", nonce: true
|
88
104
|
# # => <script src="http://www.example.com/xmlhr.js" nonce="..."></script>
|
105
|
+
#
|
106
|
+
# javascript_include_tag "http://www.example.com/xmlhr.js", async: true
|
107
|
+
# # => <script src="http://www.example.com/xmlhr.js" async="async"></script>
|
108
|
+
#
|
109
|
+
# javascript_include_tag "http://www.example.com/xmlhr.js", defer: true
|
110
|
+
# # => <script src="http://www.example.com/xmlhr.js" defer="defer"></script>
|
89
111
|
def javascript_include_tag(*sources)
|
90
112
|
options = sources.extract_options!.stringify_keys
|
91
113
|
path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
|
92
114
|
preload_links = []
|
115
|
+
use_preload_links_header = options["preload_links_header"].nil? ? preload_links_header : options.delete("preload_links_header")
|
93
116
|
nopush = options["nopush"].nil? ? true : options.delete("nopush")
|
94
117
|
crossorigin = options.delete("crossorigin")
|
95
118
|
crossorigin = "anonymous" if crossorigin == true
|
@@ -98,7 +121,7 @@ module ActionView
|
|
98
121
|
|
99
122
|
sources_tags = sources.uniq.map { |source|
|
100
123
|
href = path_to_javascript(source, path_options)
|
101
|
-
if
|
124
|
+
if use_preload_links_header && !options["defer"] && href.present? && !href.start_with?("data:")
|
102
125
|
preload_link = "<#{href}>; rel=#{rel}; as=script"
|
103
126
|
preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
|
104
127
|
preload_link += "; integrity=#{integrity}" unless integrity.nil?
|
@@ -115,7 +138,7 @@ module ActionView
|
|
115
138
|
content_tag("script", "", tag_options)
|
116
139
|
}.join("\n").html_safe
|
117
140
|
|
118
|
-
if
|
141
|
+
if use_preload_links_header
|
119
142
|
send_preload_links_header(preload_links)
|
120
143
|
end
|
121
144
|
|
@@ -130,8 +153,8 @@ module ActionView
|
|
130
153
|
# set <tt>extname: false</tt> in the options.
|
131
154
|
# You can modify the link attributes by passing a hash as the last argument.
|
132
155
|
#
|
133
|
-
# If the server supports Early Hints,
|
134
|
-
#
|
156
|
+
# If the server supports HTTP Early Hints, \Rails will push a <tt>103 Early
|
157
|
+
# Hints</tt> response that links to the assets.
|
135
158
|
#
|
136
159
|
# ==== Options
|
137
160
|
#
|
@@ -170,6 +193,7 @@ module ActionView
|
|
170
193
|
def stylesheet_link_tag(*sources)
|
171
194
|
options = sources.extract_options!.stringify_keys
|
172
195
|
path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
|
196
|
+
use_preload_links_header = options["preload_links_header"].nil? ? preload_links_header : options.delete("preload_links_header")
|
173
197
|
preload_links = []
|
174
198
|
crossorigin = options.delete("crossorigin")
|
175
199
|
crossorigin = "anonymous" if crossorigin == true
|
@@ -178,7 +202,7 @@ module ActionView
|
|
178
202
|
|
179
203
|
sources_tags = sources.uniq.map { |source|
|
180
204
|
href = path_to_stylesheet(source, path_options)
|
181
|
-
if
|
205
|
+
if use_preload_links_header && href.present? && !href.start_with?("data:")
|
182
206
|
preload_link = "<#{href}>; rel=preload; as=style"
|
183
207
|
preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
|
184
208
|
preload_link += "; integrity=#{integrity}" unless integrity.nil?
|
@@ -198,7 +222,7 @@ module ActionView
|
|
198
222
|
tag(:link, tag_options)
|
199
223
|
}.join("\n").html_safe
|
200
224
|
|
201
|
-
if
|
225
|
+
if use_preload_links_header
|
202
226
|
send_preload_links_header(preload_links)
|
203
227
|
end
|
204
228
|
|
@@ -396,7 +420,7 @@ module ActionView
|
|
396
420
|
check_for_image_tag_errors(options)
|
397
421
|
skip_pipeline = options.delete(:skip_pipeline)
|
398
422
|
|
399
|
-
options[:src] =
|
423
|
+
options[:src] = resolve_asset_source("image", source, skip_pipeline)
|
400
424
|
|
401
425
|
if options[:srcset] && !options[:srcset].is_a?(String)
|
402
426
|
options[:srcset] = options[:srcset].map do |src_path, size|
|
@@ -413,11 +437,72 @@ module ActionView
|
|
413
437
|
tag("img", options)
|
414
438
|
end
|
415
439
|
|
440
|
+
# Returns an HTML picture tag for the +sources+. If +sources+ is a string,
|
441
|
+
# a single picture tag will be returned. If +sources+ is an array, a picture
|
442
|
+
# tag with nested source tags for each source will be returned. The
|
443
|
+
# +sources+ can be full paths, files that exist in your public images
|
444
|
+
# directory, or Active Storage attachments. Since the picture tag requires
|
445
|
+
# an img tag, the last element you provide will be used for the img tag.
|
446
|
+
# For complete control over the picture tag, a block can be passed, which
|
447
|
+
# will populate the contents of the tag accordingly.
|
448
|
+
#
|
449
|
+
# ==== Options
|
450
|
+
#
|
451
|
+
# When the last parameter is a hash you can add HTML attributes using that
|
452
|
+
# parameter. Apart from all the HTML supported options, the following are supported:
|
453
|
+
#
|
454
|
+
# * <tt>:image</tt> - Hash of options that are passed directly to the +image_tag+ helper.
|
455
|
+
#
|
456
|
+
# ==== Examples
|
457
|
+
#
|
458
|
+
# picture_tag("picture.webp")
|
459
|
+
# # => <picture><img src="/images/picture.webp" /></picture>
|
460
|
+
# picture_tag("gold.png", :image => { :size => "20" })
|
461
|
+
# # => <picture><img height="20" src="/images/gold.png" width="20" /></picture>
|
462
|
+
# picture_tag("gold.png", :image => { :size => "45x70" })
|
463
|
+
# # => <picture><img height="70" src="/images/gold.png" width="45" /></picture>
|
464
|
+
# picture_tag("picture.webp", "picture.png")
|
465
|
+
# # => <picture><source srcset="/images/picture.webp" /><source srcset="/images/picture.png" /><img src="/images/picture.png" /></picture>
|
466
|
+
# picture_tag("picture.webp", "picture.png", :image => { alt: "Image" })
|
467
|
+
# # => <picture><source srcset="/images/picture.webp" /><source srcset="/images/picture.png" /><img alt="Image" src="/images/picture.png" /></picture>
|
468
|
+
# picture_tag(["picture.webp", "picture.png"], :image => { alt: "Image" })
|
469
|
+
# # => <picture><source srcset="/images/picture.webp" /><source srcset="/images/picture.png" /><img alt="Image" src="/images/picture.png" /></picture>
|
470
|
+
# picture_tag(:class => "my-class") { tag(:source, :srcset => image_path("picture.webp")) + image_tag("picture.png", :alt => "Image") }
|
471
|
+
# # => <picture class="my-class"><source srcset="/images/picture.webp" /><img alt="Image" src="/images/picture.png" /></picture>
|
472
|
+
# picture_tag { tag(:source, :srcset => image_path("picture-small.webp"), :media => "(min-width: 600px)") + tag(:source, :srcset => image_path("picture-big.webp")) + image_tag("picture.png", :alt => "Image") }
|
473
|
+
# # => <picture><source srcset="/images/picture-small.webp" media="(min-width: 600px)" /><source srcset="/images/picture-big.webp" /><img alt="Image" src="/images/picture.png" /></picture>
|
474
|
+
#
|
475
|
+
# Active Storage blobs (images that are uploaded by the users of your app):
|
476
|
+
#
|
477
|
+
# picture_tag(user.profile_picture)
|
478
|
+
# # => <picture><img src="/rails/active_storage/blobs/.../profile_picture.webp" /></picture>
|
479
|
+
def picture_tag(*sources, &block)
|
480
|
+
sources.flatten!
|
481
|
+
options = sources.extract_options!.symbolize_keys
|
482
|
+
image_options = options.delete(:image) || {}
|
483
|
+
skip_pipeline = options.delete(:skip_pipeline)
|
484
|
+
|
485
|
+
content_tag("picture", options) do
|
486
|
+
if block.present?
|
487
|
+
capture(&block).html_safe
|
488
|
+
elsif sources.size <= 1
|
489
|
+
image_tag(sources.last, image_options)
|
490
|
+
else
|
491
|
+
source_tags = sources.map do |source|
|
492
|
+
tag("source",
|
493
|
+
srcset: resolve_asset_source("image", source, skip_pipeline),
|
494
|
+
type: Template::Types[File.extname(source)[1..]]&.to_s)
|
495
|
+
end
|
496
|
+
safe_join(source_tags << image_tag(sources.last, image_options))
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
416
501
|
# Returns an HTML video tag for the +sources+. If +sources+ is a string,
|
417
502
|
# a single video tag will be returned. If +sources+ is an array, a video
|
418
503
|
# tag with nested source tags for each source will be returned. The
|
419
|
-
# +sources+ can be full paths
|
420
|
-
# directory.
|
504
|
+
# +sources+ can be full paths, files that exist in your public videos
|
505
|
+
# directory, or Active Storage attachments.
|
421
506
|
#
|
422
507
|
# ==== Options
|
423
508
|
#
|
@@ -456,6 +541,11 @@ module ActionView
|
|
456
541
|
# # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
|
457
542
|
# video_tag(["trailer.ogg", "trailer.flv"], size: "160x120")
|
458
543
|
# # => <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
|
544
|
+
#
|
545
|
+
# Active Storage blobs (videos that are uploaded by the users of your app):
|
546
|
+
#
|
547
|
+
# video_tag(user.intro_video)
|
548
|
+
# # => <video src="/rails/active_storage/blobs/.../intro_video.mp4"></video>
|
459
549
|
def video_tag(*sources)
|
460
550
|
options = sources.extract_options!.symbolize_keys
|
461
551
|
public_poster_folder = options.delete(:poster_skip_pipeline)
|
@@ -469,8 +559,8 @@ module ActionView
|
|
469
559
|
# Returns an HTML audio tag for the +sources+. If +sources+ is a string,
|
470
560
|
# a single audio tag will be returned. If +sources+ is an array, an audio
|
471
561
|
# tag with nested source tags for each source will be returned. The
|
472
|
-
# +sources+ can be full paths
|
473
|
-
# directory.
|
562
|
+
# +sources+ can be full paths, files that exist in your public audios
|
563
|
+
# directory, or Active Storage attachments.
|
474
564
|
#
|
475
565
|
# When the last parameter is a hash you can add HTML attributes using that
|
476
566
|
# parameter.
|
@@ -483,6 +573,11 @@ module ActionView
|
|
483
573
|
# # => <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav"></audio>
|
484
574
|
# audio_tag("sound.wav", "sound.mid")
|
485
575
|
# # => <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>
|
576
|
+
#
|
577
|
+
# Active Storage blobs (audios that are uploaded by the users of your app):
|
578
|
+
#
|
579
|
+
# audio_tag(user.name_pronunciation_audio)
|
580
|
+
# # => <audio src="/rails/active_storage/blobs/.../name_pronunciation_audio.mp3"></audio>
|
486
581
|
def audio_tag(*sources)
|
487
582
|
multiple_sources_tag_builder("audio", sources)
|
488
583
|
end
|
@@ -497,29 +592,29 @@ module ActionView
|
|
497
592
|
|
498
593
|
if sources.size > 1
|
499
594
|
content_tag(type, options) do
|
500
|
-
safe_join sources.map { |source| tag("source", src:
|
595
|
+
safe_join sources.map { |source| tag("source", src: resolve_asset_source(type, source, skip_pipeline)) }
|
501
596
|
end
|
502
597
|
else
|
503
|
-
options[:src] =
|
598
|
+
options[:src] = resolve_asset_source(type, sources.first, skip_pipeline)
|
504
599
|
content_tag(type, nil, options)
|
505
600
|
end
|
506
601
|
end
|
507
602
|
|
508
|
-
def
|
603
|
+
def resolve_asset_source(asset_type, source, skip_pipeline)
|
509
604
|
if source.is_a?(Symbol) || source.is_a?(String)
|
510
|
-
|
605
|
+
path_to_asset(source, type: asset_type.to_sym, skip_pipeline: skip_pipeline)
|
511
606
|
else
|
512
607
|
polymorphic_url(source)
|
513
608
|
end
|
514
609
|
rescue NoMethodError => e
|
515
|
-
raise ArgumentError, "Can't resolve
|
610
|
+
raise ArgumentError, "Can't resolve #{asset_type} into URL: #{e}"
|
516
611
|
end
|
517
612
|
|
518
613
|
def extract_dimensions(size)
|
519
614
|
size = size.to_s
|
520
|
-
if /\A
|
615
|
+
if /\A\d+(?:\.\d+)?x\d+(?:\.\d+)?\z/.match?(size)
|
521
616
|
size.split("x")
|
522
|
-
elsif /\A
|
617
|
+
elsif /\A\d+(?:\.\d+)?\z/.match?(size)
|
523
618
|
[size, size]
|
524
619
|
end
|
525
620
|
end
|
@@ -540,40 +635,29 @@ module ActionView
|
|
540
635
|
end
|
541
636
|
end
|
542
637
|
|
543
|
-
|
638
|
+
# Some HTTP client and proxies have a 4kiB header limit, but more importantly
|
639
|
+
# including preload links has diminishing returns so it's best to not go overboard
|
640
|
+
MAX_HEADER_SIZE = 1_000 # :nodoc:
|
641
|
+
|
544
642
|
def send_preload_links_header(preload_links, max_header_size: MAX_HEADER_SIZE)
|
545
643
|
return if preload_links.empty?
|
546
|
-
|
644
|
+
response_present = respond_to?(:response) && response
|
645
|
+
return if response_present && response.sending?
|
547
646
|
|
548
647
|
if respond_to?(:request) && request
|
549
648
|
request.send_early_hints("Link" => preload_links.join("\n"))
|
550
649
|
end
|
551
650
|
|
552
|
-
if
|
553
|
-
header = response.headers["Link"]
|
554
|
-
header = header ? header.dup : +""
|
555
|
-
|
556
|
-
# rindex count characters not bytes, but we assume non-ascii characters
|
557
|
-
# are rare in urls, and we have a 192 bytes margin.
|
558
|
-
last_line_offset = header.rindex("\n")
|
559
|
-
last_line_size = if last_line_offset
|
560
|
-
header.bytesize - last_line_offset
|
561
|
-
else
|
562
|
-
header.bytesize
|
563
|
-
end
|
564
|
-
|
651
|
+
if response_present
|
652
|
+
header = +response.headers["Link"].to_s
|
565
653
|
preload_links.each do |link|
|
566
|
-
if
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
end
|
654
|
+
break if header.bytesize + link.bytesize > max_header_size
|
655
|
+
|
656
|
+
if header.empty?
|
657
|
+
header << link
|
571
658
|
else
|
572
|
-
header << "
|
573
|
-
last_line_size = 0
|
659
|
+
header << "," << link
|
574
660
|
end
|
575
|
-
header << link
|
576
|
-
last_line_size += link.bytesize
|
577
661
|
end
|
578
662
|
|
579
663
|
response.headers["Link"] = header
|