actionview 7.0.8.6 → 7.1.0.beta1
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/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
|