actionview 8.0.5.2 → 8.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 +28 -171
- data/lib/action_view/base.rb +4 -2
- data/lib/action_view/buffers.rb +1 -1
- data/lib/action_view/dependency_tracker/erb_tracker.rb +1 -1
- data/lib/action_view/dependency_tracker.rb +6 -1
- data/lib/action_view/gem_version.rb +3 -3
- data/lib/action_view/helpers/asset_tag_helper.rb +20 -4
- data/lib/action_view/helpers/capture_helper.rb +2 -2
- data/lib/action_view/helpers/controller_helper.rb +6 -2
- data/lib/action_view/helpers/date_helper.rb +17 -0
- data/lib/action_view/helpers/form_helper.rb +2 -3
- data/lib/action_view/helpers/form_options_helper.rb +10 -12
- data/lib/action_view/helpers/form_tag_helper.rb +17 -10
- data/lib/action_view/helpers/javascript_helper.rb +5 -1
- data/lib/action_view/helpers/number_helper.rb +14 -0
- data/lib/action_view/helpers/sanitize_helper.rb +1 -6
- data/lib/action_view/helpers/tag_helper.rb +37 -52
- data/lib/action_view/helpers/tags/base.rb +9 -11
- data/lib/action_view/helpers/tags/check_box.rb +2 -2
- data/lib/action_view/helpers/tags/datetime_field.rb +1 -1
- data/lib/action_view/helpers/tags/file_field.rb +1 -4
- data/lib/action_view/helpers/tags/hidden_field.rb +1 -1
- data/lib/action_view/helpers/tags/label.rb +10 -3
- data/lib/action_view/helpers/tags/radio_button.rb +1 -1
- data/lib/action_view/helpers/tags/select.rb +6 -1
- data/lib/action_view/helpers/tags/select_renderer.rb +3 -3
- data/lib/action_view/helpers/tags/text_area.rb +1 -1
- data/lib/action_view/helpers/tags/text_field.rb +1 -1
- data/lib/action_view/helpers/text_helper.rb +7 -23
- data/lib/action_view/helpers/translation_helper.rb +6 -1
- data/lib/action_view/helpers/url_helper.rb +37 -9
- data/lib/action_view/locale/en.yml +3 -0
- data/lib/action_view/railtie.rb +11 -0
- data/lib/action_view/record_identifier.rb +21 -0
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +1 -5
- data/lib/action_view/renderer/partial_renderer.rb +16 -0
- data/lib/action_view/template/error.rb +7 -3
- data/lib/action_view/template/handlers/erb.rb +36 -11
- data/lib/action_view/template.rb +2 -8
- data/lib/action_view/test_case.rb +50 -52
- data/lib/action_view.rb +3 -0
- metadata +11 -17
|
@@ -26,6 +26,8 @@ module ActionView
|
|
|
26
26
|
|
|
27
27
|
# Delegates to ActiveSupport::NumberHelper#number_to_phone.
|
|
28
28
|
#
|
|
29
|
+
# number_to_phone("1234567890") # => "123-456-7890"
|
|
30
|
+
#
|
|
29
31
|
# Additionally, supports a +:raise+ option that will cause
|
|
30
32
|
# InvalidNumberError to be raised if +number+ is not a valid number:
|
|
31
33
|
#
|
|
@@ -42,6 +44,8 @@ module ActionView
|
|
|
42
44
|
|
|
43
45
|
# Delegates to ActiveSupport::NumberHelper#number_to_currency.
|
|
44
46
|
#
|
|
47
|
+
# number_to_currency("1234") # => "$1234.00"
|
|
48
|
+
#
|
|
45
49
|
# Additionally, supports a +:raise+ option that will cause
|
|
46
50
|
# InvalidNumberError to be raised if +number+ is not a valid number:
|
|
47
51
|
#
|
|
@@ -54,6 +58,8 @@ module ActionView
|
|
|
54
58
|
|
|
55
59
|
# Delegates to ActiveSupport::NumberHelper#number_to_percentage.
|
|
56
60
|
#
|
|
61
|
+
# number_to_percentage("99") # => "99.000%"
|
|
62
|
+
#
|
|
57
63
|
# Additionally, supports a +:raise+ option that will cause
|
|
58
64
|
# InvalidNumberError to be raised if +number+ is not a valid number:
|
|
59
65
|
#
|
|
@@ -66,6 +72,8 @@ module ActionView
|
|
|
66
72
|
|
|
67
73
|
# Delegates to ActiveSupport::NumberHelper#number_to_delimited.
|
|
68
74
|
#
|
|
75
|
+
# number_with_delimiter("1234") # => "1,234"
|
|
76
|
+
#
|
|
69
77
|
# Additionally, supports a +:raise+ option that will cause
|
|
70
78
|
# InvalidNumberError to be raised if +number+ is not a valid number:
|
|
71
79
|
#
|
|
@@ -78,6 +86,8 @@ module ActionView
|
|
|
78
86
|
|
|
79
87
|
# Delegates to ActiveSupport::NumberHelper#number_to_rounded.
|
|
80
88
|
#
|
|
89
|
+
# number_with_precision("1234") # => "1234.000"
|
|
90
|
+
#
|
|
81
91
|
# Additionally, supports a +:raise+ option that will cause
|
|
82
92
|
# InvalidNumberError to be raised if +number+ is not a valid number:
|
|
83
93
|
#
|
|
@@ -90,6 +100,8 @@ module ActionView
|
|
|
90
100
|
|
|
91
101
|
# Delegates to ActiveSupport::NumberHelper#number_to_human_size.
|
|
92
102
|
#
|
|
103
|
+
# number_to_human_size("1234") # => "1.21 KB"
|
|
104
|
+
#
|
|
93
105
|
# Additionally, supports a +:raise+ option that will cause
|
|
94
106
|
# InvalidNumberError to be raised if +number+ is not a valid number:
|
|
95
107
|
#
|
|
@@ -102,6 +114,8 @@ module ActionView
|
|
|
102
114
|
|
|
103
115
|
# Delegates to ActiveSupport::NumberHelper#number_to_human.
|
|
104
116
|
#
|
|
117
|
+
# number_to_human("1234") # => "1.23 Thousand"
|
|
118
|
+
#
|
|
105
119
|
# Additionally, supports a +:raise+ option that will cause
|
|
106
120
|
# InvalidNumberError to be raised if +number+ is not a valid number:
|
|
107
121
|
#
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "active_support/core_ext/string/output_safety"
|
|
4
3
|
require "rails-html-sanitizer"
|
|
5
4
|
|
|
6
5
|
module ActionView
|
|
@@ -34,9 +33,6 @@ module ActionView
|
|
|
34
33
|
# Please note that sanitizing user-provided text does not guarantee that the
|
|
35
34
|
# resulting markup is valid or even well-formed.
|
|
36
35
|
#
|
|
37
|
-
# The output is markup, which is safe only as element content. Do not use it in an attribute
|
|
38
|
-
# value; use +strip_tags+ or a tag helper such as <tt>tag.a(title: ...)</tt> instead.
|
|
39
|
-
#
|
|
40
36
|
# ==== Options
|
|
41
37
|
#
|
|
42
38
|
# [+:tags+]
|
|
@@ -141,8 +137,7 @@ module ActionView
|
|
|
141
137
|
# strip_tags("> A quote from Smith & Wesson")
|
|
142
138
|
# # => > A quote from Smith & Wesson
|
|
143
139
|
def strip_tags(html)
|
|
144
|
-
|
|
145
|
-
ERB::Util.html_escape_once(stripped) if stripped
|
|
140
|
+
self.class.full_sanitizer.sanitize(html)&.html_safe
|
|
146
141
|
end
|
|
147
142
|
|
|
148
143
|
# Strips all link tags from +html+ leaving just the link text.
|
|
@@ -44,9 +44,6 @@ module ActionView
|
|
|
44
44
|
PRE_CONTENT_STRINGS["textarea"] = "\n"
|
|
45
45
|
|
|
46
46
|
class TagBuilder # :nodoc:
|
|
47
|
-
include CaptureHelper
|
|
48
|
-
include OutputSafetyHelper
|
|
49
|
-
|
|
50
47
|
def self.define_element(name, code_generator:, method_name: name)
|
|
51
48
|
return if method_defined?(name)
|
|
52
49
|
|
|
@@ -226,17 +223,7 @@ module ActionView
|
|
|
226
223
|
tag_options(attributes.to_h).to_s.strip.html_safe
|
|
227
224
|
end
|
|
228
225
|
|
|
229
|
-
def
|
|
230
|
-
content = @view_context.capture(self, &block) if block
|
|
231
|
-
|
|
232
|
-
content_tag_string(name, content, options, escape)
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
def self_closing_tag_string(name, options, escape = true, tag_suffix = " />")
|
|
236
|
-
"<#{name}#{tag_options(options, escape)}#{tag_suffix}".html_safe
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
def content_tag_string(name, content, options, escape = true)
|
|
226
|
+
def content_tag_string(name, content, options, escape = true) # :nodoc:
|
|
240
227
|
tag_options = tag_options(options, escape) if options
|
|
241
228
|
|
|
242
229
|
if escape && content.present?
|
|
@@ -245,82 +232,80 @@ module ActionView
|
|
|
245
232
|
"<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
|
|
246
233
|
end
|
|
247
234
|
|
|
248
|
-
def tag_options(options, escape = true)
|
|
235
|
+
def tag_options(options, escape = true) # :nodoc:
|
|
249
236
|
return if options.blank?
|
|
250
237
|
output = +""
|
|
251
238
|
sep = " "
|
|
252
239
|
options.each_pair do |key, value|
|
|
253
|
-
next if key.blank?
|
|
254
|
-
|
|
255
|
-
name = Symbol === key ? key.name : key.to_s
|
|
256
|
-
next if name.blank?
|
|
257
|
-
|
|
258
240
|
type = TAG_TYPES[key]
|
|
259
241
|
if type == :data && value.is_a?(Hash)
|
|
260
242
|
value.each_pair do |k, v|
|
|
261
|
-
next if
|
|
262
|
-
|
|
263
|
-
nested_name = Symbol === k ? k.name : k.to_s
|
|
264
|
-
next if nested_name.blank?
|
|
265
|
-
|
|
243
|
+
next if v.nil?
|
|
266
244
|
output << sep
|
|
267
|
-
output << prefix_tag_option(
|
|
245
|
+
output << prefix_tag_option(key, k, v, escape)
|
|
268
246
|
end
|
|
269
247
|
elsif type == :aria && value.is_a?(Hash)
|
|
270
248
|
value.each_pair do |k, v|
|
|
271
|
-
next if
|
|
272
|
-
|
|
273
|
-
nested_name = Symbol === k ? k.name : k.to_s
|
|
274
|
-
next if nested_name.blank?
|
|
249
|
+
next if v.nil?
|
|
275
250
|
|
|
276
251
|
case v
|
|
277
252
|
when Array, Hash
|
|
278
253
|
tokens = TagHelper.build_tag_values(v)
|
|
279
254
|
next if tokens.none?
|
|
280
255
|
|
|
281
|
-
v = safe_join(tokens, " ")
|
|
256
|
+
v = @view_context.safe_join(tokens, " ")
|
|
282
257
|
else
|
|
283
258
|
v = v.to_s
|
|
284
259
|
end
|
|
285
260
|
|
|
286
261
|
output << sep
|
|
287
|
-
output << prefix_tag_option(
|
|
262
|
+
output << prefix_tag_option(key, k, v, escape)
|
|
288
263
|
end
|
|
289
264
|
elsif type == :boolean
|
|
290
265
|
if value
|
|
291
266
|
output << sep
|
|
292
|
-
output << boolean_tag_option(
|
|
267
|
+
output << boolean_tag_option(key)
|
|
293
268
|
end
|
|
294
269
|
elsif !value.nil?
|
|
295
270
|
output << sep
|
|
296
|
-
output << tag_option(
|
|
271
|
+
output << tag_option(key, value, escape)
|
|
297
272
|
end
|
|
298
273
|
end
|
|
299
274
|
output unless output.empty?
|
|
300
275
|
end
|
|
301
276
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
277
|
+
private
|
|
278
|
+
def tag_string(name, content = nil, options, escape: true, &block)
|
|
279
|
+
content = @view_context.capture(self, &block) if block
|
|
305
280
|
|
|
306
|
-
|
|
307
|
-
key = ERB::Util.xml_name_escape(key) if escape
|
|
308
|
-
|
|
309
|
-
case value
|
|
310
|
-
when Array, Hash
|
|
311
|
-
value = TagHelper.build_tag_values(value) if key.to_s == "class"
|
|
312
|
-
value = escape ? safe_join(value, " ") : value.join(" ")
|
|
313
|
-
when Regexp
|
|
314
|
-
value = escape ? ERB::Util.unwrapped_html_escape(value.source) : value.source
|
|
315
|
-
else
|
|
316
|
-
value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
|
|
281
|
+
content_tag_string(name, content, options, escape)
|
|
317
282
|
end
|
|
318
|
-
value = value.gsub('"', """) if value.include?('"')
|
|
319
283
|
|
|
320
|
-
|
|
321
|
-
|
|
284
|
+
def self_closing_tag_string(name, options, escape = true, tag_suffix = " />")
|
|
285
|
+
"<#{name}#{tag_options(options, escape)}#{tag_suffix}".html_safe
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def boolean_tag_option(key)
|
|
289
|
+
%(#{key}="#{key}")
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def tag_option(key, value, escape)
|
|
293
|
+
key = ERB::Util.xml_name_escape(key) if escape
|
|
294
|
+
|
|
295
|
+
case value
|
|
296
|
+
when Array, Hash
|
|
297
|
+
value = TagHelper.build_tag_values(value) if key.to_s == "class"
|
|
298
|
+
value = escape ? @view_context.safe_join(value, " ") : value.join(" ")
|
|
299
|
+
when Regexp
|
|
300
|
+
value = escape ? ERB::Util.unwrapped_html_escape(value.source) : value.source
|
|
301
|
+
else
|
|
302
|
+
value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
|
|
303
|
+
end
|
|
304
|
+
value = value.gsub('"', """) if value.include?('"')
|
|
305
|
+
|
|
306
|
+
%(#{key}="#{value}")
|
|
307
|
+
end
|
|
322
308
|
|
|
323
|
-
private
|
|
324
309
|
def prefix_tag_option(prefix, key, value, escape)
|
|
325
310
|
key = "#{prefix}-#{key.to_s.dasherize}"
|
|
326
311
|
unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
|
|
@@ -80,32 +80,30 @@ module ActionView
|
|
|
80
80
|
end
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
def
|
|
83
|
+
def add_default_name_and_id_for_value(tag_value, options)
|
|
84
84
|
if tag_value.nil?
|
|
85
|
-
|
|
85
|
+
add_default_name_and_id(options)
|
|
86
86
|
else
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
specified_id = options["id"]
|
|
88
|
+
add_default_name_and_id(options)
|
|
89
89
|
|
|
90
|
-
if
|
|
91
|
-
options[
|
|
90
|
+
if specified_id.blank? && options["id"].present?
|
|
91
|
+
options["id"] += "_#{sanitized_value(tag_value)}"
|
|
92
92
|
end
|
|
93
93
|
end
|
|
94
94
|
end
|
|
95
|
-
alias_method :add_default_name_and_id_for_value, :add_default_name_and_field_for_value
|
|
96
95
|
|
|
97
|
-
def
|
|
96
|
+
def add_default_name_and_id(options)
|
|
98
97
|
index = name_and_id_index(options)
|
|
99
98
|
options["name"] = options.fetch("name") { tag_name(options["multiple"], index) }
|
|
100
99
|
|
|
101
100
|
if generate_ids?
|
|
102
|
-
options[
|
|
101
|
+
options["id"] = options.fetch("id") { tag_id(index, options.delete("namespace")) }
|
|
103
102
|
if namespace = options.delete("namespace")
|
|
104
|
-
options[
|
|
103
|
+
options["id"] = options["id"] ? "#{namespace}_#{options['id']}" : namespace
|
|
105
104
|
end
|
|
106
105
|
end
|
|
107
106
|
end
|
|
108
|
-
alias_method :add_default_name_and_id, :add_default_name_and_field
|
|
109
107
|
|
|
110
108
|
def tag_name(multiple = false, index = nil)
|
|
111
109
|
@template_object.field_name(@object_name, sanitized_method_name, multiple: multiple, index: index)
|
|
@@ -21,10 +21,10 @@ module ActionView
|
|
|
21
21
|
options["checked"] = "checked" if input_checked?(options)
|
|
22
22
|
|
|
23
23
|
if options["multiple"]
|
|
24
|
-
|
|
24
|
+
add_default_name_and_id_for_value(@checked_value, options)
|
|
25
25
|
options.delete("multiple")
|
|
26
26
|
else
|
|
27
|
-
|
|
27
|
+
add_default_name_and_id(options)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
include_hidden = options.delete("include_hidden") { true }
|
|
@@ -6,7 +6,7 @@ module ActionView
|
|
|
6
6
|
class DatetimeField < TextField # :nodoc:
|
|
7
7
|
def render
|
|
8
8
|
options = @options.stringify_keys
|
|
9
|
-
options["value"] = datetime_value(options
|
|
9
|
+
options["value"] = datetime_value(options.fetch("value", value))
|
|
10
10
|
options["min"] = format_datetime(parse_datetime(options["min"]))
|
|
11
11
|
options["max"] = format_datetime(parse_datetime(options["max"]))
|
|
12
12
|
@options = options
|
|
@@ -6,11 +6,8 @@ module ActionView
|
|
|
6
6
|
class FileField < TextField # :nodoc:
|
|
7
7
|
def render
|
|
8
8
|
include_hidden = @options.delete(:include_hidden)
|
|
9
|
-
if @options[:accept].is_a?(Array)
|
|
10
|
-
@options[:accept] = @options[:accept].join(",")
|
|
11
|
-
end
|
|
12
9
|
options = @options.stringify_keys
|
|
13
|
-
|
|
10
|
+
add_default_name_and_id(options)
|
|
14
11
|
|
|
15
12
|
if options["multiple"] && include_hidden
|
|
16
13
|
hidden_field_for_multiple_file(options) + super
|
|
@@ -48,11 +48,18 @@ module ActionView
|
|
|
48
48
|
def render(&block)
|
|
49
49
|
options = @options.stringify_keys
|
|
50
50
|
tag_value = options.delete("value")
|
|
51
|
+
name_and_id = options.dup
|
|
51
52
|
|
|
52
|
-
|
|
53
|
+
if name_and_id["for"]
|
|
54
|
+
name_and_id["id"] = name_and_id["for"]
|
|
55
|
+
else
|
|
56
|
+
name_and_id.delete("id")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
add_default_name_and_id_for_value(tag_value, name_and_id)
|
|
53
60
|
options.delete("index")
|
|
54
|
-
options.delete("name")
|
|
55
61
|
options.delete("namespace")
|
|
62
|
+
options["for"] = name_and_id["id"] unless options.key?("for")
|
|
56
63
|
|
|
57
64
|
builder = LabelBuilder.new(@template_object, @object_name, @method_name, @object, tag_value)
|
|
58
65
|
|
|
@@ -64,7 +71,7 @@ module ActionView
|
|
|
64
71
|
render_component(builder)
|
|
65
72
|
end
|
|
66
73
|
|
|
67
|
-
label_tag(
|
|
74
|
+
label_tag(name_and_id["id"], content, options)
|
|
68
75
|
end
|
|
69
76
|
|
|
70
77
|
private
|
|
@@ -18,7 +18,7 @@ module ActionView
|
|
|
18
18
|
options["type"] = "radio"
|
|
19
19
|
options["value"] = @tag_value
|
|
20
20
|
options["checked"] = "checked" if input_checked?(options)
|
|
21
|
-
|
|
21
|
+
add_default_name_and_id_for_value(@tag_value, options)
|
|
22
22
|
tag("input", options)
|
|
23
23
|
end
|
|
24
24
|
|
|
@@ -37,7 +37,12 @@ module ActionView
|
|
|
37
37
|
# [nil, []]
|
|
38
38
|
# { nil => [] }
|
|
39
39
|
def grouped_choices?
|
|
40
|
-
|
|
40
|
+
return false if @choices.blank?
|
|
41
|
+
|
|
42
|
+
first_choice = @choices.first
|
|
43
|
+
return false unless first_choice.is_a?(Enumerable)
|
|
44
|
+
|
|
45
|
+
first_choice.second.is_a?(Array)
|
|
41
46
|
end
|
|
42
47
|
end
|
|
43
48
|
end
|
|
@@ -11,7 +11,7 @@ module ActionView
|
|
|
11
11
|
html_options[prop.to_s] = options.delete(prop) if options.key?(prop) && !html_options.key?(prop.to_s)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
add_default_name_and_id(html_options)
|
|
15
15
|
|
|
16
16
|
if placeholder_required?(html_options)
|
|
17
17
|
raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false
|
|
@@ -37,7 +37,7 @@ module ActionView
|
|
|
37
37
|
if options[:include_blank]
|
|
38
38
|
content = (options[:include_blank] if options[:include_blank].is_a?(String))
|
|
39
39
|
label = (" " unless content)
|
|
40
|
-
option_tags = tag_builder.
|
|
40
|
+
option_tags = tag_builder.option(content, value: "", label: label) + "\n" + option_tags
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
if value.blank? && options[:prompt]
|
|
@@ -45,7 +45,7 @@ module ActionView
|
|
|
45
45
|
prompt_opts[:disabled] = true if options[:disabled] == ""
|
|
46
46
|
prompt_opts[:selected] = true if options[:selected] == ""
|
|
47
47
|
end
|
|
48
|
-
option_tags = tag_builder.
|
|
48
|
+
option_tags = tag_builder.option(prompt_text(options[:prompt]), **tag_options) + "\n" + option_tags
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
option_tags
|
|
@@ -10,7 +10,7 @@ module ActionView
|
|
|
10
10
|
|
|
11
11
|
def render
|
|
12
12
|
options = @options.stringify_keys
|
|
13
|
-
|
|
13
|
+
add_default_name_and_id(options)
|
|
14
14
|
|
|
15
15
|
if size = options.delete("size")
|
|
16
16
|
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
|
|
@@ -13,7 +13,7 @@ module ActionView
|
|
|
13
13
|
options["size"] = options["maxlength"] unless options.key?("size")
|
|
14
14
|
options["type"] ||= field_type
|
|
15
15
|
options["value"] = options.fetch("value") { value_before_type_cast } unless field_type == "file"
|
|
16
|
-
|
|
16
|
+
add_default_name_and_id(options)
|
|
17
17
|
tag("input", options)
|
|
18
18
|
end
|
|
19
19
|
|
|
@@ -143,15 +143,8 @@ module ActionView
|
|
|
143
143
|
#
|
|
144
144
|
# [+:highlighter+]
|
|
145
145
|
# The highlighter string. Uses <tt>\1</tt> as the placeholder for a
|
|
146
|
-
# phrase
|
|
147
|
-
#
|
|
148
|
-
# are not. The phrase is HTML escaped, quotes included. This option
|
|
149
|
-
# is ignored if a block is specified.
|
|
150
|
-
#
|
|
151
|
-
# WARNING: Put a placeholder only in element content or inside a
|
|
152
|
-
# quoted attribute value. HTML escaping does not make the phrase safe
|
|
153
|
-
# in an unquoted attribute value, in a URL, JavaScript or CSS
|
|
154
|
-
# context, or in an attribute whose value is HTML, such as +srcdoc+.
|
|
146
|
+
# phrase, similar to +String#sub+. Defaults to <tt>"<mark>\1</mark>"</tt>.
|
|
147
|
+
# This option is ignored if a block is specified.
|
|
155
148
|
#
|
|
156
149
|
# [+:sanitize+]
|
|
157
150
|
# Whether to sanitize +text+ before highlighting. Defaults to true.
|
|
@@ -170,8 +163,8 @@ module ActionView
|
|
|
170
163
|
# highlight('You searched for: rails', ['for', 'rails'], highlighter: '<em>\1</em>')
|
|
171
164
|
# # => "You searched <em>for</em>: <em>rails</em>"
|
|
172
165
|
#
|
|
173
|
-
# highlight('You searched for: rails', 'rails', highlighter: '<
|
|
174
|
-
# # => "You searched for: <
|
|
166
|
+
# highlight('You searched for: rails', 'rails', highlighter: '<a href="search?q=\1">\1</a>')
|
|
167
|
+
# # => "You searched for: <a href=\"search?q=rails\">rails</a>"
|
|
175
168
|
#
|
|
176
169
|
# highlight('You searched for: rails', 'rails') { |match| link_to(search_path(q: match)) }
|
|
177
170
|
# # => "You searched for: <a href=\"search?q=rails\">rails</a>"
|
|
@@ -186,23 +179,14 @@ module ActionView
|
|
|
186
179
|
else
|
|
187
180
|
patterns = Array(phrases).map { |phrase| Regexp === phrase ? phrase : Regexp.escape(phrase) }
|
|
188
181
|
pattern = /(#{patterns.join("|")})/i
|
|
189
|
-
# The text after a tag begins with the ">" that closes it. Leave it
|
|
190
|
-
# in the segment so phrases can see it as context, but keep it out
|
|
191
|
-
# of the match so the highlighter can't take the place of the ">".
|
|
192
|
-
pattern_after_tag = /(?!\A)#{pattern}/
|
|
193
182
|
highlighter = options.fetch(:highlighter, '<mark>\1</mark>') unless block
|
|
194
183
|
|
|
195
|
-
text.scan(/<[^>]*|[^<]+/).
|
|
184
|
+
text.scan(/<[^>]*|[^<]+/).each do |segment|
|
|
196
185
|
if !segment.start_with?("<")
|
|
197
|
-
segment_pattern = index > 0 ? pattern_after_tag : pattern
|
|
198
|
-
|
|
199
186
|
if block
|
|
200
|
-
segment.gsub!(
|
|
187
|
+
segment.gsub!(pattern, &block)
|
|
201
188
|
else
|
|
202
|
-
segment.gsub!(
|
|
203
|
-
match = Regexp.last_match
|
|
204
|
-
highlighter.gsub(/\\(\d)/) { ERB::Util.html_escape_once(match[$1.to_i]) }
|
|
205
|
-
end
|
|
189
|
+
segment.gsub!(pattern, highlighter)
|
|
206
190
|
end
|
|
207
191
|
end
|
|
208
192
|
end.join
|
|
@@ -140,7 +140,12 @@ module ActionView
|
|
|
140
140
|
end
|
|
141
141
|
|
|
142
142
|
def missing_translation(key, options)
|
|
143
|
-
|
|
143
|
+
locale = options[:locale] || I18n.locale
|
|
144
|
+
|
|
145
|
+
i18n_exception = I18n::MissingTranslation.new(locale, key, options)
|
|
146
|
+
I18n.exception_handler.call(i18n_exception, locale, key, options)
|
|
147
|
+
|
|
148
|
+
keys = I18n.normalize_keys(locale, key, options[:scope])
|
|
144
149
|
|
|
145
150
|
title = +"translation missing: #{keys.join(".")}"
|
|
146
151
|
|
|
@@ -341,8 +341,9 @@ module ActionView
|
|
|
341
341
|
inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag)
|
|
342
342
|
if params
|
|
343
343
|
to_form_params(params).each do |param|
|
|
344
|
-
|
|
345
|
-
|
|
344
|
+
options = { type: "hidden", name: param[:name], value: param[:value] }
|
|
345
|
+
options[:autocomplete] = "off" unless ActionView::Base.remove_hidden_field_autocomplete
|
|
346
|
+
inner_tags.safe_concat tag(:input, **options)
|
|
346
347
|
end
|
|
347
348
|
end
|
|
348
349
|
html = content_tag("form", inner_tags, form_options)
|
|
@@ -538,24 +539,47 @@ module ActionView
|
|
|
538
539
|
# current_page?('http://www.example.com/shop/checkout?order=desc&page=1')
|
|
539
540
|
# # => true
|
|
540
541
|
#
|
|
541
|
-
#
|
|
542
|
+
# Different actions may share the same URL path but have a different HTTP method. Let's say we
|
|
543
|
+
# sent a POST to <tt>http://www.example.com/products</tt> and rendered a validation error.
|
|
542
544
|
#
|
|
543
545
|
# current_page?(controller: 'product', action: 'index')
|
|
544
546
|
# # => false
|
|
545
547
|
#
|
|
548
|
+
# current_page?(controller: 'product', action: 'create')
|
|
549
|
+
# # => false
|
|
550
|
+
#
|
|
551
|
+
# current_page?(controller: 'product', action: 'create', method: :post)
|
|
552
|
+
# # => true
|
|
553
|
+
#
|
|
554
|
+
# current_page?(controller: 'product', action: 'index', method: [:get, :post])
|
|
555
|
+
# # => true
|
|
556
|
+
#
|
|
546
557
|
# We can also pass in the symbol arguments instead of strings.
|
|
547
558
|
#
|
|
548
|
-
def current_page?(options = nil, check_parameters: false, **options_as_kwargs)
|
|
559
|
+
def current_page?(options = nil, check_parameters: false, method: :get, **options_as_kwargs)
|
|
549
560
|
unless request
|
|
550
561
|
raise "You cannot use helpers that need to determine the current " \
|
|
551
562
|
"page unless your view context provides a Request object " \
|
|
552
563
|
"in a #request method"
|
|
553
564
|
end
|
|
554
565
|
|
|
555
|
-
|
|
566
|
+
if options.is_a?(Hash)
|
|
567
|
+
check_parameters = options.delete(:check_parameters) { check_parameters }
|
|
568
|
+
method = options.delete(:method) { method }
|
|
569
|
+
else
|
|
570
|
+
options ||= options_as_kwargs
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
method_matches = case method
|
|
574
|
+
when :get
|
|
575
|
+
request.get? || request.head?
|
|
576
|
+
when Array
|
|
577
|
+
method.include?(request.method_symbol) || (method.include?(:get) && request.head?)
|
|
578
|
+
else
|
|
579
|
+
method == request.method_symbol
|
|
580
|
+
end
|
|
581
|
+
return false unless method_matches
|
|
556
582
|
|
|
557
|
-
options ||= options_as_kwargs
|
|
558
|
-
check_parameters ||= options.is_a?(Hash) && options.delete(:check_parameters)
|
|
559
583
|
url_string = URI::RFC2396_PARSER.unescape(url_for(options)).force_encoding(Encoding::BINARY)
|
|
560
584
|
|
|
561
585
|
# We ignore any extra parameters in the request_uri if the
|
|
@@ -751,14 +775,18 @@ module ActionView
|
|
|
751
775
|
else
|
|
752
776
|
token
|
|
753
777
|
end
|
|
754
|
-
|
|
778
|
+
options = { type: "hidden", name: request_forgery_protection_token.to_s, value: token }
|
|
779
|
+
options[:autocomplete] = "off" unless ActionView::Base.remove_hidden_field_autocomplete
|
|
780
|
+
tag(:input, **options)
|
|
755
781
|
else
|
|
756
782
|
""
|
|
757
783
|
end
|
|
758
784
|
end
|
|
759
785
|
|
|
760
786
|
def method_tag(method)
|
|
761
|
-
|
|
787
|
+
options = { type: "hidden", name: "_method", value: method.to_s }
|
|
788
|
+
options[:autocomplete] = "off" unless ActionView::Base.remove_hidden_field_autocomplete
|
|
789
|
+
tag("input", **options)
|
|
762
790
|
end
|
|
763
791
|
|
|
764
792
|
# Returns an array of hashes each containing :name and :value keys
|
data/lib/action_view/railtie.rb
CHANGED
|
@@ -72,8 +72,19 @@ module ActionView
|
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
config.after_initialize do |app|
|
|
75
|
+
ActionView::Helpers::AssetTagHelper.auto_include_nonce_for_scripts = app.config.content_security_policy_nonce_auto && app.config.content_security_policy_nonce_directives.intersect?(["script-src", "script-src-elem", "script-src-attr"]) && app.config.content_security_policy_nonce_generator.present?
|
|
76
|
+
ActionView::Helpers::AssetTagHelper.auto_include_nonce_for_styles = app.config.content_security_policy_nonce_auto && app.config.content_security_policy_nonce_directives.intersect?(["style-src", "style-src-elem", "style-src-attr"]) && app.config.content_security_policy_nonce_generator.present?
|
|
77
|
+
ActionView::Helpers::JavaScriptHelper.auto_include_nonce = app.config.content_security_policy_nonce_auto && app.config.content_security_policy_nonce_directives.intersect?(["script-src", "script-src-elem", "script-src-attr"]) && app.config.content_security_policy_nonce_generator.present?
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
config.after_initialize do |app|
|
|
81
|
+
config.after_initialize do
|
|
82
|
+
ActionView.render_tracker = config.action_view.render_tracker
|
|
83
|
+
end
|
|
84
|
+
|
|
75
85
|
ActiveSupport.on_load(:action_view) do
|
|
76
86
|
app.config.action_view.each do |k, v|
|
|
87
|
+
next if k == :render_tracker
|
|
77
88
|
send "#{k}=", v
|
|
78
89
|
end
|
|
79
90
|
end
|
|
@@ -101,6 +101,27 @@ module ActionView
|
|
|
101
101
|
end
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
+
# The DOM target convention is to concatenate any number of parameters into a string.
|
|
105
|
+
# Records are passed through dom_id, while string and symbols are retained.
|
|
106
|
+
#
|
|
107
|
+
# dom_target(Post.find(45)) # => "post_45"
|
|
108
|
+
# dom_target(Post.find(45), :edit) # => "post_45_edit"
|
|
109
|
+
# dom_target(Post.find(45), :edit, :special) # => "post_45_edit_special"
|
|
110
|
+
# dom_target(Post.find(45), Comment.find(1)) # => "post_45_comment_1"
|
|
111
|
+
def dom_target(*objects)
|
|
112
|
+
objects.map! do |object|
|
|
113
|
+
case object
|
|
114
|
+
when Symbol, String
|
|
115
|
+
object
|
|
116
|
+
when Class
|
|
117
|
+
dom_class(object)
|
|
118
|
+
else
|
|
119
|
+
dom_id(object)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
objects.join(JOIN)
|
|
123
|
+
end
|
|
124
|
+
|
|
104
125
|
private
|
|
105
126
|
# Returns a string representation of the key attribute(s) that is suitable for use in an HTML DOM id.
|
|
106
127
|
# This can be overwritten to customize the default generated string representation if desired.
|