actionview 5.2.3

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.

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