actionview 5.2.4.4 → 6.1.1
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 +221 -93
- data/MIT-LICENSE +1 -1
- data/README.rdoc +5 -3
- data/lib/action_view.rb +7 -2
- data/lib/action_view/base.rb +81 -15
- data/lib/action_view/buffers.rb +15 -0
- data/lib/action_view/cache_expiry.rb +52 -0
- data/lib/action_view/context.rb +5 -9
- data/lib/action_view/dependency_tracker.rb +10 -4
- data/lib/action_view/digestor.rb +15 -22
- data/lib/action_view/flows.rb +0 -1
- data/lib/action_view/gem_version.rb +4 -4
- data/lib/action_view/helpers.rb +0 -2
- data/lib/action_view/helpers/active_model_helper.rb +0 -1
- data/lib/action_view/helpers/asset_tag_helper.rb +63 -46
- data/lib/action_view/helpers/asset_url_helper.rb +9 -6
- data/lib/action_view/helpers/atom_feed_helper.rb +2 -1
- data/lib/action_view/helpers/cache_helper.rb +23 -22
- data/lib/action_view/helpers/capture_helper.rb +4 -0
- data/lib/action_view/helpers/csp_helper.rb +4 -2
- data/lib/action_view/helpers/csrf_helper.rb +1 -1
- data/lib/action_view/helpers/date_helper.rb +73 -30
- data/lib/action_view/helpers/form_helper.rb +305 -37
- data/lib/action_view/helpers/form_options_helper.rb +23 -23
- data/lib/action_view/helpers/form_tag_helper.rb +19 -16
- data/lib/action_view/helpers/javascript_helper.rb +12 -11
- data/lib/action_view/helpers/number_helper.rb +14 -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 +12 -18
- data/lib/action_view/helpers/tag_helper.rb +98 -22
- data/lib/action_view/helpers/tags/base.rb +18 -11
- 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 +1 -2
- 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/translator.rb +1 -6
- data/lib/action_view/helpers/tags/week_field.rb +1 -2
- data/lib/action_view/helpers/text_helper.rb +3 -4
- data/lib/action_view/helpers/translation_helper.rb +93 -55
- data/lib/action_view/helpers/url_helper.rb +121 -27
- data/lib/action_view/layouts.rb +8 -10
- data/lib/action_view/log_subscriber.rb +30 -15
- data/lib/action_view/lookup_context.rb +63 -35
- data/lib/action_view/path_set.rb +3 -12
- data/lib/action_view/railtie.rb +42 -26
- data/lib/action_view/record_identifier.rb +2 -3
- 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.rb +21 -273
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +61 -16
- data/lib/action_view/renderer/renderer.rb +59 -4
- data/lib/action_view/renderer/streaming_template_renderer.rb +10 -8
- data/lib/action_view/renderer/template_renderer.rb +35 -27
- data/lib/action_view/rendering.rb +54 -33
- data/lib/action_view/routing_url_for.rb +13 -12
- data/lib/action_view/template.rb +66 -75
- data/lib/action_view/template/error.rb +30 -15
- data/lib/action_view/template/handlers.rb +1 -1
- data/lib/action_view/template/handlers/builder.rb +2 -2
- data/lib/action_view/template/handlers/erb.rb +16 -11
- data/lib/action_view/template/handlers/erb/erubi.rb +15 -9
- 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/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 +191 -150
- data/lib/action_view/template/sources.rb +13 -0
- data/lib/action_view/template/sources/file.rb +17 -0
- data/lib/action_view/template/text.rb +2 -3
- data/lib/action_view/test_case.rb +21 -29
- 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/assets/compiled/rails-ujs.js +29 -3
- metadata +32 -21
- data/lib/action_view/helpers/record_tag_helper.rb +0 -23
@@ -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, []]
|
@@ -16,13 +16,8 @@ module ActionView
|
|
16
16
|
translated_attribute || human_attribute_name
|
17
17
|
end
|
18
18
|
|
19
|
-
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
20
|
-
# Workaround for Ruby 2.2 "private attribute?" warning.
|
21
|
-
protected
|
22
|
-
|
23
|
-
attr_reader :object_name, :method_and_value, :scope, :model
|
24
|
-
|
25
19
|
private
|
20
|
+
attr_reader :object_name, :method_and_value, :scope, :model
|
26
21
|
|
27
22
|
def i18n_default
|
28
23
|
if model
|
@@ -188,7 +188,7 @@ module ActionView
|
|
188
188
|
|
189
189
|
unless separator.empty?
|
190
190
|
text.split(separator).each do |value|
|
191
|
-
if value.match(regex)
|
191
|
+
if value.match?(regex)
|
192
192
|
phrase = value
|
193
193
|
break
|
194
194
|
end
|
@@ -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
|
231
|
+
word = if count == 1 || count.to_s.match?(/^1(\.0+)?$/)
|
232
232
|
singular
|
233
233
|
else
|
234
234
|
plural || singular.pluralize(locale)
|
@@ -259,7 +259,7 @@ module ActionView
|
|
259
259
|
# # => Once\r\nupon\r\na\r\ntime
|
260
260
|
def word_wrap(text, line_width: 80, break_sequence: "\n")
|
261
261
|
text.split("\n").collect! do |line|
|
262
|
-
line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").
|
262
|
+
line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").rstrip : line
|
263
263
|
end * break_sequence
|
264
264
|
end
|
265
265
|
|
@@ -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
|
@@ -1,8 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "action_view/helpers/tag_helper"
|
4
|
-
require "active_support/core_ext/
|
5
|
-
require "i18n/exceptions"
|
4
|
+
require "active_support/core_ext/symbol/starts_ends_with"
|
6
5
|
|
7
6
|
module ActionView
|
8
7
|
# = Action View Translation Helpers
|
@@ -57,74 +56,65 @@ module ActionView
|
|
57
56
|
# that include HTML tags so that you know what kind of output to expect
|
58
57
|
# when you call translate in a template and translators know which keys
|
59
58
|
# they can provide HTML values for.
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
59
|
+
#
|
60
|
+
# To access the translated text along with the fully resolved
|
61
|
+
# translation key, <tt>translate</tt> accepts a block:
|
62
|
+
#
|
63
|
+
# <%= translate(".relative_key") do |translation, resolved_key| %>
|
64
|
+
# <span title="<%= resolved_key %>"><%= translation %></span>
|
65
|
+
# <% end %>
|
66
|
+
#
|
67
|
+
# This enables annotate translated text to be aware of the scope it was
|
68
|
+
# resolved against.
|
69
|
+
#
|
70
|
+
def translate(key, **options)
|
71
|
+
return key.map { |k| translate(k, **options) } if key.is_a?(Array)
|
72
|
+
key = key&.to_s unless key.is_a?(Symbol)
|
64
73
|
|
65
|
-
|
66
|
-
options[:default]
|
74
|
+
alternatives = if options.key?(:default)
|
75
|
+
options[:default].is_a?(Array) ? options.delete(:default).compact : [options.delete(:default)]
|
67
76
|
end
|
68
77
|
|
69
|
-
|
70
|
-
|
71
|
-
# Note: `raise_error` refers to us re-raising the error in this method. I18n is forced to raise by default.
|
72
|
-
if options[:raise] == false
|
73
|
-
raise_error = false
|
74
|
-
i18n_raise = false
|
75
|
-
else
|
76
|
-
raise_error = options[:raise] || ActionView::Base.raise_on_missing_translations
|
77
|
-
i18n_raise = true
|
78
|
-
end
|
78
|
+
options[:raise] = true if options[:raise].nil? && ActionView::Base.raise_on_missing_translations
|
79
|
+
default = MISSING_TRANSLATION
|
79
80
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
options.except(*I18n::RESERVED_KEYS).each do |name, value|
|
84
|
-
unless name == :count && value.is_a?(Numeric)
|
85
|
-
html_safe_options[name] = ERB::Util.html_escape(value.to_s)
|
86
|
-
end
|
81
|
+
translation = while key || alternatives.present?
|
82
|
+
if alternatives.blank? && !options[:raise].nil?
|
83
|
+
default = NO_DEFAULT # let I18n handle missing translation
|
87
84
|
end
|
88
85
|
|
89
|
-
|
86
|
+
key = scope_key_by_partial(key)
|
90
87
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
88
|
+
if html_safe_translation_key?(key)
|
89
|
+
html_safe_options ||= html_escape_translation_options(options)
|
90
|
+
translated = I18n.translate(key, **html_safe_options, default: default)
|
91
|
+
break html_safe_translation(translated) unless translated.equal?(MISSING_TRANSLATION)
|
95
92
|
else
|
96
|
-
|
97
|
-
|
98
|
-
else
|
99
|
-
I18n.translate(scope_key_by_partial(key), options.merge(raise: i18n_raise))
|
100
|
-
end
|
101
|
-
rescue I18n::MissingTranslationData => e
|
102
|
-
if remaining_defaults.present?
|
103
|
-
translate remaining_defaults.shift, options.merge(default: remaining_defaults)
|
104
|
-
else
|
105
|
-
raise e if raise_error
|
106
|
-
|
107
|
-
keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
|
108
|
-
title = "translation missing: #{keys.join('.')}".dup
|
109
|
-
|
110
|
-
interpolations = options.except(:default, :scope)
|
111
|
-
if interpolations.any?
|
112
|
-
title << ", " << interpolations.map { |k, v| "#{k}: #{ERB::Util.html_escape(v)}" }.join(", ")
|
93
|
+
translated = I18n.translate(key, **options, default: default)
|
94
|
+
break translated unless translated.equal?(MISSING_TRANSLATION)
|
113
95
|
end
|
114
96
|
|
115
|
-
|
97
|
+
break alternatives.first if alternatives.present? && !alternatives.first.is_a?(Symbol)
|
116
98
|
|
117
|
-
|
99
|
+
first_key ||= key
|
100
|
+
key = alternatives&.shift
|
118
101
|
end
|
102
|
+
|
103
|
+
if key.nil? && !first_key.nil?
|
104
|
+
translation = missing_translation(first_key, options)
|
105
|
+
key = first_key
|
106
|
+
end
|
107
|
+
|
108
|
+
block_given? ? yield(translation, key) : translation
|
119
109
|
end
|
120
110
|
alias :t :translate
|
121
111
|
|
122
112
|
# Delegates to <tt>I18n.localize</tt> with no additional functionality.
|
123
113
|
#
|
124
|
-
# See
|
114
|
+
# See https://www.rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
|
125
115
|
# for more information.
|
126
|
-
def localize(
|
127
|
-
I18n.localize(
|
116
|
+
def localize(object, **options)
|
117
|
+
I18n.localize(object, **options)
|
128
118
|
end
|
129
119
|
alias :l :localize
|
130
120
|
|
@@ -132,10 +122,19 @@ module ActionView
|
|
132
122
|
MISSING_TRANSLATION = Object.new
|
133
123
|
private_constant :MISSING_TRANSLATION
|
134
124
|
|
125
|
+
NO_DEFAULT = [].freeze
|
126
|
+
private_constant :NO_DEFAULT
|
127
|
+
|
128
|
+
def self.i18n_option?(name)
|
129
|
+
(@i18n_option_names ||= I18n::RESERVED_KEYS.to_set).include?(name)
|
130
|
+
end
|
131
|
+
|
135
132
|
def scope_key_by_partial(key)
|
136
|
-
if key
|
133
|
+
if key&.start_with?(".")
|
137
134
|
if @virtual_path
|
138
|
-
@
|
135
|
+
@_scope_key_by_partial_cache ||= {}
|
136
|
+
@_scope_key_by_partial_cache[@virtual_path] ||= @virtual_path.gsub(%r{/_?}, ".")
|
137
|
+
"#{@_scope_key_by_partial_cache[@virtual_path]}#{key}"
|
139
138
|
else
|
140
139
|
raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
|
141
140
|
end
|
@@ -144,8 +143,47 @@ module ActionView
|
|
144
143
|
end
|
145
144
|
end
|
146
145
|
|
146
|
+
def html_escape_translation_options(options)
|
147
|
+
return options if options.empty?
|
148
|
+
html_safe_options = options.dup
|
149
|
+
|
150
|
+
options.each do |name, value|
|
151
|
+
unless TranslationHelper.i18n_option?(name) || (name == :count && value.is_a?(Numeric))
|
152
|
+
html_safe_options[name] = ERB::Util.html_escape(value.to_s)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
html_safe_options
|
157
|
+
end
|
158
|
+
|
147
159
|
def html_safe_translation_key?(key)
|
148
|
-
/(
|
160
|
+
/(?:_|\b)html\z/.match?(key)
|
161
|
+
end
|
162
|
+
|
163
|
+
def html_safe_translation(translation)
|
164
|
+
if translation.respond_to?(:map)
|
165
|
+
translation.map { |element| element.respond_to?(:html_safe) ? element.html_safe : element }
|
166
|
+
else
|
167
|
+
translation.respond_to?(:html_safe) ? translation.html_safe : translation
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def missing_translation(key, options)
|
172
|
+
keys = I18n.normalize_keys(options[:locale] || I18n.locale, key, options[:scope])
|
173
|
+
|
174
|
+
title = +"translation missing: #{keys.join(".")}"
|
175
|
+
|
176
|
+
options.each do |name, value|
|
177
|
+
unless name == :scope
|
178
|
+
title << ", " << name.to_s << ": " << ERB::Util.html_escape(value)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
if ActionView::Base.debug_missing_translation
|
183
|
+
content_tag("span", keys.last.to_s.titleize, class: "translation_missing", title: title)
|
184
|
+
else
|
185
|
+
title
|
186
|
+
end
|
149
187
|
end
|
150
188
|
end
|
151
189
|
end
|
@@ -45,7 +45,7 @@ module ActionView
|
|
45
45
|
def _back_url # :nodoc:
|
46
46
|
_filtered_referrer || "javascript:history.back()"
|
47
47
|
end
|
48
|
-
|
48
|
+
private :_back_url
|
49
49
|
|
50
50
|
def _filtered_referrer # :nodoc:
|
51
51
|
if controller.respond_to?(:request)
|
@@ -56,12 +56,12 @@ module ActionView
|
|
56
56
|
end
|
57
57
|
rescue URI::InvalidURIError
|
58
58
|
end
|
59
|
-
|
59
|
+
private :_filtered_referrer
|
60
60
|
|
61
61
|
# Creates an anchor element of the given +name+ using a URL created by the set of +options+.
|
62
62
|
# See the valid options in the documentation for +url_for+. It's also possible to
|
63
|
-
# pass a String instead of an options hash, which generates an anchor element that uses the
|
64
|
-
# value of the String as the href for the link. Using a <tt>:back</tt> Symbol instead
|
63
|
+
# pass a \String instead of an options hash, which generates an anchor element that uses the
|
64
|
+
# value of the \String as the href for the link. Using a <tt>:back</tt> \Symbol instead
|
65
65
|
# of an options hash will generate a link to the referrer (a JavaScript back link
|
66
66
|
# will be used in place of a referrer if none exists). If +nil+ is passed as the name
|
67
67
|
# the value of the link itself will become the name.
|
@@ -177,7 +177,7 @@ module ActionView
|
|
177
177
|
# # => <a href="/searches?query=ruby+on+rails">Ruby on Rails search</a>
|
178
178
|
#
|
179
179
|
# link_to "Nonsense search", searches_path(foo: "bar", baz: "quux")
|
180
|
-
# # => <a href="/searches?foo=bar&
|
180
|
+
# # => <a href="/searches?foo=bar&baz=quux">Nonsense search</a>
|
181
181
|
#
|
182
182
|
# The only option specific to +link_to+ (<tt>:method</tt>) is used as follows:
|
183
183
|
#
|
@@ -200,9 +200,9 @@ module ActionView
|
|
200
200
|
html_options = convert_options_to_data_attributes(options, html_options)
|
201
201
|
|
202
202
|
url = url_for(options)
|
203
|
-
html_options["href"
|
203
|
+
html_options["href"] ||= url
|
204
204
|
|
205
|
-
content_tag("a"
|
205
|
+
content_tag("a", name || url, html_options, &block)
|
206
206
|
end
|
207
207
|
|
208
208
|
# Generates a form containing a single button that submits to the URL created
|
@@ -226,7 +226,7 @@ module ActionView
|
|
226
226
|
# The +options+ hash accepts the same options as +url_for+.
|
227
227
|
#
|
228
228
|
# There are a few special +html_options+:
|
229
|
-
# * <tt>:method</tt> - Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>,
|
229
|
+
# * <tt>:method</tt> - \Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>,
|
230
230
|
# <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>. By default it will be <tt>:post</tt>.
|
231
231
|
# * <tt>:disabled</tt> - If set to true, it will generate a disabled button.
|
232
232
|
# * <tt>:data</tt> - This option can be used to add custom data attributes.
|
@@ -235,7 +235,7 @@ module ActionView
|
|
235
235
|
# * <tt>:form</tt> - This hash will be form attributes
|
236
236
|
# * <tt>:form_class</tt> - This controls the class of the form within which the submit button will
|
237
237
|
# be placed
|
238
|
-
# * <tt>:params</tt> - Hash of parameters to be rendered as hidden fields within the form.
|
238
|
+
# * <tt>:params</tt> - \Hash of parameters to be rendered as hidden fields within the form.
|
239
239
|
#
|
240
240
|
# ==== Data attributes
|
241
241
|
#
|
@@ -290,7 +290,7 @@ module ActionView
|
|
290
290
|
#
|
291
291
|
#
|
292
292
|
# <%= button_to('Destroy', 'http://www.example.com',
|
293
|
-
# method:
|
293
|
+
# method: :delete, remote: true, data: { confirm: 'Are you sure?', disable_with: 'loading...' }) %>
|
294
294
|
# # => "<form class='button_to' method='post' action='http://www.example.com' data-remote='true'>
|
295
295
|
# # <input name='_method' value='delete' type='hidden' />
|
296
296
|
# # <input value='Destroy' type='submit' data-disable-with='loading...' data-confirm='Are you sure?' />
|
@@ -308,7 +308,7 @@ module ActionView
|
|
308
308
|
params = html_options.delete("params")
|
309
309
|
|
310
310
|
method = html_options.delete("method").to_s
|
311
|
-
method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : "".
|
311
|
+
method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : "".html_safe
|
312
312
|
|
313
313
|
form_method = method == "get" ? "get" : "post"
|
314
314
|
form_options = html_options.delete("form") || {}
|
@@ -321,7 +321,7 @@ module ActionView
|
|
321
321
|
request_method = method.empty? ? "post" : method
|
322
322
|
token_tag(nil, form_options: { action: url, method: request_method })
|
323
323
|
else
|
324
|
-
""
|
324
|
+
""
|
325
325
|
end
|
326
326
|
|
327
327
|
html_options = convert_options_to_data_attributes(options, html_options)
|
@@ -412,8 +412,7 @@ module ActionView
|
|
412
412
|
# Creates a link tag of the given +name+ using a URL created by the set of
|
413
413
|
# +options+ if +condition+ is true, otherwise only the name is
|
414
414
|
# returned. To specialize the default behavior, you can pass a block that
|
415
|
-
# accepts the name or the full argument list for +
|
416
|
-
# in +link_to_unless+).
|
415
|
+
# accepts the name or the full argument list for +link_to_if+.
|
417
416
|
#
|
418
417
|
# ==== Examples
|
419
418
|
# <%= link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) %>
|
@@ -487,12 +486,12 @@ module ActionView
|
|
487
486
|
option = html_options.delete(item).presence || next
|
488
487
|
"#{item.dasherize}=#{ERB::Util.url_encode(option)}"
|
489
488
|
}.compact
|
490
|
-
extras = extras.empty? ? ""
|
489
|
+
extras = extras.empty? ? "" : "?" + extras.join("&")
|
491
490
|
|
492
491
|
encoded_email_address = ERB::Util.url_encode(email_address).gsub("%40", "@")
|
493
492
|
html_options["href"] = "mailto:#{encoded_email_address}#{extras}"
|
494
493
|
|
495
|
-
content_tag("a"
|
494
|
+
content_tag("a", name || email_address, html_options, &block)
|
496
495
|
end
|
497
496
|
|
498
497
|
# True if the current request URI was generated by the given +options+.
|
@@ -550,14 +549,14 @@ module ActionView
|
|
550
549
|
return false unless request.get? || request.head?
|
551
550
|
|
552
551
|
check_parameters ||= options.is_a?(Hash) && options.delete(:check_parameters)
|
553
|
-
url_string = URI.
|
552
|
+
url_string = URI::DEFAULT_PARSER.unescape(url_for(options)).force_encoding(Encoding::BINARY)
|
554
553
|
|
555
554
|
# We ignore any extra parameters in the request_uri if the
|
556
|
-
# submitted
|
555
|
+
# submitted URL doesn't have any either. This lets the function
|
557
556
|
# work with things like ?order=asc
|
558
557
|
# the behaviour can be disabled with check_parameters: true
|
559
558
|
request_uri = url_string.index("?") || check_parameters ? request.fullpath : request.path
|
560
|
-
request_uri = URI.
|
559
|
+
request_uri = URI::DEFAULT_PARSER.unescape(request_uri).force_encoding(Encoding::BINARY)
|
561
560
|
|
562
561
|
if url_string.start_with?("/") && url_string != "/"
|
563
562
|
url_string.chomp!("/")
|
@@ -571,30 +570,125 @@ module ActionView
|
|
571
570
|
end
|
572
571
|
end
|
573
572
|
|
573
|
+
# Creates an SMS anchor link tag to the specified +phone_number+, which is
|
574
|
+
# also used as the name of the link unless +name+ is specified. Additional
|
575
|
+
# HTML attributes for the link can be passed in +html_options+.
|
576
|
+
#
|
577
|
+
# When clicked, an SMS message is prepopulated with the passed phone number
|
578
|
+
# and optional +body+ value.
|
579
|
+
#
|
580
|
+
# +sms_to+ has a +body+ option for customizing the SMS message itself by
|
581
|
+
# passing special keys to +html_options+.
|
582
|
+
#
|
583
|
+
# ==== Options
|
584
|
+
# * <tt>:body</tt> - Preset the body of the message.
|
585
|
+
#
|
586
|
+
# ==== Examples
|
587
|
+
# sms_to "5155555785"
|
588
|
+
# # => <a href="sms:5155555785;">5155555785</a>
|
589
|
+
#
|
590
|
+
# sms_to "5155555785", "Text me"
|
591
|
+
# # => <a href="sms:5155555785;">Text me</a>
|
592
|
+
#
|
593
|
+
# sms_to "5155555785", "Text me",
|
594
|
+
# body: "Hello Jim I have a question about your product."
|
595
|
+
# # => <a href="sms:5155555785;?body=Hello%20Jim%20I%20have%20a%20question%20about%20your%20product">Text me</a>
|
596
|
+
#
|
597
|
+
# You can use a block as well if your link target is hard to fit into the name parameter. \ERB example:
|
598
|
+
#
|
599
|
+
# <%= sms_to "5155555785" do %>
|
600
|
+
# <strong>Text me:</strong>
|
601
|
+
# <% end %>
|
602
|
+
# # => <a href="sms:5155555785;">
|
603
|
+
# <strong>Text me:</strong>
|
604
|
+
# </a>
|
605
|
+
def sms_to(phone_number, name = nil, html_options = {}, &block)
|
606
|
+
html_options, name = name, nil if block_given?
|
607
|
+
html_options = (html_options || {}).stringify_keys
|
608
|
+
|
609
|
+
extras = %w{ body }.map! { |item|
|
610
|
+
option = html_options.delete(item).presence || next
|
611
|
+
"#{item.dasherize}=#{ERB::Util.url_encode(option)}"
|
612
|
+
}.compact
|
613
|
+
extras = extras.empty? ? "" : "?&" + extras.join("&")
|
614
|
+
|
615
|
+
encoded_phone_number = ERB::Util.url_encode(phone_number)
|
616
|
+
html_options["href"] = "sms:#{encoded_phone_number};#{extras}"
|
617
|
+
|
618
|
+
content_tag("a", name || phone_number, html_options, &block)
|
619
|
+
end
|
620
|
+
|
621
|
+
# Creates a TEL anchor link tag to the specified +phone_number+, which is
|
622
|
+
# also used as the name of the link unless +name+ is specified. Additional
|
623
|
+
# HTML attributes for the link can be passed in +html_options+.
|
624
|
+
#
|
625
|
+
# When clicked, the default app to make calls is opened, and it
|
626
|
+
# is prepopulated with the passed phone number and optional
|
627
|
+
# +country_code+ value.
|
628
|
+
#
|
629
|
+
# +phone_to+ has an optional +country_code+ option which automatically adds the country
|
630
|
+
# code as well as the + sign in the phone numer that gets prepopulated,
|
631
|
+
# for example if +country_code: "01"+ +\+01+ will be prepended to the
|
632
|
+
# phone numer, by passing special keys to +html_options+.
|
633
|
+
#
|
634
|
+
# ==== Options
|
635
|
+
# * <tt>:country_code</tt> - Prepends the country code to the number
|
636
|
+
#
|
637
|
+
# ==== Examples
|
638
|
+
# phone_to "1234567890"
|
639
|
+
# # => <a href="tel:1234567890">1234567890</a>
|
640
|
+
#
|
641
|
+
# phone_to "1234567890", "Phone me"
|
642
|
+
# # => <a href="tel:134567890">Phone me</a>
|
643
|
+
#
|
644
|
+
# phone_to "1234567890", "Phone me", country_code: "01"
|
645
|
+
# # => <a href="tel:+015155555785">Phone me</a>
|
646
|
+
#
|
647
|
+
# You can use a block as well if your link target is hard to fit into the name parameter. \ERB example:
|
648
|
+
#
|
649
|
+
# <%= phone_to "1234567890" do %>
|
650
|
+
# <strong>Phone me:</strong>
|
651
|
+
# <% end %>
|
652
|
+
# # => <a href="tel:1234567890">
|
653
|
+
# <strong>Phone me:</strong>
|
654
|
+
# </a>
|
655
|
+
def phone_to(phone_number, name = nil, html_options = {}, &block)
|
656
|
+
html_options, name = name, nil if block_given?
|
657
|
+
html_options = (html_options || {}).stringify_keys
|
658
|
+
|
659
|
+
country_code = html_options.delete("country_code").presence
|
660
|
+
country_code = country_code.nil? ? "" : "+#{ERB::Util.url_encode(country_code)}"
|
661
|
+
|
662
|
+
encoded_phone_number = ERB::Util.url_encode(phone_number)
|
663
|
+
html_options["href"] = "tel:#{country_code}#{encoded_phone_number}"
|
664
|
+
|
665
|
+
content_tag("a", name || phone_number, html_options, &block)
|
666
|
+
end
|
667
|
+
|
574
668
|
private
|
575
669
|
def convert_options_to_data_attributes(options, html_options)
|
576
670
|
if html_options
|
577
671
|
html_options = html_options.stringify_keys
|
578
|
-
html_options["data-remote"] = "true"
|
672
|
+
html_options["data-remote"] = "true" if link_to_remote_options?(options) || link_to_remote_options?(html_options)
|
579
673
|
|
580
|
-
method = html_options.delete("method"
|
674
|
+
method = html_options.delete("method")
|
581
675
|
|
582
676
|
add_method_to_attributes!(html_options, method) if method
|
583
677
|
|
584
678
|
html_options
|
585
679
|
else
|
586
|
-
link_to_remote_options?(options) ? { "data-remote" => "true"
|
680
|
+
link_to_remote_options?(options) ? { "data-remote" => "true" } : {}
|
587
681
|
end
|
588
682
|
end
|
589
683
|
|
590
684
|
def link_to_remote_options?(options)
|
591
685
|
if options.is_a?(Hash)
|
592
|
-
options.delete("remote"
|
686
|
+
options.delete("remote") || options.delete(:remote)
|
593
687
|
end
|
594
688
|
end
|
595
689
|
|
596
690
|
def add_method_to_attributes!(html_options, method)
|
597
|
-
if method_not_get_method?(method) && html_options["rel"]
|
691
|
+
if method_not_get_method?(method) && !html_options["rel"]&.match?(/nofollow/)
|
598
692
|
if html_options["rel"].blank?
|
599
693
|
html_options["rel"] = "nofollow"
|
600
694
|
else
|
@@ -618,11 +712,11 @@ module ActionView
|
|
618
712
|
end
|
619
713
|
|
620
714
|
def token_tag(token = nil, form_options: {})
|
621
|
-
if token != false && protect_against_forgery?
|
715
|
+
if token != false && defined?(protect_against_forgery?) && protect_against_forgery?
|
622
716
|
token ||= form_authenticity_token(form_options: form_options)
|
623
717
|
tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token)
|
624
718
|
else
|
625
|
-
""
|
719
|
+
""
|
626
720
|
end
|
627
721
|
end
|
628
722
|
|
@@ -636,7 +730,7 @@ module ActionView
|
|
636
730
|
# to_form_params(name: 'David', nationality: 'Danish')
|
637
731
|
# # => [{name: 'name', value: 'David'}, {name: 'nationality', value: 'Danish'}]
|
638
732
|
#
|
639
|
-
# to_form_params(country: {name: 'Denmark'})
|
733
|
+
# to_form_params(country: { name: 'Denmark' })
|
640
734
|
# # => [{name: 'country[name]', value: 'Denmark'}]
|
641
735
|
#
|
642
736
|
# to_form_params(countries: ['Denmark', 'Sweden']})
|