actionview 4.2.11.1 → 7.0.2.4
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.
Potentially problematic release.
This version of actionview might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +229 -215
- data/MIT-LICENSE +1 -1
- data/README.rdoc +9 -8
- data/lib/action_view/base.rb +116 -43
- data/lib/action_view/buffers.rb +20 -3
- data/lib/action_view/cache_expiry.rb +66 -0
- data/lib/action_view/context.rb +8 -12
- data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
- data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
- data/lib/action_view/dependency_tracker.rb +21 -122
- data/lib/action_view/digestor.rb +92 -85
- data/lib/action_view/flows.rb +15 -16
- data/lib/action_view/gem_version.rb +6 -4
- data/lib/action_view/helpers/active_model_helper.rb +17 -12
- data/lib/action_view/helpers/asset_tag_helper.rb +356 -101
- data/lib/action_view/helpers/asset_url_helper.rb +180 -74
- data/lib/action_view/helpers/atom_feed_helper.rb +21 -19
- data/lib/action_view/helpers/cache_helper.rb +156 -43
- data/lib/action_view/helpers/capture_helper.rb +21 -14
- data/lib/action_view/helpers/controller_helper.rb +16 -5
- data/lib/action_view/helpers/csp_helper.rb +26 -0
- data/lib/action_view/helpers/csrf_helper.rb +8 -6
- data/lib/action_view/helpers/date_helper.rb +288 -132
- data/lib/action_view/helpers/debug_helper.rb +9 -6
- data/lib/action_view/helpers/form_helper.rb +956 -173
- data/lib/action_view/helpers/form_options_helper.rb +178 -97
- data/lib/action_view/helpers/form_tag_helper.rb +220 -101
- data/lib/action_view/helpers/javascript_helper.rb +33 -19
- data/lib/action_view/helpers/number_helper.rb +88 -63
- data/lib/action_view/helpers/output_safety_helper.rb +38 -6
- data/lib/action_view/helpers/rendering_helper.rb +21 -10
- data/lib/action_view/helpers/sanitize_helper.rb +31 -32
- data/lib/action_view/helpers/tag_helper.rb +332 -71
- data/lib/action_view/helpers/tags/base.rb +123 -99
- data/lib/action_view/helpers/tags/check_box.rb +21 -20
- data/lib/action_view/helpers/tags/checkable.rb +4 -2
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -34
- data/lib/action_view/helpers/tags/collection_helpers.rb +69 -36
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -12
- data/lib/action_view/helpers/tags/collection_select.rb +5 -3
- data/lib/action_view/helpers/tags/color_field.rb +4 -3
- data/lib/action_view/helpers/tags/date_field.rb +3 -2
- data/lib/action_view/helpers/tags/date_select.rb +38 -37
- data/lib/action_view/helpers/tags/datetime_field.rb +4 -3
- data/lib/action_view/helpers/tags/datetime_local_field.rb +3 -2
- data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
- data/lib/action_view/helpers/tags/email_field.rb +2 -0
- data/lib/action_view/helpers/tags/file_field.rb +18 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
- data/lib/action_view/helpers/tags/hidden_field.rb +6 -0
- data/lib/action_view/helpers/tags/label.rb +7 -2
- data/lib/action_view/helpers/tags/month_field.rb +3 -2
- data/lib/action_view/helpers/tags/number_field.rb +2 -0
- data/lib/action_view/helpers/tags/password_field.rb +3 -1
- data/lib/action_view/helpers/tags/placeholderable.rb +3 -1
- data/lib/action_view/helpers/tags/radio_button.rb +7 -6
- data/lib/action_view/helpers/tags/range_field.rb +2 -0
- data/lib/action_view/helpers/tags/search_field.rb +14 -9
- data/lib/action_view/helpers/tags/select.rb +11 -10
- data/lib/action_view/helpers/tags/tel_field.rb +2 -0
- data/lib/action_view/helpers/tags/text_area.rb +4 -2
- data/lib/action_view/helpers/tags/text_field.rb +8 -8
- data/lib/action_view/helpers/tags/time_field.rb +12 -2
- data/lib/action_view/helpers/tags/time_select.rb +2 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
- data/lib/action_view/helpers/tags/translator.rb +15 -16
- data/lib/action_view/helpers/tags/url_field.rb +2 -0
- data/lib/action_view/helpers/tags/week_field.rb +3 -2
- data/lib/action_view/helpers/tags/weekday_select.rb +28 -0
- data/lib/action_view/helpers/tags.rb +5 -2
- data/lib/action_view/helpers/text_helper.rb +80 -51
- data/lib/action_view/helpers/translation_helper.rb +120 -69
- data/lib/action_view/helpers/url_helper.rb +398 -171
- data/lib/action_view/helpers.rb +29 -27
- data/lib/action_view/layouts.rb +68 -63
- data/lib/action_view/log_subscriber.rb +77 -10
- data/lib/action_view/lookup_context.rb +137 -113
- data/lib/action_view/model_naming.rb +4 -2
- data/lib/action_view/path_set.rb +28 -32
- data/lib/action_view/railtie.rb +74 -13
- data/lib/action_view/record_identifier.rb +53 -26
- data/lib/action_view/render_parser.rb +188 -0
- data/lib/action_view/renderer/abstract_renderer.rb +152 -15
- 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 +102 -0
- data/lib/action_view/renderer/partial_renderer.rb +51 -333
- data/lib/action_view/renderer/renderer.rb +68 -11
- data/lib/action_view/renderer/streaming_template_renderer.rb +60 -56
- data/lib/action_view/renderer/template_renderer.rb +87 -74
- data/lib/action_view/rendering.rb +73 -47
- data/lib/action_view/ripper_ast_parser.rb +198 -0
- data/lib/action_view/routing_url_for.rb +35 -24
- data/lib/action_view/tasks/cache_digests.rake +25 -0
- data/lib/action_view/template/error.rb +151 -41
- data/lib/action_view/template/handlers/builder.rb +12 -13
- data/lib/action_view/template/handlers/erb/erubi.rb +89 -0
- data/lib/action_view/template/handlers/erb.rb +29 -89
- data/lib/action_view/template/handlers/html.rb +11 -0
- data/lib/action_view/template/handlers/raw.rb +4 -4
- data/lib/action_view/template/handlers.rb +14 -10
- data/lib/action_view/template/html.rb +12 -13
- 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 +139 -300
- 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 +10 -12
- data/lib/action_view/template/types.rb +28 -26
- data/lib/action_view/template.rb +123 -91
- data/lib/action_view/template_details.rb +66 -0
- data/lib/action_view/template_path.rb +64 -0
- data/lib/action_view/test_case.rb +70 -53
- data/lib/action_view/testing/resolvers.rb +25 -35
- data/lib/action_view/unbound_template.rb +57 -0
- data/lib/action_view/version.rb +3 -1
- data/lib/action_view/view_paths.rb +73 -58
- data/lib/action_view.rb +16 -11
- data/lib/assets/compiled/rails-ujs.js +746 -0
- metadata +52 -32
- data/lib/action_view/helpers/record_tag_helper.rb +0 -108
- data/lib/action_view/tasks/dependencies.rake +0 -23
@@ -1,28 +1,28 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails-html-sanitizer"
|
4
4
|
|
5
5
|
module ActionView
|
6
6
|
# = Action View Sanitize Helpers
|
7
|
-
module Helpers
|
7
|
+
module Helpers # :nodoc:
|
8
8
|
# The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements.
|
9
9
|
# These helper methods extend Action View making them callable within your template files.
|
10
10
|
module SanitizeHelper
|
11
11
|
extend ActiveSupport::Concern
|
12
|
-
# Sanitizes HTML input, stripping all tags and attributes
|
12
|
+
# Sanitizes HTML input, stripping all but known-safe tags and attributes.
|
13
13
|
#
|
14
14
|
# It also strips href/src attributes with unsafe protocols like
|
15
15
|
# <tt>javascript:</tt>, while also protecting against attempts to use Unicode,
|
16
16
|
# ASCII, and hex character references to work around these protocol filters.
|
17
|
+
# All special characters will be escaped.
|
17
18
|
#
|
18
|
-
# The default sanitizer is Rails::Html::
|
19
|
+
# The default sanitizer is Rails::Html::SafeListSanitizer. See {Rails HTML
|
19
20
|
# Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information.
|
20
21
|
#
|
21
22
|
# Custom sanitization rules can also be provided.
|
22
23
|
#
|
23
24
|
# Please note that sanitizing user-provided text does not guarantee that the
|
24
|
-
# resulting markup is valid or even well-formed.
|
25
|
-
# contain unescaped characters like <tt><</tt>, <tt>></tt>, or <tt>&</tt>.
|
25
|
+
# resulting markup is valid or even well-formed.
|
26
26
|
#
|
27
27
|
# ==== Options
|
28
28
|
#
|
@@ -39,24 +39,22 @@ module ActionView
|
|
39
39
|
#
|
40
40
|
# <%= sanitize @comment.body %>
|
41
41
|
#
|
42
|
-
# Providing custom
|
42
|
+
# Providing custom lists of permitted tags and attributes:
|
43
43
|
#
|
44
44
|
# <%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %>
|
45
45
|
#
|
46
46
|
# Providing a custom Rails::Html scrubber:
|
47
47
|
#
|
48
48
|
# class CommentScrubber < Rails::Html::PermitScrubber
|
49
|
-
# def
|
50
|
-
#
|
49
|
+
# def initialize
|
50
|
+
# super
|
51
|
+
# self.tags = %w( form script comment blockquote )
|
52
|
+
# self.attributes = %w( style )
|
51
53
|
# end
|
52
54
|
#
|
53
55
|
# def skip_node?(node)
|
54
56
|
# node.text?
|
55
57
|
# end
|
56
|
-
#
|
57
|
-
# def scrub_attribute?(name)
|
58
|
-
# name == 'style'
|
59
|
-
# end
|
60
58
|
# end
|
61
59
|
#
|
62
60
|
# <%= sanitize @comment.body, scrubber: CommentScrubber.new %>
|
@@ -81,15 +79,15 @@ module ActionView
|
|
81
79
|
# config.action_view.sanitized_allowed_tags = ['strong', 'em', 'a']
|
82
80
|
# config.action_view.sanitized_allowed_attributes = ['href', 'title']
|
83
81
|
def sanitize(html, options = {})
|
84
|
-
self.class.
|
82
|
+
self.class.safe_list_sanitizer.sanitize(html, options)&.html_safe
|
85
83
|
end
|
86
84
|
|
87
85
|
# Sanitizes a block of CSS code. Used by +sanitize+ when it comes across a style attribute.
|
88
86
|
def sanitize_css(style)
|
89
|
-
self.class.
|
87
|
+
self.class.safe_list_sanitizer.sanitize_css(style)
|
90
88
|
end
|
91
89
|
|
92
|
-
# Strips all HTML tags from +html+, including comments.
|
90
|
+
# Strips all HTML tags from +html+, including comments and special characters.
|
93
91
|
#
|
94
92
|
# strip_tags("Strip <i>these</i> tags!")
|
95
93
|
# # => Strip these tags!
|
@@ -99,8 +97,11 @@ module ActionView
|
|
99
97
|
#
|
100
98
|
# strip_tags("<div id='top-bar'>Welcome to my website!</div>")
|
101
99
|
# # => Welcome to my website!
|
100
|
+
#
|
101
|
+
# strip_tags("> A quote from Smith & Wesson")
|
102
|
+
# # => > A quote from Smith & Wesson
|
102
103
|
def strip_tags(html)
|
103
|
-
self.class.full_sanitizer.sanitize(html
|
104
|
+
self.class.full_sanitizer.sanitize(html)
|
104
105
|
end
|
105
106
|
|
106
107
|
# Strips all link tags from +html+ leaving just the link text.
|
@@ -113,25 +114,26 @@ module ActionView
|
|
113
114
|
#
|
114
115
|
# strip_links('Blog: <a href="http://www.myblog.com/" class="nav" target=\"_blank\">Visit</a>.')
|
115
116
|
# # => Blog: Visit.
|
117
|
+
#
|
118
|
+
# strip_links('<<a href="https://example.org">malformed & link</a>')
|
119
|
+
# # => <malformed & link
|
116
120
|
def strip_links(html)
|
117
121
|
self.class.link_sanitizer.sanitize(html)
|
118
122
|
end
|
119
123
|
|
120
|
-
module ClassMethods
|
121
|
-
attr_writer :full_sanitizer, :link_sanitizer, :
|
124
|
+
module ClassMethods # :nodoc:
|
125
|
+
attr_writer :full_sanitizer, :link_sanitizer, :safe_list_sanitizer
|
122
126
|
|
123
|
-
# Vendors the full, link and white list sanitizers.
|
124
|
-
# Provided strictly for compabitility and can be removed in Rails 5.
|
125
127
|
def sanitizer_vendor
|
126
128
|
Rails::Html::Sanitizer
|
127
129
|
end
|
128
130
|
|
129
131
|
def sanitized_allowed_tags
|
130
|
-
sanitizer_vendor.
|
132
|
+
sanitizer_vendor.safe_list_sanitizer.allowed_tags
|
131
133
|
end
|
132
134
|
|
133
135
|
def sanitized_allowed_attributes
|
134
|
-
sanitizer_vendor.
|
136
|
+
sanitizer_vendor.safe_list_sanitizer.allowed_attributes
|
135
137
|
end
|
136
138
|
|
137
139
|
# Gets the Rails::Html::FullSanitizer instance used by +strip_tags+. Replace with
|
@@ -140,7 +142,6 @@ module ActionView
|
|
140
142
|
# class Application < Rails::Application
|
141
143
|
# config.action_view.full_sanitizer = MySpecialSanitizer.new
|
142
144
|
# end
|
143
|
-
#
|
144
145
|
def full_sanitizer
|
145
146
|
@full_sanitizer ||= sanitizer_vendor.full_sanitizer.new
|
146
147
|
end
|
@@ -151,20 +152,18 @@ module ActionView
|
|
151
152
|
# class Application < Rails::Application
|
152
153
|
# config.action_view.link_sanitizer = MySpecialSanitizer.new
|
153
154
|
# end
|
154
|
-
#
|
155
155
|
def link_sanitizer
|
156
156
|
@link_sanitizer ||= sanitizer_vendor.link_sanitizer.new
|
157
157
|
end
|
158
158
|
|
159
|
-
# Gets the Rails::Html::
|
159
|
+
# Gets the Rails::Html::SafeListSanitizer instance used by sanitize and +sanitize_css+.
|
160
160
|
# Replace with any object that responds to +sanitize+.
|
161
161
|
#
|
162
162
|
# class Application < Rails::Application
|
163
|
-
# config.action_view.
|
163
|
+
# config.action_view.safe_list_sanitizer = MySpecialSanitizer.new
|
164
164
|
# end
|
165
|
-
|
166
|
-
|
167
|
-
@white_list_sanitizer ||= sanitizer_vendor.white_list_sanitizer.new
|
165
|
+
def safe_list_sanitizer
|
166
|
+
@safe_list_sanitizer ||= sanitizer_vendor.safe_list_sanitizer.new
|
168
167
|
end
|
169
168
|
end
|
170
169
|
end
|
@@ -1,38 +1,299 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/enumerable"
|
4
|
+
require "active_support/core_ext/string/output_safety"
|
5
|
+
require "set"
|
6
|
+
require "action_view/helpers/capture_helper"
|
7
|
+
require "action_view/helpers/output_safety_helper"
|
3
8
|
|
4
9
|
module ActionView
|
5
10
|
# = Action View Tag Helpers
|
6
|
-
module Helpers
|
7
|
-
# Provides methods to generate HTML tags programmatically
|
8
|
-
#
|
11
|
+
module Helpers # :nodoc:
|
12
|
+
# Provides methods to generate HTML tags programmatically both as a modern
|
13
|
+
# HTML5 compliant builder style and legacy XHTML compliant tags.
|
9
14
|
module TagHelper
|
10
|
-
extend ActiveSupport::Concern
|
11
15
|
include CaptureHelper
|
12
16
|
include OutputSafetyHelper
|
13
17
|
|
14
|
-
BOOLEAN_ATTRIBUTES = %w(
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
BOOLEAN_ATTRIBUTES = %w(allowfullscreen allowpaymentrequest async autofocus
|
19
|
+
autoplay checked compact controls declare default
|
20
|
+
defaultchecked defaultmuted defaultselected defer
|
21
|
+
disabled enabled formnovalidate hidden indeterminate
|
22
|
+
inert ismap itemscope loop multiple muted nohref
|
23
|
+
nomodule noresize noshade novalidate nowrap open
|
24
|
+
pauseonexit playsinline readonly required reversed
|
25
|
+
scoped seamless selected sortable truespeed
|
26
|
+
typemustmatch visible).to_set
|
27
|
+
|
28
|
+
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
|
29
|
+
BOOLEAN_ATTRIBUTES.freeze
|
30
|
+
|
31
|
+
ARIA_PREFIXES = ["aria", :aria].to_set.freeze
|
32
|
+
DATA_PREFIXES = ["data", :data].to_set.freeze
|
33
|
+
|
34
|
+
TAG_TYPES = {}
|
35
|
+
TAG_TYPES.merge! BOOLEAN_ATTRIBUTES.index_with(:boolean)
|
36
|
+
TAG_TYPES.merge! DATA_PREFIXES.index_with(:data)
|
37
|
+
TAG_TYPES.merge! ARIA_PREFIXES.index_with(:aria)
|
38
|
+
TAG_TYPES.freeze
|
39
|
+
|
40
|
+
PRE_CONTENT_STRINGS = Hash.new { "" }
|
41
|
+
PRE_CONTENT_STRINGS[:textarea] = "\n"
|
42
|
+
PRE_CONTENT_STRINGS["textarea"] = "\n"
|
43
|
+
|
44
|
+
class TagBuilder # :nodoc:
|
45
|
+
include CaptureHelper
|
46
|
+
include OutputSafetyHelper
|
47
|
+
|
48
|
+
HTML_VOID_ELEMENTS = %i(area base br col circle embed hr img input keygen link meta param source track wbr).to_set
|
49
|
+
SVG_VOID_ELEMENTS = %i(animate animateMotion animateTransform circle ellipse line path polygon polyline rect set stop use view).to_set
|
50
|
+
|
51
|
+
def initialize(view_context)
|
52
|
+
@view_context = view_context
|
53
|
+
end
|
54
|
+
|
55
|
+
# Transforms a Hash into HTML Attributes, ready to be interpolated into
|
56
|
+
# ERB.
|
57
|
+
#
|
58
|
+
# <input <%= tag.attributes(type: :text, aria: { label: "Search" }) %> >
|
59
|
+
# # => <input type="text" aria-label="Search">
|
60
|
+
def attributes(attributes)
|
61
|
+
tag_options(attributes.to_h).to_s.strip.html_safe
|
62
|
+
end
|
63
|
+
|
64
|
+
def p(*arguments, **options, &block)
|
65
|
+
tag_string(:p, *arguments, **options, &block)
|
66
|
+
end
|
67
|
+
|
68
|
+
def tag_string(name, content = nil, **options, &block)
|
69
|
+
escape = handle_deprecated_escape_options(options)
|
70
|
+
|
71
|
+
content = @view_context.capture(self, &block) if block_given?
|
72
|
+
if (HTML_VOID_ELEMENTS.include?(name) || SVG_VOID_ELEMENTS.include?(name)) && content.nil?
|
73
|
+
"<#{name.to_s.dasherize}#{tag_options(options, escape)}>".html_safe
|
74
|
+
else
|
75
|
+
content_tag_string(name.to_s.dasherize, content || "", options, escape)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def content_tag_string(name, content, options, escape = true)
|
80
|
+
tag_options = tag_options(options, escape) if options
|
81
|
+
|
82
|
+
if escape
|
83
|
+
name = ERB::Util.xml_name_escape(name)
|
84
|
+
content = ERB::Util.unwrapped_html_escape(content)
|
85
|
+
end
|
86
|
+
|
87
|
+
"<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
|
88
|
+
end
|
89
|
+
|
90
|
+
def tag_options(options, escape = true)
|
91
|
+
return if options.blank?
|
92
|
+
output = +""
|
93
|
+
sep = " "
|
94
|
+
options.each_pair do |key, value|
|
95
|
+
type = TAG_TYPES[key]
|
96
|
+
if type == :data && value.is_a?(Hash)
|
97
|
+
value.each_pair do |k, v|
|
98
|
+
next if v.nil?
|
99
|
+
output << sep
|
100
|
+
output << prefix_tag_option(key, k, v, escape)
|
101
|
+
end
|
102
|
+
elsif type == :aria && value.is_a?(Hash)
|
103
|
+
value.each_pair do |k, v|
|
104
|
+
next if v.nil?
|
105
|
+
|
106
|
+
case v
|
107
|
+
when Array, Hash
|
108
|
+
tokens = TagHelper.build_tag_values(v)
|
109
|
+
next if tokens.none?
|
110
|
+
|
111
|
+
v = safe_join(tokens, " ")
|
112
|
+
else
|
113
|
+
v = v.to_s
|
114
|
+
end
|
115
|
+
|
116
|
+
output << sep
|
117
|
+
output << prefix_tag_option(key, k, v, escape)
|
118
|
+
end
|
119
|
+
elsif type == :boolean
|
120
|
+
if value
|
121
|
+
output << sep
|
122
|
+
output << boolean_tag_option(key)
|
123
|
+
end
|
124
|
+
elsif !value.nil?
|
125
|
+
output << sep
|
126
|
+
output << tag_option(key, value, escape)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
output unless output.empty?
|
130
|
+
end
|
131
|
+
|
132
|
+
def boolean_tag_option(key)
|
133
|
+
%(#{key}="#{key}")
|
134
|
+
end
|
20
135
|
|
21
|
-
|
136
|
+
def tag_option(key, value, escape)
|
137
|
+
key = ERB::Util.xml_name_escape(key) if escape
|
22
138
|
|
23
|
-
|
139
|
+
case value
|
140
|
+
when Array, Hash
|
141
|
+
value = TagHelper.build_tag_values(value) if key.to_s == "class"
|
142
|
+
value = escape ? safe_join(value, " ") : value.join(" ")
|
143
|
+
when Regexp
|
144
|
+
value = escape ? ERB::Util.unwrapped_html_escape(value.source) : value.source
|
145
|
+
else
|
146
|
+
value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
|
147
|
+
end
|
148
|
+
value = value.gsub('"', """) if value.include?('"')
|
24
149
|
|
25
|
-
|
26
|
-
|
27
|
-
}
|
150
|
+
%(#{key}="#{value}")
|
151
|
+
end
|
28
152
|
|
29
|
-
|
153
|
+
private
|
154
|
+
def prefix_tag_option(prefix, key, value, escape)
|
155
|
+
key = "#{prefix}-#{key.to_s.dasherize}"
|
156
|
+
unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
|
157
|
+
value = value.to_json
|
158
|
+
end
|
159
|
+
tag_option(key, value, escape)
|
160
|
+
end
|
161
|
+
|
162
|
+
def respond_to_missing?(*args)
|
163
|
+
true
|
164
|
+
end
|
165
|
+
|
166
|
+
def handle_deprecated_escape_options(options)
|
167
|
+
# The option :escape_attributes has been merged into the options hash to be
|
168
|
+
# able to warn when it is used, so we need to handle default values here.
|
169
|
+
escape_option_provided = options.has_key?(:escape)
|
170
|
+
escape_attributes_option_provided = options.has_key?(:escape_attributes)
|
171
|
+
|
172
|
+
if escape_attributes_option_provided
|
173
|
+
ActiveSupport::Deprecation.warn(<<~MSG)
|
174
|
+
Use of the option :escape_attributes is deprecated. It currently \
|
175
|
+
escapes both names and values of tags and attributes and it is \
|
176
|
+
equivalent to :escape. If any of them are enabled, the escaping \
|
177
|
+
is fully enabled.
|
178
|
+
MSG
|
179
|
+
end
|
180
|
+
|
181
|
+
return true unless escape_option_provided || escape_attributes_option_provided
|
182
|
+
escape_option = options.delete(:escape)
|
183
|
+
escape_attributes_option = options.delete(:escape_attributes)
|
184
|
+
escape_option || escape_attributes_option
|
185
|
+
end
|
186
|
+
|
187
|
+
def method_missing(called, *args, **options, &block)
|
188
|
+
tag_string(called, *args, **options, &block)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Returns an HTML tag.
|
193
|
+
#
|
194
|
+
# === Building HTML tags
|
195
|
+
#
|
196
|
+
# Builds HTML5 compliant tags with a tag proxy. Every tag can be built with:
|
197
|
+
#
|
198
|
+
# tag.<tag name>(optional content, options)
|
199
|
+
#
|
200
|
+
# where tag name can be e.g. br, div, section, article, or any tag really.
|
201
|
+
#
|
202
|
+
# ==== Passing content
|
203
|
+
#
|
204
|
+
# Tags can pass content to embed within it:
|
205
|
+
#
|
206
|
+
# tag.h1 'All titles fit to print' # => <h1>All titles fit to print</h1>
|
207
|
+
#
|
208
|
+
# tag.div tag.p('Hello world!') # => <div><p>Hello world!</p></div>
|
209
|
+
#
|
210
|
+
# Content can also be captured with a block, which is useful in templates:
|
211
|
+
#
|
212
|
+
# <%= tag.p do %>
|
213
|
+
# The next great American novel starts here.
|
214
|
+
# <% end %>
|
215
|
+
# # => <p>The next great American novel starts here.</p>
|
216
|
+
#
|
217
|
+
# ==== Options
|
218
|
+
#
|
219
|
+
# Use symbol keyed options to add attributes to the generated tag.
|
220
|
+
#
|
221
|
+
# tag.section class: %w( kitties puppies )
|
222
|
+
# # => <section class="kitties puppies"></section>
|
223
|
+
#
|
224
|
+
# tag.section id: dom_id(@post)
|
225
|
+
# # => <section id="<generated dom id>"></section>
|
226
|
+
#
|
227
|
+
# Pass +true+ for any attributes that can render with no values, like +disabled+ and +readonly+.
|
228
|
+
#
|
229
|
+
# tag.input type: 'text', disabled: true
|
230
|
+
# # => <input type="text" disabled="disabled">
|
231
|
+
#
|
232
|
+
# HTML5 <tt>data-*</tt> and <tt>aria-*</tt> attributes can be set with a
|
233
|
+
# single +data+ or +aria+ key pointing to a hash of sub-attributes.
|
234
|
+
#
|
235
|
+
# To play nicely with JavaScript conventions, sub-attributes are dasherized.
|
236
|
+
#
|
237
|
+
# tag.article data: { user_id: 123 }
|
238
|
+
# # => <article data-user-id="123"></article>
|
239
|
+
#
|
240
|
+
# Thus <tt>data-user-id</tt> can be accessed as <tt>dataset.userId</tt>.
|
241
|
+
#
|
242
|
+
# Data attribute values are encoded to JSON, with the exception of strings, symbols and
|
243
|
+
# BigDecimals.
|
244
|
+
# This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt>
|
245
|
+
# from 1.4.3.
|
246
|
+
#
|
247
|
+
# tag.div data: { city_state: %w( Chicago IL ) }
|
248
|
+
# # => <div data-city-state="["Chicago","IL"]"></div>
|
249
|
+
#
|
250
|
+
# The generated tag names and attributes are escaped by default. This can be disabled using
|
251
|
+
# +escape+.
|
252
|
+
#
|
253
|
+
# tag.img src: 'open & shut.png'
|
254
|
+
# # => <img src="open & shut.png">
|
255
|
+
#
|
256
|
+
# tag.img src: 'open & shut.png', escape: false
|
257
|
+
# # => <img src="open & shut.png">
|
258
|
+
#
|
259
|
+
# The tag builder respects
|
260
|
+
# {HTML5 void elements}[https://www.w3.org/TR/html5/syntax.html#void-elements]
|
261
|
+
# if no content is passed, and omits closing tags for those elements.
|
262
|
+
#
|
263
|
+
# # A standard element:
|
264
|
+
# tag.div # => <div></div>
|
265
|
+
#
|
266
|
+
# # A void element:
|
267
|
+
# tag.br # => <br>
|
268
|
+
#
|
269
|
+
# === Building HTML attributes
|
270
|
+
#
|
271
|
+
# Transforms a Hash into HTML attributes, ready to be interpolated into
|
272
|
+
# ERB. Includes or omits boolean attributes based on their truthiness.
|
273
|
+
# Transforms keys nested within
|
274
|
+
# <tt>aria:</tt> or <tt>data:</tt> objects into `aria-` and `data-`
|
275
|
+
# prefixed attributes:
|
276
|
+
#
|
277
|
+
# <input <%= tag.attributes(type: :text, aria: { label: "Search" }) %>>
|
278
|
+
# # => <input type="text" aria-label="Search">
|
279
|
+
#
|
280
|
+
# <button <%= tag.attributes id: "call-to-action", disabled: false, aria: { expanded: false } %> class="primary">Get Started!</button>
|
281
|
+
# # => <button id="call-to-action" aria-expanded="false" class="primary">Get Started!</button>
|
282
|
+
#
|
283
|
+
# === Legacy syntax
|
284
|
+
#
|
285
|
+
# The following format is for legacy syntax support. It will be deprecated in future versions of Rails.
|
286
|
+
#
|
287
|
+
# tag(name, options = nil, open = false, escape = true)
|
288
|
+
#
|
289
|
+
# It returns an empty HTML tag of type +name+ which by default is XHTML
|
30
290
|
# compliant. Set +open+ to true to create an open tag compatible
|
31
291
|
# with HTML 4.0 and below. Add HTML attributes by passing an attributes
|
32
292
|
# hash to +options+. Set +escape+ to false to disable attribute value
|
33
293
|
# escaping.
|
34
294
|
#
|
35
295
|
# ==== Options
|
296
|
+
#
|
36
297
|
# You can use symbols or strings for the attribute names.
|
37
298
|
#
|
38
299
|
# Use +true+ with boolean attributes that can render with no value, like
|
@@ -41,16 +302,8 @@ module ActionView
|
|
41
302
|
# HTML5 <tt>data-*</tt> attributes can be set with a single +data+ key
|
42
303
|
# pointing to a hash of sub-attributes.
|
43
304
|
#
|
44
|
-
# To play nicely with JavaScript conventions sub-attributes are dasherized.
|
45
|
-
# For example, a key +user_id+ would render as <tt>data-user-id</tt> and
|
46
|
-
# thus accessed as <tt>dataset.userId</tt>.
|
47
|
-
#
|
48
|
-
# Values are encoded to JSON, with the exception of strings, symbols and
|
49
|
-
# BigDecimals.
|
50
|
-
# This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt>
|
51
|
-
# from 1.4.3.
|
52
|
-
#
|
53
305
|
# ==== Examples
|
306
|
+
#
|
54
307
|
# tag("br")
|
55
308
|
# # => <br />
|
56
309
|
#
|
@@ -66,20 +319,29 @@ module ActionView
|
|
66
319
|
# tag("img", src: "open & shut.png")
|
67
320
|
# # => <img src="open & shut.png" />
|
68
321
|
#
|
69
|
-
# tag("img", {src: "open & shut.png"}, false, false)
|
322
|
+
# tag("img", { src: "open & shut.png" }, false, false)
|
70
323
|
# # => <img src="open & shut.png" />
|
71
324
|
#
|
72
|
-
# tag("div", data: {name: 'Stephen', city_state: %w(Chicago IL)})
|
325
|
+
# tag("div", data: { name: 'Stephen', city_state: %w(Chicago IL) })
|
73
326
|
# # => <div data-name="Stephen" data-city-state="["Chicago","IL"]" />
|
74
|
-
|
75
|
-
|
327
|
+
#
|
328
|
+
# tag("div", class: { highlight: current_user.admin? })
|
329
|
+
# # => <div class="highlight" />
|
330
|
+
def tag(name = nil, options = nil, open = false, escape = true)
|
331
|
+
if name.nil?
|
332
|
+
tag_builder
|
333
|
+
else
|
334
|
+
name = ERB::Util.xml_name_escape(name) if escape
|
335
|
+
"<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
|
336
|
+
end
|
76
337
|
end
|
77
338
|
|
78
339
|
# Returns an HTML block tag of type +name+ surrounding the +content+. Add
|
79
340
|
# HTML attributes by passing an attributes hash to +options+.
|
80
341
|
# Instead of passing the content as an argument, you can also use a block
|
81
342
|
# in which case, you pass your +options+ as the second parameter.
|
82
|
-
# Set escape to false to disable
|
343
|
+
# Set escape to false to disable escaping.
|
344
|
+
# Note: this is legacy syntax, see +tag+ method description for details.
|
83
345
|
#
|
84
346
|
# ==== Options
|
85
347
|
# The +options+ hash can be used with attributes with no value like (<tt>disabled</tt> and
|
@@ -93,6 +355,8 @@ module ActionView
|
|
93
355
|
# # => <div class="strong"><p>Hello world!</p></div>
|
94
356
|
# content_tag(:div, "Hello world!", class: ["strong", "highlight"])
|
95
357
|
# # => <div class="strong highlight">Hello world!</div>
|
358
|
+
# content_tag(:div, "Hello world!", class: ["strong", { highlight: current_user.admin? }])
|
359
|
+
# # => <div class="strong highlight">Hello world!</div>
|
96
360
|
# content_tag("select", options, multiple: true)
|
97
361
|
# # => <select multiple="multiple">...options...</select>
|
98
362
|
#
|
@@ -103,12 +367,30 @@ module ActionView
|
|
103
367
|
def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
|
104
368
|
if block_given?
|
105
369
|
options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
|
106
|
-
content_tag_string(name, capture(&block), options, escape)
|
370
|
+
tag_builder.content_tag_string(name, capture(&block), options, escape)
|
107
371
|
else
|
108
|
-
content_tag_string(name, content_or_options_with_block, options, escape)
|
372
|
+
tag_builder.content_tag_string(name, content_or_options_with_block, options, escape)
|
109
373
|
end
|
110
374
|
end
|
111
375
|
|
376
|
+
# Returns a string of tokens built from +args+.
|
377
|
+
#
|
378
|
+
# ==== Examples
|
379
|
+
# token_list("foo", "bar")
|
380
|
+
# # => "foo bar"
|
381
|
+
# token_list("foo", "foo bar")
|
382
|
+
# # => "foo bar"
|
383
|
+
# token_list({ foo: true, bar: false })
|
384
|
+
# # => "foo"
|
385
|
+
# token_list(nil, false, 123, "", "foo", { bar: true })
|
386
|
+
# # => "123 foo bar"
|
387
|
+
def token_list(*args)
|
388
|
+
tokens = build_tag_values(*args).flat_map { |value| value.to_s.split(/\s+/) }.uniq
|
389
|
+
|
390
|
+
safe_join(tokens, " ")
|
391
|
+
end
|
392
|
+
alias_method :class_names, :token_list
|
393
|
+
|
112
394
|
# Returns a CDATA section with the given +content+. CDATA sections
|
113
395
|
# are used to escape blocks of text containing characters which would
|
114
396
|
# otherwise be recognized as markup. CDATA sections begin with the string
|
@@ -123,7 +405,7 @@ module ActionView
|
|
123
405
|
# cdata_section("hello]]>world")
|
124
406
|
# # => <![CDATA[hello]]]]><![CDATA[>world]]>
|
125
407
|
def cdata_section(content)
|
126
|
-
splitted = content.to_s.gsub(/\]\]
|
408
|
+
splitted = content.to_s.gsub(/\]\]>/, "]]]]><![CDATA[>")
|
127
409
|
"<![CDATA[#{splitted}]]>".html_safe
|
128
410
|
end
|
129
411
|
|
@@ -139,49 +421,28 @@ module ActionView
|
|
139
421
|
end
|
140
422
|
|
141
423
|
private
|
424
|
+
def build_tag_values(*args)
|
425
|
+
tag_values = []
|
142
426
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
def tag_options(options, escape = true)
|
150
|
-
return if options.blank?
|
151
|
-
attrs = []
|
152
|
-
options.each_pair do |key, value|
|
153
|
-
if TAG_PREFIXES.include?(key) && value.is_a?(Hash)
|
154
|
-
value.each_pair do |k, v|
|
155
|
-
attrs << prefix_tag_option(key, k, v, escape)
|
427
|
+
args.each do |tag_value|
|
428
|
+
case tag_value
|
429
|
+
when Hash
|
430
|
+
tag_value.each do |key, val|
|
431
|
+
tag_values << key.to_s if val && key.present?
|
156
432
|
end
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
433
|
+
when Array
|
434
|
+
tag_values.concat build_tag_values(*tag_value)
|
435
|
+
else
|
436
|
+
tag_values << tag_value.to_s if tag_value.present?
|
161
437
|
end
|
162
438
|
end
|
163
|
-
" #{attrs * ' '}" unless attrs.empty?
|
164
|
-
end
|
165
439
|
|
166
|
-
|
167
|
-
key = "#{prefix}-#{key.to_s.dasherize}"
|
168
|
-
unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
|
169
|
-
value = value.to_json
|
170
|
-
end
|
171
|
-
tag_option(key, value, escape)
|
440
|
+
tag_values
|
172
441
|
end
|
442
|
+
module_function :build_tag_values
|
173
443
|
|
174
|
-
def
|
175
|
-
|
176
|
-
end
|
177
|
-
|
178
|
-
def tag_option(key, value, escape)
|
179
|
-
if value.is_a?(Array)
|
180
|
-
value = escape ? safe_join(value, " ") : value.join(" ")
|
181
|
-
else
|
182
|
-
value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
|
183
|
-
end
|
184
|
-
%(#{key}="#{value.gsub('"'.freeze, '"'.freeze)}")
|
444
|
+
def tag_builder
|
445
|
+
@tag_builder ||= TagBuilder.new(self)
|
185
446
|
end
|
186
447
|
end
|
187
448
|
end
|