actionview 6.0.0
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 +7 -0
- data/CHANGELOG.md +271 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +40 -0
- data/lib/action_view.rb +98 -0
- data/lib/action_view/base.rb +312 -0
- data/lib/action_view/buffers.rb +67 -0
- data/lib/action_view/cache_expiry.rb +54 -0
- data/lib/action_view/context.rb +32 -0
- data/lib/action_view/dependency_tracker.rb +175 -0
- data/lib/action_view/digestor.rb +126 -0
- data/lib/action_view/flows.rb +76 -0
- data/lib/action_view/gem_version.rb +17 -0
- data/lib/action_view/helpers.rb +66 -0
- data/lib/action_view/helpers/active_model_helper.rb +55 -0
- data/lib/action_view/helpers/asset_tag_helper.rb +488 -0
- data/lib/action_view/helpers/asset_url_helper.rb +470 -0
- data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
- data/lib/action_view/helpers/cache_helper.rb +271 -0
- data/lib/action_view/helpers/capture_helper.rb +216 -0
- data/lib/action_view/helpers/controller_helper.rb +36 -0
- data/lib/action_view/helpers/csp_helper.rb +26 -0
- data/lib/action_view/helpers/csrf_helper.rb +35 -0
- data/lib/action_view/helpers/date_helper.rb +1200 -0
- data/lib/action_view/helpers/debug_helper.rb +36 -0
- data/lib/action_view/helpers/form_helper.rb +2569 -0
- data/lib/action_view/helpers/form_options_helper.rb +896 -0
- data/lib/action_view/helpers/form_tag_helper.rb +920 -0
- data/lib/action_view/helpers/javascript_helper.rb +95 -0
- data/lib/action_view/helpers/number_helper.rb +456 -0
- data/lib/action_view/helpers/output_safety_helper.rb +70 -0
- data/lib/action_view/helpers/rendering_helper.rb +101 -0
- data/lib/action_view/helpers/sanitize_helper.rb +171 -0
- data/lib/action_view/helpers/tag_helper.rb +314 -0
- data/lib/action_view/helpers/tags.rb +44 -0
- data/lib/action_view/helpers/tags/base.rb +196 -0
- data/lib/action_view/helpers/tags/check_box.rb +66 -0
- data/lib/action_view/helpers/tags/checkable.rb +18 -0
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +36 -0
- data/lib/action_view/helpers/tags/collection_helpers.rb +119 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
- data/lib/action_view/helpers/tags/collection_select.rb +30 -0
- data/lib/action_view/helpers/tags/color_field.rb +27 -0
- data/lib/action_view/helpers/tags/date_field.rb +15 -0
- data/lib/action_view/helpers/tags/date_select.rb +74 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +32 -0
- data/lib/action_view/helpers/tags/datetime_local_field.rb +21 -0
- data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
- data/lib/action_view/helpers/tags/email_field.rb +10 -0
- data/lib/action_view/helpers/tags/file_field.rb +10 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +31 -0
- data/lib/action_view/helpers/tags/hidden_field.rb +10 -0
- data/lib/action_view/helpers/tags/label.rb +81 -0
- data/lib/action_view/helpers/tags/month_field.rb +15 -0
- data/lib/action_view/helpers/tags/number_field.rb +20 -0
- data/lib/action_view/helpers/tags/password_field.rb +14 -0
- data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
- data/lib/action_view/helpers/tags/radio_button.rb +33 -0
- data/lib/action_view/helpers/tags/range_field.rb +10 -0
- data/lib/action_view/helpers/tags/search_field.rb +27 -0
- data/lib/action_view/helpers/tags/select.rb +43 -0
- data/lib/action_view/helpers/tags/tel_field.rb +10 -0
- data/lib/action_view/helpers/tags/text_area.rb +24 -0
- data/lib/action_view/helpers/tags/text_field.rb +34 -0
- data/lib/action_view/helpers/tags/time_field.rb +15 -0
- data/lib/action_view/helpers/tags/time_select.rb +10 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +22 -0
- data/lib/action_view/helpers/tags/translator.rb +39 -0
- data/lib/action_view/helpers/tags/url_field.rb +10 -0
- data/lib/action_view/helpers/tags/week_field.rb +15 -0
- data/lib/action_view/helpers/text_helper.rb +486 -0
- data/lib/action_view/helpers/translation_helper.rb +145 -0
- data/lib/action_view/helpers/url_helper.rb +676 -0
- data/lib/action_view/layouts.rb +433 -0
- data/lib/action_view/locale/en.yml +56 -0
- data/lib/action_view/log_subscriber.rb +96 -0
- data/lib/action_view/lookup_context.rb +316 -0
- data/lib/action_view/model_naming.rb +14 -0
- data/lib/action_view/path_set.rb +95 -0
- data/lib/action_view/railtie.rb +105 -0
- data/lib/action_view/record_identifier.rb +112 -0
- data/lib/action_view/renderer/abstract_renderer.rb +108 -0
- data/lib/action_view/renderer/partial_renderer.rb +563 -0
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +103 -0
- data/lib/action_view/renderer/renderer.rb +68 -0
- data/lib/action_view/renderer/streaming_template_renderer.rb +105 -0
- data/lib/action_view/renderer/template_renderer.rb +108 -0
- data/lib/action_view/rendering.rb +171 -0
- data/lib/action_view/routing_url_for.rb +146 -0
- data/lib/action_view/tasks/cache_digests.rake +25 -0
- data/lib/action_view/template.rb +393 -0
- data/lib/action_view/template/error.rb +161 -0
- data/lib/action_view/template/handlers.rb +92 -0
- data/lib/action_view/template/handlers/builder.rb +25 -0
- data/lib/action_view/template/handlers/erb.rb +84 -0
- data/lib/action_view/template/handlers/erb/erubi.rb +87 -0
- data/lib/action_view/template/handlers/html.rb +11 -0
- data/lib/action_view/template/handlers/raw.rb +11 -0
- data/lib/action_view/template/html.rb +43 -0
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +28 -0
- data/lib/action_view/template/resolver.rb +394 -0
- 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 +35 -0
- data/lib/action_view/template/types.rb +57 -0
- data/lib/action_view/test_case.rb +300 -0
- data/lib/action_view/testing/resolvers.rb +67 -0
- data/lib/action_view/unbound_template.rb +32 -0
- data/lib/action_view/version.rb +10 -0
- data/lib/action_view/view_paths.rb +129 -0
- data/lib/assets/compiled/rails-ujs.js +746 -0
- metadata +260 -0
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/string/output_safety"
|
4
|
+
|
5
|
+
module ActionView #:nodoc:
|
6
|
+
# = Action View Raw Output Helper
|
7
|
+
module Helpers #:nodoc:
|
8
|
+
module OutputSafetyHelper
|
9
|
+
# This method outputs without escaping a string. Since escaping tags is
|
10
|
+
# now default, this can be used when you don't want Rails to automatically
|
11
|
+
# escape tags. This is not recommended if the data is coming from the user's
|
12
|
+
# input.
|
13
|
+
#
|
14
|
+
# For example:
|
15
|
+
#
|
16
|
+
# raw @user.name
|
17
|
+
# # => 'Jimmy <alert>Tables</alert>'
|
18
|
+
def raw(stringish)
|
19
|
+
stringish.to_s.html_safe
|
20
|
+
end
|
21
|
+
|
22
|
+
# This method returns an HTML safe string similar to what <tt>Array#join</tt>
|
23
|
+
# would return. The array is flattened, and all items, including
|
24
|
+
# the supplied separator, are HTML escaped unless they are HTML
|
25
|
+
# safe, and the returned string is marked as HTML safe.
|
26
|
+
#
|
27
|
+
# safe_join([raw("<p>foo</p>"), "<p>bar</p>"], "<br />")
|
28
|
+
# # => "<p>foo</p><br /><p>bar</p>"
|
29
|
+
#
|
30
|
+
# safe_join([raw("<p>foo</p>"), raw("<p>bar</p>")], raw("<br />"))
|
31
|
+
# # => "<p>foo</p><br /><p>bar</p>"
|
32
|
+
#
|
33
|
+
def safe_join(array, sep = $,)
|
34
|
+
sep = ERB::Util.unwrapped_html_escape(sep)
|
35
|
+
|
36
|
+
array.flatten.map! { |i| ERB::Util.unwrapped_html_escape(i) }.join(sep).html_safe
|
37
|
+
end
|
38
|
+
|
39
|
+
# Converts the array to a comma-separated sentence where the last element is
|
40
|
+
# joined by the connector word. This is the html_safe-aware version of
|
41
|
+
# ActiveSupport's {Array#to_sentence}[https://api.rubyonrails.org/classes/Array.html#method-i-to_sentence].
|
42
|
+
#
|
43
|
+
def to_sentence(array, options = {})
|
44
|
+
options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
|
45
|
+
|
46
|
+
default_connectors = {
|
47
|
+
words_connector: ", ",
|
48
|
+
two_words_connector: " and ",
|
49
|
+
last_word_connector: ", and "
|
50
|
+
}
|
51
|
+
if defined?(I18n)
|
52
|
+
i18n_connectors = I18n.translate(:'support.array', locale: options[:locale], default: {})
|
53
|
+
default_connectors.merge!(i18n_connectors)
|
54
|
+
end
|
55
|
+
options = default_connectors.merge!(options)
|
56
|
+
|
57
|
+
case array.length
|
58
|
+
when 0
|
59
|
+
"".html_safe
|
60
|
+
when 1
|
61
|
+
ERB::Util.html_escape(array[0])
|
62
|
+
when 2
|
63
|
+
safe_join([array[0], array[1]], options[:two_words_connector])
|
64
|
+
else
|
65
|
+
safe_join([safe_join(array[0...-1], options[:words_connector]), options[:last_word_connector], array[-1]], nil)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionView
|
4
|
+
module Helpers #:nodoc:
|
5
|
+
# = Action View Rendering
|
6
|
+
#
|
7
|
+
# Implements methods that allow rendering from a view context.
|
8
|
+
# In order to use this module, all you need is to implement
|
9
|
+
# view_renderer that returns an ActionView::Renderer object.
|
10
|
+
module RenderingHelper
|
11
|
+
# Returns the result of a render that's dictated by the options hash. The primary options are:
|
12
|
+
#
|
13
|
+
# * <tt>:partial</tt> - See <tt>ActionView::PartialRenderer</tt>.
|
14
|
+
# * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
|
15
|
+
# * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
|
16
|
+
# * <tt>:plain</tt> - Renders the text passed in out. Setting the content
|
17
|
+
# type as <tt>text/plain</tt>.
|
18
|
+
# * <tt>:html</tt> - Renders the HTML safe string passed in out, otherwise
|
19
|
+
# performs HTML escape on the string first. Setting the content type as
|
20
|
+
# <tt>text/html</tt>.
|
21
|
+
# * <tt>:body</tt> - Renders the text passed in, and inherits the content
|
22
|
+
# type of <tt>text/plain</tt> from <tt>ActionDispatch::Response</tt>
|
23
|
+
# object.
|
24
|
+
#
|
25
|
+
# If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
|
26
|
+
# as the locals hash.
|
27
|
+
def render(options = {}, locals = {}, &block)
|
28
|
+
case options
|
29
|
+
when Hash
|
30
|
+
in_rendering_context(options) do |renderer|
|
31
|
+
if block_given?
|
32
|
+
view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block)
|
33
|
+
else
|
34
|
+
view_renderer.render(self, options)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
else
|
38
|
+
view_renderer.render_partial(self, partial: options, locals: locals, &block)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Overwrites _layout_for in the context object so it supports the case a block is
|
43
|
+
# passed to a partial. Returns the contents that are yielded to a layout, given a
|
44
|
+
# name or a block.
|
45
|
+
#
|
46
|
+
# You can think of a layout as a method that is called with a block. If the user calls
|
47
|
+
# <tt>yield :some_name</tt>, the block, by default, returns <tt>content_for(:some_name)</tt>.
|
48
|
+
# If the user calls simply +yield+, the default block returns <tt>content_for(:layout)</tt>.
|
49
|
+
#
|
50
|
+
# The user can override this default by passing a block to the layout:
|
51
|
+
#
|
52
|
+
# # The template
|
53
|
+
# <%= render layout: "my_layout" do %>
|
54
|
+
# Content
|
55
|
+
# <% end %>
|
56
|
+
#
|
57
|
+
# # The layout
|
58
|
+
# <html>
|
59
|
+
# <%= yield %>
|
60
|
+
# </html>
|
61
|
+
#
|
62
|
+
# In this case, instead of the default block, which would return <tt>content_for(:layout)</tt>,
|
63
|
+
# this method returns the block that was passed in to <tt>render :layout</tt>, and the response
|
64
|
+
# would be
|
65
|
+
#
|
66
|
+
# <html>
|
67
|
+
# Content
|
68
|
+
# </html>
|
69
|
+
#
|
70
|
+
# Finally, the block can take block arguments, which can be passed in by +yield+:
|
71
|
+
#
|
72
|
+
# # The template
|
73
|
+
# <%= render layout: "my_layout" do |customer| %>
|
74
|
+
# Hello <%= customer.name %>
|
75
|
+
# <% end %>
|
76
|
+
#
|
77
|
+
# # The layout
|
78
|
+
# <html>
|
79
|
+
# <%= yield Struct.new(:name).new("David") %>
|
80
|
+
# </html>
|
81
|
+
#
|
82
|
+
# In this case, the layout would receive the block passed into <tt>render :layout</tt>,
|
83
|
+
# and the struct specified would be passed into the block as an argument. The result
|
84
|
+
# would be
|
85
|
+
#
|
86
|
+
# <html>
|
87
|
+
# Hello David
|
88
|
+
# </html>
|
89
|
+
#
|
90
|
+
def _layout_for(*args, &block)
|
91
|
+
name = args.first
|
92
|
+
|
93
|
+
if block && !name.is_a?(Symbol)
|
94
|
+
capture(*args, &block)
|
95
|
+
else
|
96
|
+
super
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails-html-sanitizer"
|
4
|
+
|
5
|
+
module ActionView
|
6
|
+
# = Action View Sanitize Helpers
|
7
|
+
module Helpers #:nodoc:
|
8
|
+
# The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements.
|
9
|
+
# These helper methods extend Action View making them callable within your template files.
|
10
|
+
module SanitizeHelper
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
# Sanitizes HTML input, stripping all but known-safe tags and attributes.
|
13
|
+
#
|
14
|
+
# It also strips href/src attributes with unsafe protocols like
|
15
|
+
# <tt>javascript:</tt>, while also protecting against attempts to use Unicode,
|
16
|
+
# ASCII, and hex character references to work around these protocol filters.
|
17
|
+
# All special characters will be escaped.
|
18
|
+
#
|
19
|
+
# The default sanitizer is Rails::Html::SafeListSanitizer. See {Rails HTML
|
20
|
+
# Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information.
|
21
|
+
#
|
22
|
+
# Custom sanitization rules can also be provided.
|
23
|
+
#
|
24
|
+
# Please note that sanitizing user-provided text does not guarantee that the
|
25
|
+
# resulting markup is valid or even well-formed.
|
26
|
+
#
|
27
|
+
# ==== Options
|
28
|
+
#
|
29
|
+
# * <tt>:tags</tt> - An array of allowed tags.
|
30
|
+
# * <tt>:attributes</tt> - An array of allowed attributes.
|
31
|
+
# * <tt>:scrubber</tt> - A {Rails::Html scrubber}[https://github.com/rails/rails-html-sanitizer]
|
32
|
+
# or {Loofah::Scrubber}[https://github.com/flavorjones/loofah] object that
|
33
|
+
# defines custom sanitization rules. A custom scrubber takes precedence over
|
34
|
+
# custom tags and attributes.
|
35
|
+
#
|
36
|
+
# ==== Examples
|
37
|
+
#
|
38
|
+
# Normal use:
|
39
|
+
#
|
40
|
+
# <%= sanitize @comment.body %>
|
41
|
+
#
|
42
|
+
# Providing custom lists of permitted tags and attributes:
|
43
|
+
#
|
44
|
+
# <%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %>
|
45
|
+
#
|
46
|
+
# Providing a custom Rails::Html scrubber:
|
47
|
+
#
|
48
|
+
# class CommentScrubber < Rails::Html::PermitScrubber
|
49
|
+
# def initialize
|
50
|
+
# super
|
51
|
+
# self.tags = %w( form script comment blockquote )
|
52
|
+
# self.attributes = %w( style )
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# def skip_node?(node)
|
56
|
+
# node.text?
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# <%= sanitize @comment.body, scrubber: CommentScrubber.new %>
|
61
|
+
#
|
62
|
+
# See {Rails HTML Sanitizer}[https://github.com/rails/rails-html-sanitizer] for
|
63
|
+
# documentation about Rails::Html scrubbers.
|
64
|
+
#
|
65
|
+
# Providing a custom Loofah::Scrubber:
|
66
|
+
#
|
67
|
+
# scrubber = Loofah::Scrubber.new do |node|
|
68
|
+
# node.remove if node.name == 'script'
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# <%= sanitize @comment.body, scrubber: scrubber %>
|
72
|
+
#
|
73
|
+
# See {Loofah's documentation}[https://github.com/flavorjones/loofah] for more
|
74
|
+
# information about defining custom Loofah::Scrubber objects.
|
75
|
+
#
|
76
|
+
# To set the default allowed tags or attributes across your application:
|
77
|
+
#
|
78
|
+
# # In config/application.rb
|
79
|
+
# config.action_view.sanitized_allowed_tags = ['strong', 'em', 'a']
|
80
|
+
# config.action_view.sanitized_allowed_attributes = ['href', 'title']
|
81
|
+
def sanitize(html, options = {})
|
82
|
+
self.class.safe_list_sanitizer.sanitize(html, options)&.html_safe
|
83
|
+
end
|
84
|
+
|
85
|
+
# Sanitizes a block of CSS code. Used by +sanitize+ when it comes across a style attribute.
|
86
|
+
def sanitize_css(style)
|
87
|
+
self.class.safe_list_sanitizer.sanitize_css(style)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Strips all HTML tags from +html+, including comments and special characters.
|
91
|
+
#
|
92
|
+
# strip_tags("Strip <i>these</i> tags!")
|
93
|
+
# # => Strip these tags!
|
94
|
+
#
|
95
|
+
# strip_tags("<b>Bold</b> no more! <a href='more.html'>See more here</a>...")
|
96
|
+
# # => Bold no more! See more here...
|
97
|
+
#
|
98
|
+
# strip_tags("<div id='top-bar'>Welcome to my website!</div>")
|
99
|
+
# # => Welcome to my website!
|
100
|
+
#
|
101
|
+
# strip_tags("> A quote from Smith & Wesson")
|
102
|
+
# # => > A quote from Smith & Wesson
|
103
|
+
def strip_tags(html)
|
104
|
+
self.class.full_sanitizer.sanitize(html)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Strips all link tags from +html+ leaving just the link text.
|
108
|
+
#
|
109
|
+
# strip_links('<a href="http://www.rubyonrails.org">Ruby on Rails</a>')
|
110
|
+
# # => Ruby on Rails
|
111
|
+
#
|
112
|
+
# strip_links('Please e-mail me at <a href="mailto:me@email.com">me@email.com</a>.')
|
113
|
+
# # => Please e-mail me at me@email.com.
|
114
|
+
#
|
115
|
+
# strip_links('Blog: <a href="http://www.myblog.com/" class="nav" target=\"_blank\">Visit</a>.')
|
116
|
+
# # => Blog: Visit.
|
117
|
+
#
|
118
|
+
# strip_links('<<a href="https://example.org">malformed & link</a>')
|
119
|
+
# # => <malformed & link
|
120
|
+
def strip_links(html)
|
121
|
+
self.class.link_sanitizer.sanitize(html)
|
122
|
+
end
|
123
|
+
|
124
|
+
module ClassMethods #:nodoc:
|
125
|
+
attr_writer :full_sanitizer, :link_sanitizer, :safe_list_sanitizer
|
126
|
+
|
127
|
+
def sanitizer_vendor
|
128
|
+
Rails::Html::Sanitizer
|
129
|
+
end
|
130
|
+
|
131
|
+
def sanitized_allowed_tags
|
132
|
+
safe_list_sanitizer.allowed_tags
|
133
|
+
end
|
134
|
+
|
135
|
+
def sanitized_allowed_attributes
|
136
|
+
safe_list_sanitizer.allowed_attributes
|
137
|
+
end
|
138
|
+
|
139
|
+
# Gets the Rails::Html::FullSanitizer instance used by +strip_tags+. Replace with
|
140
|
+
# any object that responds to +sanitize+.
|
141
|
+
#
|
142
|
+
# class Application < Rails::Application
|
143
|
+
# config.action_view.full_sanitizer = MySpecialSanitizer.new
|
144
|
+
# end
|
145
|
+
def full_sanitizer
|
146
|
+
@full_sanitizer ||= sanitizer_vendor.full_sanitizer.new
|
147
|
+
end
|
148
|
+
|
149
|
+
# Gets the Rails::Html::LinkSanitizer instance used by +strip_links+.
|
150
|
+
# Replace with any object that responds to +sanitize+.
|
151
|
+
#
|
152
|
+
# class Application < Rails::Application
|
153
|
+
# config.action_view.link_sanitizer = MySpecialSanitizer.new
|
154
|
+
# end
|
155
|
+
def link_sanitizer
|
156
|
+
@link_sanitizer ||= sanitizer_vendor.link_sanitizer.new
|
157
|
+
end
|
158
|
+
|
159
|
+
# Gets the Rails::Html::SafeListSanitizer instance used by sanitize and +sanitize_css+.
|
160
|
+
# Replace with any object that responds to +sanitize+.
|
161
|
+
#
|
162
|
+
# class Application < Rails::Application
|
163
|
+
# config.action_view.safe_list_sanitizer = MySpecialSanitizer.new
|
164
|
+
# end
|
165
|
+
def safe_list_sanitizer
|
166
|
+
@safe_list_sanitizer ||= sanitizer_vendor.safe_list_sanitizer.new
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,314 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/string/output_safety"
|
4
|
+
require "set"
|
5
|
+
|
6
|
+
module ActionView
|
7
|
+
# = Action View Tag Helpers
|
8
|
+
module Helpers #:nodoc:
|
9
|
+
# Provides methods to generate HTML tags programmatically both as a modern
|
10
|
+
# HTML5 compliant builder style and legacy XHTML compliant tags.
|
11
|
+
module TagHelper
|
12
|
+
extend ActiveSupport::Concern
|
13
|
+
include CaptureHelper
|
14
|
+
include OutputSafetyHelper
|
15
|
+
|
16
|
+
BOOLEAN_ATTRIBUTES = %w(allowfullscreen async autofocus autoplay checked
|
17
|
+
compact controls declare default defaultchecked
|
18
|
+
defaultmuted defaultselected defer disabled
|
19
|
+
enabled formnovalidate hidden indeterminate inert
|
20
|
+
ismap itemscope loop multiple muted nohref
|
21
|
+
noresize noshade novalidate nowrap open
|
22
|
+
pauseonexit readonly required reversed scoped
|
23
|
+
seamless selected sortable truespeed typemustmatch
|
24
|
+
visible).to_set
|
25
|
+
|
26
|
+
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
|
27
|
+
|
28
|
+
TAG_PREFIXES = ["aria", "data", :aria, :data].to_set
|
29
|
+
|
30
|
+
PRE_CONTENT_STRINGS = Hash.new { "" }
|
31
|
+
PRE_CONTENT_STRINGS[:textarea] = "\n"
|
32
|
+
PRE_CONTENT_STRINGS["textarea"] = "\n"
|
33
|
+
|
34
|
+
class TagBuilder #:nodoc:
|
35
|
+
include CaptureHelper
|
36
|
+
include OutputSafetyHelper
|
37
|
+
|
38
|
+
VOID_ELEMENTS = %i(area base br col embed hr img input keygen link meta param source track wbr).to_set
|
39
|
+
|
40
|
+
def initialize(view_context)
|
41
|
+
@view_context = view_context
|
42
|
+
end
|
43
|
+
|
44
|
+
def tag_string(name, content = nil, escape_attributes: true, **options, &block)
|
45
|
+
content = @view_context.capture(self, &block) if block_given?
|
46
|
+
if VOID_ELEMENTS.include?(name) && content.nil?
|
47
|
+
"<#{name.to_s.dasherize}#{tag_options(options, escape_attributes)}>".html_safe
|
48
|
+
else
|
49
|
+
content_tag_string(name.to_s.dasherize, content || "", options, escape_attributes)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def content_tag_string(name, content, options, escape = true)
|
54
|
+
tag_options = tag_options(options, escape) if options
|
55
|
+
content = ERB::Util.unwrapped_html_escape(content) if escape
|
56
|
+
"<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
|
57
|
+
end
|
58
|
+
|
59
|
+
def tag_options(options, escape = true)
|
60
|
+
return if options.blank?
|
61
|
+
output = +""
|
62
|
+
sep = " "
|
63
|
+
options.each_pair do |key, value|
|
64
|
+
if TAG_PREFIXES.include?(key) && value.is_a?(Hash)
|
65
|
+
value.each_pair do |k, v|
|
66
|
+
next if v.nil?
|
67
|
+
output << sep
|
68
|
+
output << prefix_tag_option(key, k, v, escape)
|
69
|
+
end
|
70
|
+
elsif BOOLEAN_ATTRIBUTES.include?(key)
|
71
|
+
if value
|
72
|
+
output << sep
|
73
|
+
output << boolean_tag_option(key)
|
74
|
+
end
|
75
|
+
elsif !value.nil?
|
76
|
+
output << sep
|
77
|
+
output << tag_option(key, value, escape)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
output unless output.empty?
|
81
|
+
end
|
82
|
+
|
83
|
+
def boolean_tag_option(key)
|
84
|
+
%(#{key}="#{key}")
|
85
|
+
end
|
86
|
+
|
87
|
+
def tag_option(key, value, escape)
|
88
|
+
if value.is_a?(Array)
|
89
|
+
value = escape ? safe_join(value, " ") : value.join(" ")
|
90
|
+
else
|
91
|
+
value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s.dup
|
92
|
+
end
|
93
|
+
value.gsub!('"', """)
|
94
|
+
%(#{key}="#{value}")
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
def prefix_tag_option(prefix, key, value, escape)
|
99
|
+
key = "#{prefix}-#{key.to_s.dasherize}"
|
100
|
+
unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
|
101
|
+
value = value.to_json
|
102
|
+
end
|
103
|
+
tag_option(key, value, escape)
|
104
|
+
end
|
105
|
+
|
106
|
+
def respond_to_missing?(*args)
|
107
|
+
true
|
108
|
+
end
|
109
|
+
|
110
|
+
def method_missing(called, *args, &block)
|
111
|
+
tag_string(called, *args, &block)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Returns an HTML tag.
|
116
|
+
#
|
117
|
+
# === Building HTML tags
|
118
|
+
#
|
119
|
+
# Builds HTML5 compliant tags with a tag proxy. Every tag can be built with:
|
120
|
+
#
|
121
|
+
# tag.<tag name>(optional content, options)
|
122
|
+
#
|
123
|
+
# where tag name can be e.g. br, div, section, article, or any tag really.
|
124
|
+
#
|
125
|
+
# ==== Passing content
|
126
|
+
#
|
127
|
+
# Tags can pass content to embed within it:
|
128
|
+
#
|
129
|
+
# tag.h1 'All titles fit to print' # => <h1>All titles fit to print</h1>
|
130
|
+
#
|
131
|
+
# tag.div tag.p('Hello world!') # => <div><p>Hello world!</p></div>
|
132
|
+
#
|
133
|
+
# Content can also be captured with a block, which is useful in templates:
|
134
|
+
#
|
135
|
+
# <%= tag.p do %>
|
136
|
+
# The next great American novel starts here.
|
137
|
+
# <% end %>
|
138
|
+
# # => <p>The next great American novel starts here.</p>
|
139
|
+
#
|
140
|
+
# ==== Options
|
141
|
+
#
|
142
|
+
# Use symbol keyed options to add attributes to the generated tag.
|
143
|
+
#
|
144
|
+
# tag.section class: %w( kitties puppies )
|
145
|
+
# # => <section class="kitties puppies"></section>
|
146
|
+
#
|
147
|
+
# tag.section id: dom_id(@post)
|
148
|
+
# # => <section id="<generated dom id>"></section>
|
149
|
+
#
|
150
|
+
# Pass +true+ for any attributes that can render with no values, like +disabled+ and +readonly+.
|
151
|
+
#
|
152
|
+
# tag.input type: 'text', disabled: true
|
153
|
+
# # => <input type="text" disabled="disabled">
|
154
|
+
#
|
155
|
+
# HTML5 <tt>data-*</tt> attributes can be set with a single +data+ key
|
156
|
+
# pointing to a hash of sub-attributes.
|
157
|
+
#
|
158
|
+
# To play nicely with JavaScript conventions, sub-attributes are dasherized.
|
159
|
+
#
|
160
|
+
# tag.article data: { user_id: 123 }
|
161
|
+
# # => <article data-user-id="123"></article>
|
162
|
+
#
|
163
|
+
# Thus <tt>data-user-id</tt> can be accessed as <tt>dataset.userId</tt>.
|
164
|
+
#
|
165
|
+
# Data attribute values are encoded to JSON, with the exception of strings, symbols and
|
166
|
+
# BigDecimals.
|
167
|
+
# This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt>
|
168
|
+
# from 1.4.3.
|
169
|
+
#
|
170
|
+
# tag.div data: { city_state: %w( Chicago IL ) }
|
171
|
+
# # => <div data-city-state="["Chicago","IL"]"></div>
|
172
|
+
#
|
173
|
+
# The generated attributes are escaped by default. This can be disabled using
|
174
|
+
# +escape_attributes+.
|
175
|
+
#
|
176
|
+
# tag.img src: 'open & shut.png'
|
177
|
+
# # => <img src="open & shut.png">
|
178
|
+
#
|
179
|
+
# tag.img src: 'open & shut.png', escape_attributes: false
|
180
|
+
# # => <img src="open & shut.png">
|
181
|
+
#
|
182
|
+
# The tag builder respects
|
183
|
+
# {HTML5 void elements}[https://www.w3.org/TR/html5/syntax.html#void-elements]
|
184
|
+
# if no content is passed, and omits closing tags for those elements.
|
185
|
+
#
|
186
|
+
# # A standard element:
|
187
|
+
# tag.div # => <div></div>
|
188
|
+
#
|
189
|
+
# # A void element:
|
190
|
+
# tag.br # => <br>
|
191
|
+
#
|
192
|
+
# === Legacy syntax
|
193
|
+
#
|
194
|
+
# The following format is for legacy syntax support. It will be deprecated in future versions of Rails.
|
195
|
+
#
|
196
|
+
# tag(name, options = nil, open = false, escape = true)
|
197
|
+
#
|
198
|
+
# It returns an empty HTML tag of type +name+ which by default is XHTML
|
199
|
+
# compliant. Set +open+ to true to create an open tag compatible
|
200
|
+
# with HTML 4.0 and below. Add HTML attributes by passing an attributes
|
201
|
+
# hash to +options+. Set +escape+ to false to disable attribute value
|
202
|
+
# escaping.
|
203
|
+
#
|
204
|
+
# ==== Options
|
205
|
+
#
|
206
|
+
# You can use symbols or strings for the attribute names.
|
207
|
+
#
|
208
|
+
# Use +true+ with boolean attributes that can render with no value, like
|
209
|
+
# +disabled+ and +readonly+.
|
210
|
+
#
|
211
|
+
# HTML5 <tt>data-*</tt> attributes can be set with a single +data+ key
|
212
|
+
# pointing to a hash of sub-attributes.
|
213
|
+
#
|
214
|
+
# ==== Examples
|
215
|
+
#
|
216
|
+
# tag("br")
|
217
|
+
# # => <br />
|
218
|
+
#
|
219
|
+
# tag("br", nil, true)
|
220
|
+
# # => <br>
|
221
|
+
#
|
222
|
+
# tag("input", type: 'text', disabled: true)
|
223
|
+
# # => <input type="text" disabled="disabled" />
|
224
|
+
#
|
225
|
+
# tag("input", type: 'text', class: ["strong", "highlight"])
|
226
|
+
# # => <input class="strong highlight" type="text" />
|
227
|
+
#
|
228
|
+
# tag("img", src: "open & shut.png")
|
229
|
+
# # => <img src="open & shut.png" />
|
230
|
+
#
|
231
|
+
# tag("img", { src: "open & shut.png" }, false, false)
|
232
|
+
# # => <img src="open & shut.png" />
|
233
|
+
#
|
234
|
+
# tag("div", data: { name: 'Stephen', city_state: %w(Chicago IL) })
|
235
|
+
# # => <div data-name="Stephen" data-city-state="["Chicago","IL"]" />
|
236
|
+
def tag(name = nil, options = nil, open = false, escape = true)
|
237
|
+
if name.nil?
|
238
|
+
tag_builder
|
239
|
+
else
|
240
|
+
"<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# Returns an HTML block tag of type +name+ surrounding the +content+. Add
|
245
|
+
# HTML attributes by passing an attributes hash to +options+.
|
246
|
+
# Instead of passing the content as an argument, you can also use a block
|
247
|
+
# in which case, you pass your +options+ as the second parameter.
|
248
|
+
# Set escape to false to disable attribute value escaping.
|
249
|
+
# Note: this is legacy syntax, see +tag+ method description for details.
|
250
|
+
#
|
251
|
+
# ==== Options
|
252
|
+
# The +options+ hash can be used with attributes with no value like (<tt>disabled</tt> and
|
253
|
+
# <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use
|
254
|
+
# symbols or strings for the attribute names.
|
255
|
+
#
|
256
|
+
# ==== Examples
|
257
|
+
# content_tag(:p, "Hello world!")
|
258
|
+
# # => <p>Hello world!</p>
|
259
|
+
# content_tag(:div, content_tag(:p, "Hello world!"), class: "strong")
|
260
|
+
# # => <div class="strong"><p>Hello world!</p></div>
|
261
|
+
# content_tag(:div, "Hello world!", class: ["strong", "highlight"])
|
262
|
+
# # => <div class="strong highlight">Hello world!</div>
|
263
|
+
# content_tag("select", options, multiple: true)
|
264
|
+
# # => <select multiple="multiple">...options...</select>
|
265
|
+
#
|
266
|
+
# <%= content_tag :div, class: "strong" do -%>
|
267
|
+
# Hello world!
|
268
|
+
# <% end -%>
|
269
|
+
# # => <div class="strong">Hello world!</div>
|
270
|
+
def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
|
271
|
+
if block_given?
|
272
|
+
options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
|
273
|
+
tag_builder.content_tag_string(name, capture(&block), options, escape)
|
274
|
+
else
|
275
|
+
tag_builder.content_tag_string(name, content_or_options_with_block, options, escape)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
# Returns a CDATA section with the given +content+. CDATA sections
|
280
|
+
# are used to escape blocks of text containing characters which would
|
281
|
+
# otherwise be recognized as markup. CDATA sections begin with the string
|
282
|
+
# <tt><![CDATA[</tt> and end with (and may not contain) the string <tt>]]></tt>.
|
283
|
+
#
|
284
|
+
# cdata_section("<hello world>")
|
285
|
+
# # => <![CDATA[<hello world>]]>
|
286
|
+
#
|
287
|
+
# cdata_section(File.read("hello_world.txt"))
|
288
|
+
# # => <![CDATA[<hello from a text file]]>
|
289
|
+
#
|
290
|
+
# cdata_section("hello]]>world")
|
291
|
+
# # => <![CDATA[hello]]]]><![CDATA[>world]]>
|
292
|
+
def cdata_section(content)
|
293
|
+
splitted = content.to_s.gsub(/\]\]\>/, "]]]]><![CDATA[>")
|
294
|
+
"<![CDATA[#{splitted}]]>".html_safe
|
295
|
+
end
|
296
|
+
|
297
|
+
# Returns an escaped version of +html+ without affecting existing escaped entities.
|
298
|
+
#
|
299
|
+
# escape_once("1 < 2 & 3")
|
300
|
+
# # => "1 < 2 & 3"
|
301
|
+
#
|
302
|
+
# escape_once("<< Accept & Checkout")
|
303
|
+
# # => "<< Accept & Checkout"
|
304
|
+
def escape_once(html)
|
305
|
+
ERB::Util.html_escape_once(html)
|
306
|
+
end
|
307
|
+
|
308
|
+
private
|
309
|
+
def tag_builder
|
310
|
+
@tag_builder ||= TagBuilder.new(self)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|