actionview 6.0.0.beta1 → 6.1.4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionview might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +273 -119
- data/MIT-LICENSE +1 -1
- data/README.rdoc +5 -3
- data/lib/action_view/base.rb +81 -15
- data/lib/action_view/cache_expiry.rb +52 -0
- data/lib/action_view/context.rb +0 -5
- data/lib/action_view/dependency_tracker.rb +10 -4
- data/lib/action_view/digestor.rb +11 -19
- data/lib/action_view/flows.rb +0 -1
- data/lib/action_view/gem_version.rb +3 -3
- data/lib/action_view/helpers/active_model_helper.rb +0 -1
- data/lib/action_view/helpers/asset_tag_helper.rb +62 -22
- data/lib/action_view/helpers/asset_url_helper.rb +6 -4
- data/lib/action_view/helpers/atom_feed_helper.rb +2 -1
- data/lib/action_view/helpers/cache_helper.rb +16 -23
- data/lib/action_view/helpers/csp_helper.rb +4 -2
- data/lib/action_view/helpers/date_helper.rb +5 -6
- data/lib/action_view/helpers/form_helper.rb +70 -34
- data/lib/action_view/helpers/form_options_helper.rb +10 -18
- data/lib/action_view/helpers/form_tag_helper.rb +12 -9
- data/lib/action_view/helpers/javascript_helper.rb +7 -5
- data/lib/action_view/helpers/number_helper.rb +9 -8
- data/lib/action_view/helpers/output_safety_helper.rb +1 -1
- data/lib/action_view/helpers/rendering_helper.rb +17 -7
- data/lib/action_view/helpers/sanitize_helper.rb +10 -16
- data/lib/action_view/helpers/tag_helper.rb +94 -19
- data/lib/action_view/helpers/tags/base.rb +10 -7
- data/lib/action_view/helpers/tags/check_box.rb +0 -1
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -1
- data/lib/action_view/helpers/tags/collection_helpers.rb +0 -1
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -1
- data/lib/action_view/helpers/tags/color_field.rb +0 -1
- data/lib/action_view/helpers/tags/date_field.rb +1 -2
- data/lib/action_view/helpers/tags/date_select.rb +2 -3
- data/lib/action_view/helpers/tags/datetime_field.rb +0 -1
- data/lib/action_view/helpers/tags/datetime_local_field.rb +1 -2
- data/lib/action_view/helpers/tags/label.rb +4 -1
- data/lib/action_view/helpers/tags/month_field.rb +1 -2
- data/lib/action_view/helpers/tags/radio_button.rb +0 -1
- data/lib/action_view/helpers/tags/select.rb +1 -2
- data/lib/action_view/helpers/tags/text_field.rb +0 -1
- data/lib/action_view/helpers/tags/time_field.rb +1 -2
- data/lib/action_view/helpers/tags/week_field.rb +1 -2
- data/lib/action_view/helpers/text_helper.rb +2 -3
- data/lib/action_view/helpers/translation_helper.rb +98 -51
- data/lib/action_view/helpers/url_helper.rb +124 -16
- data/lib/action_view/layouts.rb +8 -10
- data/lib/action_view/log_subscriber.rb +26 -11
- data/lib/action_view/lookup_context.rb +59 -31
- data/lib/action_view/path_set.rb +3 -12
- data/lib/action_view/railtie.rb +39 -41
- data/lib/action_view/record_identifier.rb +0 -1
- data/lib/action_view/renderer/abstract_renderer.rb +142 -11
- data/lib/action_view/renderer/collection_renderer.rb +196 -0
- data/lib/action_view/renderer/object_renderer.rb +34 -0
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +35 -29
- data/lib/action_view/renderer/partial_renderer.rb +21 -273
- data/lib/action_view/renderer/renderer.rb +59 -4
- data/lib/action_view/renderer/streaming_template_renderer.rb +9 -7
- data/lib/action_view/renderer/template_renderer.rb +35 -27
- data/lib/action_view/rendering.rb +49 -29
- data/lib/action_view/routing_url_for.rb +1 -1
- data/lib/action_view/template/error.rb +30 -15
- data/lib/action_view/template/handlers/builder.rb +2 -2
- data/lib/action_view/template/handlers/erb/erubi.rb +15 -9
- data/lib/action_view/template/handlers/erb.rb +14 -19
- data/lib/action_view/template/handlers/html.rb +1 -1
- data/lib/action_view/template/handlers/raw.rb +2 -2
- data/lib/action_view/template/handlers.rb +1 -1
- data/lib/action_view/template/html.rb +5 -6
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +25 -0
- data/lib/action_view/template/renderable.rb +24 -0
- data/lib/action_view/template/resolver.rb +141 -140
- data/lib/action_view/template/sources/file.rb +17 -0
- data/lib/action_view/template/sources.rb +13 -0
- data/lib/action_view/template/text.rb +2 -3
- data/lib/action_view/template.rb +49 -75
- data/lib/action_view/test_case.rb +20 -28
- data/lib/action_view/testing/resolvers.rb +18 -27
- data/lib/action_view/unbound_template.rb +31 -0
- data/lib/action_view/view_paths.rb +59 -38
- data/lib/action_view.rb +7 -2
- data/lib/assets/compiled/rails-ujs.js +25 -16
- metadata +30 -18
@@ -13,19 +13,27 @@ module ActionView
|
|
13
13
|
include CaptureHelper
|
14
14
|
include OutputSafetyHelper
|
15
15
|
|
16
|
-
BOOLEAN_ATTRIBUTES = %w(allowfullscreen async autofocus
|
17
|
-
compact controls declare default
|
18
|
-
defaultmuted defaultselected defer
|
19
|
-
enabled formnovalidate hidden indeterminate
|
20
|
-
ismap itemscope loop multiple muted nohref
|
21
|
-
noresize noshade novalidate nowrap open
|
22
|
-
pauseonexit readonly required reversed
|
23
|
-
seamless selected sortable truespeed
|
24
|
-
visible).to_set
|
16
|
+
BOOLEAN_ATTRIBUTES = %w(allowfullscreen allowpaymentrequest async autofocus
|
17
|
+
autoplay checked compact controls declare default
|
18
|
+
defaultchecked defaultmuted defaultselected defer
|
19
|
+
disabled enabled formnovalidate hidden indeterminate
|
20
|
+
inert ismap itemscope loop multiple muted nohref
|
21
|
+
nomodule noresize noshade novalidate nowrap open
|
22
|
+
pauseonexit playsinline readonly required reversed
|
23
|
+
scoped seamless selected sortable truespeed
|
24
|
+
typemustmatch visible).to_set
|
25
25
|
|
26
26
|
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
|
27
|
+
BOOLEAN_ATTRIBUTES.freeze
|
27
28
|
|
28
|
-
|
29
|
+
ARIA_PREFIXES = ["aria", :aria].to_set.freeze
|
30
|
+
DATA_PREFIXES = ["data", :data].to_set.freeze
|
31
|
+
|
32
|
+
TAG_TYPES = {}
|
33
|
+
TAG_TYPES.merge! BOOLEAN_ATTRIBUTES.index_with(:boolean)
|
34
|
+
TAG_TYPES.merge! DATA_PREFIXES.index_with(:data)
|
35
|
+
TAG_TYPES.merge! ARIA_PREFIXES.index_with(:aria)
|
36
|
+
TAG_TYPES.freeze
|
29
37
|
|
30
38
|
PRE_CONTENT_STRINGS = Hash.new { "" }
|
31
39
|
PRE_CONTENT_STRINGS[:textarea] = "\n"
|
@@ -41,6 +49,10 @@ module ActionView
|
|
41
49
|
@view_context = view_context
|
42
50
|
end
|
43
51
|
|
52
|
+
def p(*arguments, **options, &block)
|
53
|
+
tag_string(:p, *arguments, **options, &block)
|
54
|
+
end
|
55
|
+
|
44
56
|
def tag_string(name, content = nil, escape_attributes: true, **options, &block)
|
45
57
|
content = @view_context.capture(self, &block) if block_given?
|
46
58
|
if VOID_ELEMENTS.include?(name) && content.nil?
|
@@ -61,13 +73,31 @@ module ActionView
|
|
61
73
|
output = +""
|
62
74
|
sep = " "
|
63
75
|
options.each_pair do |key, value|
|
64
|
-
|
76
|
+
type = TAG_TYPES[key]
|
77
|
+
if type == :data && value.is_a?(Hash)
|
78
|
+
value.each_pair do |k, v|
|
79
|
+
next if v.nil?
|
80
|
+
output << sep
|
81
|
+
output << prefix_tag_option(key, k, v, escape)
|
82
|
+
end
|
83
|
+
elsif type == :aria && value.is_a?(Hash)
|
65
84
|
value.each_pair do |k, v|
|
66
85
|
next if v.nil?
|
86
|
+
|
87
|
+
case v
|
88
|
+
when Array, Hash
|
89
|
+
tokens = TagHelper.build_tag_values(v)
|
90
|
+
next if tokens.none?
|
91
|
+
|
92
|
+
v = safe_join(tokens, " ")
|
93
|
+
else
|
94
|
+
v = v.to_s
|
95
|
+
end
|
96
|
+
|
67
97
|
output << sep
|
68
98
|
output << prefix_tag_option(key, k, v, escape)
|
69
99
|
end
|
70
|
-
elsif
|
100
|
+
elsif type == :boolean
|
71
101
|
if value
|
72
102
|
output << sep
|
73
103
|
output << boolean_tag_option(key)
|
@@ -85,12 +115,14 @@ module ActionView
|
|
85
115
|
end
|
86
116
|
|
87
117
|
def tag_option(key, value, escape)
|
88
|
-
|
118
|
+
case value
|
119
|
+
when Array, Hash
|
120
|
+
value = TagHelper.build_tag_values(value) if key.to_s == "class"
|
89
121
|
value = escape ? safe_join(value, " ") : value.join(" ")
|
90
122
|
else
|
91
|
-
value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
|
123
|
+
value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
|
92
124
|
end
|
93
|
-
value.gsub
|
125
|
+
value = value.gsub('"', """) if value.include?('"')
|
94
126
|
%(#{key}="#{value}")
|
95
127
|
end
|
96
128
|
|
@@ -107,8 +139,8 @@ module ActionView
|
|
107
139
|
true
|
108
140
|
end
|
109
141
|
|
110
|
-
def method_missing(called, *args, &block)
|
111
|
-
tag_string(called, *args, &block)
|
142
|
+
def method_missing(called, *args, **options, &block)
|
143
|
+
tag_string(called, *args, **options, &block)
|
112
144
|
end
|
113
145
|
end
|
114
146
|
|
@@ -152,8 +184,8 @@ module ActionView
|
|
152
184
|
# tag.input type: 'text', disabled: true
|
153
185
|
# # => <input type="text" disabled="disabled">
|
154
186
|
#
|
155
|
-
# HTML5 <tt>data-*</tt> attributes can be set with a
|
156
|
-
# pointing to a hash of sub-attributes.
|
187
|
+
# HTML5 <tt>data-*</tt> and <tt>aria-*</tt> attributes can be set with a
|
188
|
+
# single +data+ or +aria+ key pointing to a hash of sub-attributes.
|
157
189
|
#
|
158
190
|
# To play nicely with JavaScript conventions, sub-attributes are dasherized.
|
159
191
|
#
|
@@ -233,6 +265,9 @@ module ActionView
|
|
233
265
|
#
|
234
266
|
# tag("div", data: { name: 'Stephen', city_state: %w(Chicago IL) })
|
235
267
|
# # => <div data-name="Stephen" data-city-state="["Chicago","IL"]" />
|
268
|
+
#
|
269
|
+
# tag("div", class: { highlight: current_user.admin? })
|
270
|
+
# # => <div class="highlight" />
|
236
271
|
def tag(name = nil, options = nil, open = false, escape = true)
|
237
272
|
if name.nil?
|
238
273
|
tag_builder
|
@@ -260,6 +295,8 @@ module ActionView
|
|
260
295
|
# # => <div class="strong"><p>Hello world!</p></div>
|
261
296
|
# content_tag(:div, "Hello world!", class: ["strong", "highlight"])
|
262
297
|
# # => <div class="strong highlight">Hello world!</div>
|
298
|
+
# content_tag(:div, "Hello world!", class: ["strong", { highlight: current_user.admin? }])
|
299
|
+
# # => <div class="strong highlight">Hello world!</div>
|
263
300
|
# content_tag("select", options, multiple: true)
|
264
301
|
# # => <select multiple="multiple">...options...</select>
|
265
302
|
#
|
@@ -276,6 +313,24 @@ module ActionView
|
|
276
313
|
end
|
277
314
|
end
|
278
315
|
|
316
|
+
# Returns a string of tokens built from +args+.
|
317
|
+
#
|
318
|
+
# ==== Examples
|
319
|
+
# token_list("foo", "bar")
|
320
|
+
# # => "foo bar"
|
321
|
+
# token_list("foo", "foo bar")
|
322
|
+
# # => "foo bar"
|
323
|
+
# token_list({ foo: true, bar: false })
|
324
|
+
# # => "foo"
|
325
|
+
# token_list(nil, false, 123, "", "foo", { bar: true })
|
326
|
+
# # => "123 foo bar"
|
327
|
+
def token_list(*args)
|
328
|
+
tokens = build_tag_values(*args).flat_map { |value| value.to_s.split(/\s+/) }.uniq
|
329
|
+
|
330
|
+
safe_join(tokens, " ")
|
331
|
+
end
|
332
|
+
alias_method :class_names, :token_list
|
333
|
+
|
279
334
|
# Returns a CDATA section with the given +content+. CDATA sections
|
280
335
|
# are used to escape blocks of text containing characters which would
|
281
336
|
# otherwise be recognized as markup. CDATA sections begin with the string
|
@@ -306,6 +361,26 @@ module ActionView
|
|
306
361
|
end
|
307
362
|
|
308
363
|
private
|
364
|
+
def build_tag_values(*args)
|
365
|
+
tag_values = []
|
366
|
+
|
367
|
+
args.each do |tag_value|
|
368
|
+
case tag_value
|
369
|
+
when Hash
|
370
|
+
tag_value.each do |key, val|
|
371
|
+
tag_values << key.to_s if val && key.present?
|
372
|
+
end
|
373
|
+
when Array
|
374
|
+
tag_values.concat build_tag_values(*tag_value)
|
375
|
+
else
|
376
|
+
tag_values << tag_value.to_s if tag_value.present?
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
tag_values
|
381
|
+
end
|
382
|
+
module_function :build_tag_values
|
383
|
+
|
309
384
|
def tag_builder
|
310
385
|
@tag_builder ||= TagBuilder.new(self)
|
311
386
|
end
|
@@ -34,7 +34,6 @@ module ActionView
|
|
34
34
|
end
|
35
35
|
|
36
36
|
private
|
37
|
-
|
38
37
|
def value
|
39
38
|
if @allow_method_names_outside_object
|
40
39
|
object.public_send @method_name if object && object.respond_to?(@method_name)
|
@@ -106,7 +105,7 @@ module ActionView
|
|
106
105
|
end
|
107
106
|
|
108
107
|
def tag_name(multiple = false, index = nil)
|
109
|
-
# a little duplication to construct
|
108
|
+
# a little duplication to construct fewer strings
|
110
109
|
case
|
111
110
|
when @object_name.empty?
|
112
111
|
"#{sanitized_method_name}#{multiple ? "[]" : ""}"
|
@@ -118,7 +117,7 @@ module ActionView
|
|
118
117
|
end
|
119
118
|
|
120
119
|
def tag_id(index = nil)
|
121
|
-
# a little duplication to construct
|
120
|
+
# a little duplication to construct fewer strings
|
122
121
|
case
|
123
122
|
when @object_name.empty?
|
124
123
|
sanitized_method_name.dup
|
@@ -130,15 +129,15 @@ module ActionView
|
|
130
129
|
end
|
131
130
|
|
132
131
|
def sanitized_object_name
|
133
|
-
@sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").
|
132
|
+
@sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").delete_suffix("_")
|
134
133
|
end
|
135
134
|
|
136
135
|
def sanitized_method_name
|
137
|
-
@sanitized_method_name ||= @method_name.
|
136
|
+
@sanitized_method_name ||= @method_name.delete_suffix("?")
|
138
137
|
end
|
139
138
|
|
140
139
|
def sanitized_value(value)
|
141
|
-
value.to_s.gsub(
|
140
|
+
value.to_s.gsub(/[\s\.]/, "_").gsub(/[^-[[:word:]]]/, "").downcase
|
142
141
|
end
|
143
142
|
|
144
143
|
def select_content_tag(option_tags, options, html_options)
|
@@ -167,8 +166,11 @@ module ActionView
|
|
167
166
|
|
168
167
|
def add_options(option_tags, options, value = nil)
|
169
168
|
if options[:include_blank]
|
170
|
-
|
169
|
+
content = (options[:include_blank] if options[:include_blank].is_a?(String))
|
170
|
+
label = (" " unless content)
|
171
|
+
option_tags = tag_builder.content_tag_string("option", content, value: "", label: label) + "\n" + option_tags
|
171
172
|
end
|
173
|
+
|
172
174
|
if value.blank? && options[:prompt]
|
173
175
|
tag_options = { value: "" }.tap do |prompt_opts|
|
174
176
|
prompt_opts[:disabled] = true if options[:disabled] == ""
|
@@ -176,6 +178,7 @@ module ActionView
|
|
176
178
|
end
|
177
179
|
option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), tag_options) + "\n" + option_tags
|
178
180
|
end
|
181
|
+
|
179
182
|
option_tags
|
180
183
|
end
|
181
184
|
|
@@ -13,7 +13,7 @@ module ActionView
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def render
|
16
|
-
error_wrapping(datetime_selector(@options, @html_options).
|
16
|
+
error_wrapping(datetime_selector(@options, @html_options).public_send("select_#{select_type}").html_safe)
|
17
17
|
end
|
18
18
|
|
19
19
|
class << self
|
@@ -23,7 +23,6 @@ module ActionView
|
|
23
23
|
end
|
24
24
|
|
25
25
|
private
|
26
|
-
|
27
26
|
def select_type
|
28
27
|
self.class.select_type
|
29
28
|
end
|
@@ -59,7 +58,7 @@ module ActionView
|
|
59
58
|
time = Time.current
|
60
59
|
|
61
60
|
[:year, :month, :day, :hour, :min, :sec].each do |key|
|
62
|
-
default[key] ||= time.
|
61
|
+
default[key] ||= time.public_send(key)
|
63
62
|
end
|
64
63
|
|
65
64
|
Time.utc(
|
@@ -25,6 +25,10 @@ module ActionView
|
|
25
25
|
|
26
26
|
content
|
27
27
|
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
translation
|
31
|
+
end
|
28
32
|
end
|
29
33
|
|
30
34
|
def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil)
|
@@ -71,7 +75,6 @@ module ActionView
|
|
71
75
|
end
|
72
76
|
|
73
77
|
private
|
74
|
-
|
75
78
|
def render_component(builder)
|
76
79
|
builder.translation
|
77
80
|
end
|
@@ -15,7 +15,7 @@ module ActionView
|
|
15
15
|
|
16
16
|
def render
|
17
17
|
option_tags_options = {
|
18
|
-
selected: @options.fetch(:selected) { value },
|
18
|
+
selected: @options.fetch(:selected) { value.nil? ? "" : value },
|
19
19
|
disabled: @options[:disabled]
|
20
20
|
}
|
21
21
|
|
@@ -29,7 +29,6 @@ module ActionView
|
|
29
29
|
end
|
30
30
|
|
31
31
|
private
|
32
|
-
|
33
32
|
# Grouped choices look like this:
|
34
33
|
#
|
35
34
|
# [nil, []]
|
@@ -105,7 +105,7 @@ module ActionView
|
|
105
105
|
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into
|
106
106
|
# a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt>
|
107
107
|
# as a single-quoted string with <tt>\1</tt> where the phrase is to be inserted (defaults to
|
108
|
-
#
|
108
|
+
# <tt><mark>\1</mark></tt>) or passing a block that receives each matched term. By default +text+
|
109
109
|
# is sanitized to prevent possible XSS attacks. If the input is trustworthy, passing false
|
110
110
|
# for <tt>:sanitize</tt> will turn sanitizing off.
|
111
111
|
#
|
@@ -228,7 +228,7 @@ module ActionView
|
|
228
228
|
# pluralize(2, 'Person', locale: :de)
|
229
229
|
# # => 2 Personen
|
230
230
|
def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale)
|
231
|
-
word = if count == 1 || count.to_s
|
231
|
+
word = if count == 1 || count.to_s.match?(/^1(\.0+)?$/)
|
232
232
|
singular
|
233
233
|
else
|
234
234
|
plural || singular.pluralize(locale)
|
@@ -426,7 +426,6 @@ module ActionView
|
|
426
426
|
end
|
427
427
|
|
428
428
|
private
|
429
|
-
|
430
429
|
def next_index
|
431
430
|
step_index(1)
|
432
431
|
end
|