actionview 4.1.13 → 6.1.3.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionview might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +181 -359
- data/MIT-LICENSE +1 -1
- data/README.rdoc +12 -6
- data/lib/action_view/base.rb +115 -43
- data/lib/action_view/buffers.rb +22 -4
- data/lib/action_view/cache_expiry.rb +52 -0
- data/lib/action_view/context.rb +8 -12
- data/lib/action_view/dependency_tracker.rb +61 -21
- data/lib/action_view/digestor.rb +89 -84
- data/lib/action_view/flows.rb +12 -13
- data/lib/action_view/gem_version.rb +6 -4
- data/lib/action_view/helpers/active_model_helper.rb +16 -11
- data/lib/action_view/helpers/asset_tag_helper.rb +311 -105
- data/lib/action_view/helpers/asset_url_helper.rb +197 -80
- data/lib/action_view/helpers/atom_feed_helper.rb +20 -17
- data/lib/action_view/helpers/cache_helper.rb +109 -45
- data/lib/action_view/helpers/capture_helper.rb +20 -22
- data/lib/action_view/helpers/controller_helper.rb +15 -4
- 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 +245 -140
- data/lib/action_view/helpers/debug_helper.rb +14 -17
- data/lib/action_view/helpers/form_helper.rb +875 -148
- data/lib/action_view/helpers/form_options_helper.rb +128 -82
- data/lib/action_view/helpers/form_tag_helper.rb +253 -91
- data/lib/action_view/helpers/javascript_helper.rb +37 -15
- data/lib/action_view/helpers/number_helper.rb +100 -77
- data/lib/action_view/helpers/output_safety_helper.rb +42 -10
- data/lib/action_view/helpers/rendering_helper.rb +26 -15
- data/lib/action_view/helpers/sanitize_helper.rb +79 -164
- data/lib/action_view/helpers/tag_helper.rb +277 -64
- data/lib/action_view/helpers/tags/base.rb +143 -92
- data/lib/action_view/helpers/tags/check_box.rb +20 -19
- data/lib/action_view/helpers/tags/checkable.rb +4 -2
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -30
- 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 +4 -2
- 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 +14 -5
- 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 +2 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
- data/lib/action_view/helpers/tags/hidden_field.rb +2 -0
- data/lib/action_view/helpers/tags/label.rb +41 -22
- 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 +24 -0
- 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 +3 -0
- 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 +7 -1
- data/lib/action_view/helpers/tags/text_field.rb +11 -7
- data/lib/action_view/helpers/tags/time_field.rb +3 -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 +39 -0
- 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.rb +4 -1
- data/lib/action_view/helpers/text_helper.rb +80 -45
- data/lib/action_view/helpers/translation_helper.rb +148 -67
- data/lib/action_view/helpers/url_helper.rb +289 -147
- data/lib/action_view/helpers.rb +5 -3
- data/lib/action_view/layouts.rb +68 -63
- data/lib/action_view/log_subscriber.rb +80 -13
- data/lib/action_view/lookup_context.rb +137 -92
- data/lib/action_view/model_naming.rb +4 -2
- data/lib/action_view/path_set.rb +30 -16
- data/lib/action_view/railtie.rb +62 -13
- data/lib/action_view/record_identifier.rb +53 -26
- data/lib/action_view/renderer/abstract_renderer.rb +152 -13
- 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 +61 -261
- data/lib/action_view/renderer/renderer.rb +67 -6
- data/lib/action_view/renderer/streaming_template_renderer.rb +58 -54
- data/lib/action_view/renderer/template_renderer.rb +83 -75
- data/lib/action_view/rendering.rb +73 -46
- data/lib/action_view/routing_url_for.rb +54 -17
- data/lib/action_view/tasks/cache_digests.rake +25 -0
- data/lib/action_view/template/error.rb +44 -29
- 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 +23 -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 +22 -9
- data/lib/action_view/template/html.rb +10 -11
- 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 +267 -181
- 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 +8 -10
- data/lib/action_view/template/types.rb +18 -18
- data/lib/action_view/template.rb +109 -99
- data/lib/action_view/test_case.rb +73 -53
- data/lib/action_view/testing/resolvers.rb +24 -33
- data/lib/action_view/unbound_template.rb +31 -0
- data/lib/action_view/version.rb +3 -1
- data/lib/action_view/view_paths.rb +74 -44
- data/lib/action_view.rb +14 -9
- data/lib/assets/compiled/rails-ujs.js +746 -0
- metadata +71 -26
- data/lib/action_view/helpers/record_tag_helper.rb +0 -108
- data/lib/action_view/tasks/dependencies.rake +0 -23
- data/lib/action_view/vendor/html-scanner/html/document.rb +0 -68
- data/lib/action_view/vendor/html-scanner/html/node.rb +0 -532
- data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +0 -188
- data/lib/action_view/vendor/html-scanner/html/selector.rb +0 -830
- data/lib/action_view/vendor/html-scanner/html/tokenizer.rb +0 -107
- data/lib/action_view/vendor/html-scanner/html/version.rb +0 -11
- data/lib/action_view/vendor/html-scanner.rb +0 -20
data/lib/action_view/digestor.rb
CHANGED
@@ -1,122 +1,127 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_view/dependency_tracker"
|
4
4
|
|
5
5
|
module ActionView
|
6
6
|
class Digestor
|
7
|
-
|
8
|
-
@@cache = ThreadSafe::Cache.new
|
9
|
-
@@digest_monitor = Monitor.new
|
7
|
+
@@digest_mutex = Mutex.new
|
10
8
|
|
11
9
|
class << self
|
12
10
|
# Supported options:
|
13
11
|
#
|
14
|
-
# * <tt>name</tt>
|
15
|
-
# * <tt>
|
16
|
-
# * <tt>
|
17
|
-
# * <tt>
|
18
|
-
def digest(
|
19
|
-
|
20
|
-
|
21
|
-
|
12
|
+
# * <tt>name</tt> - Template name
|
13
|
+
# * <tt>format</tt> - Template format
|
14
|
+
# * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt>
|
15
|
+
# * <tt>dependencies</tt> - An array of dependent views
|
16
|
+
def digest(name:, format: nil, finder:, dependencies: nil)
|
17
|
+
if dependencies.nil? || dependencies.empty?
|
18
|
+
cache_key = "#{name}.#{format}"
|
19
|
+
else
|
20
|
+
cache_key = [ name, format, dependencies ].flatten.compact.join(".")
|
21
|
+
end
|
22
22
|
|
23
23
|
# this is a correctly done double-checked locking idiom
|
24
|
-
# (
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
# (Concurrent::Map's lookups have volatile semantics)
|
25
|
+
finder.digest_cache[cache_key] || @@digest_mutex.synchronize do
|
26
|
+
finder.digest_cache.fetch(cache_key) do # re-check under lock
|
27
|
+
partial = name.include?("/_")
|
28
|
+
root = tree(name, finder, partial)
|
29
|
+
dependencies.each do |injected_dep|
|
30
|
+
root.children << Injected.new(injected_dep, nil, nil)
|
31
|
+
end if dependencies
|
32
|
+
finder.digest_cache[cache_key] = root.digest(finder)
|
28
33
|
end
|
29
34
|
end
|
30
35
|
end
|
31
36
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
# Prevent re-entry or else recursive templates will blow the stack.
|
36
|
-
# There is no need to worry about other threads seeing the +false+ value,
|
37
|
-
# as they will then have to wait for this thread to let go of the @@digest_monitor lock.
|
38
|
-
pre_stored = @@cache.put_if_absent(cache_key, false).nil? # put_if_absent returns nil on insertion
|
39
|
-
PartialDigestor
|
40
|
-
else
|
41
|
-
Digestor
|
42
|
-
end
|
37
|
+
def logger
|
38
|
+
ActionView::Base.logger || NullLogger
|
39
|
+
end
|
43
40
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
ensure
|
49
|
-
# something went wrong or ActionView::Resolver.caching? is false, make sure not to corrupt the @@cache
|
50
|
-
@@cache.delete_pair(cache_key, false) if pre_stored && !stored_digest
|
51
|
-
end
|
52
|
-
end
|
41
|
+
# Create a dependency tree for template named +name+.
|
42
|
+
def tree(name, finder, partial = false, seen = {})
|
43
|
+
logical_name = name.gsub(%r|/_|, "/")
|
44
|
+
interpolated = name.include?("#")
|
53
45
|
|
54
|
-
|
46
|
+
if !interpolated && (template = find_template(finder, logical_name, [], partial, []))
|
47
|
+
if node = seen[template.identifier] # handle cycles in the tree
|
48
|
+
node
|
49
|
+
else
|
50
|
+
node = seen[template.identifier] = Node.create(name, logical_name, template, partial)
|
55
51
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
52
|
+
deps = DependencyTracker.find_dependencies(name, template, finder.view_paths)
|
53
|
+
deps.uniq { |n| n.gsub(%r|/_|, "/") }.each do |dep_file|
|
54
|
+
node.children << tree(dep_file, finder, true, seen)
|
55
|
+
end
|
56
|
+
node
|
57
|
+
end
|
58
|
+
else
|
59
|
+
unless interpolated # Dynamic template partial names can never be tracked
|
60
|
+
logger.error " Couldn't find template for digesting: #{name}"
|
61
|
+
end
|
60
62
|
|
61
|
-
|
62
|
-
|
63
|
-
logger.try :info, " Cache digest for #{template.inspect}: #{digest}"
|
63
|
+
seen[name] ||= Missing.new(name, logical_name, nil)
|
64
|
+
end
|
64
65
|
end
|
65
|
-
rescue ActionView::MissingTemplate
|
66
|
-
logger.try :error, " Couldn't find template for digesting: #{name}"
|
67
|
-
''
|
68
|
-
end
|
69
66
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
67
|
+
private
|
68
|
+
def find_template(finder, name, prefixes, partial, keys)
|
69
|
+
finder.disable_cache do
|
70
|
+
finder.find_all(name, prefixes, partial, keys).first
|
71
|
+
end
|
72
|
+
end
|
74
73
|
end
|
75
74
|
|
76
|
-
|
77
|
-
|
78
|
-
dependencies = PartialDigestor.new(name: dependency, finder: finder).nested_dependencies
|
79
|
-
dependencies.any? ? { dependency => dependencies } : dependency
|
80
|
-
end
|
81
|
-
end
|
75
|
+
class Node
|
76
|
+
attr_reader :name, :logical_name, :template, :children
|
82
77
|
|
83
|
-
|
84
|
-
|
85
|
-
|
78
|
+
def self.create(name, logical_name, template, partial)
|
79
|
+
klass = partial ? Partial : Node
|
80
|
+
klass.new(name, logical_name, template, [])
|
86
81
|
end
|
87
82
|
|
88
|
-
def logical_name
|
89
|
-
name
|
83
|
+
def initialize(name, logical_name, template, children = [])
|
84
|
+
@name = name
|
85
|
+
@logical_name = logical_name
|
86
|
+
@template = template
|
87
|
+
@children = children
|
90
88
|
end
|
91
89
|
|
92
|
-
def
|
93
|
-
|
90
|
+
def digest(finder, stack = [])
|
91
|
+
ActiveSupport::Digest.hexdigest("#{template.source}-#{dependency_digest(finder, stack)}")
|
94
92
|
end
|
95
93
|
|
96
|
-
def
|
97
|
-
|
94
|
+
def dependency_digest(finder, stack)
|
95
|
+
children.map do |node|
|
96
|
+
if stack.include?(node)
|
97
|
+
false
|
98
|
+
else
|
99
|
+
finder.digest_cache[node.name] ||= begin
|
100
|
+
stack.push node
|
101
|
+
node.digest(finder, stack).tap { stack.pop }
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end.join("-")
|
98
105
|
end
|
99
106
|
|
100
|
-
def
|
101
|
-
|
107
|
+
def to_dep_map
|
108
|
+
children.any? ? { name => children.map(&:to_dep_map) } : name
|
102
109
|
end
|
110
|
+
end
|
103
111
|
|
104
|
-
|
105
|
-
template_digests = dependencies.collect do |template_name|
|
106
|
-
Digestor.digest(name: template_name, finder: finder, partial: true)
|
107
|
-
end
|
112
|
+
class Partial < Node; end
|
108
113
|
|
109
|
-
|
110
|
-
end
|
114
|
+
class Missing < Node
|
115
|
+
def digest(finder, _ = []) "" end
|
116
|
+
end
|
111
117
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
end
|
118
|
+
class Injected < Node
|
119
|
+
def digest(finder, _ = []) name end
|
120
|
+
end
|
116
121
|
|
117
|
-
|
118
|
-
|
119
|
-
|
122
|
+
class NullLogger
|
123
|
+
def self.debug(_); end
|
124
|
+
def self.error(_); end
|
120
125
|
end
|
121
126
|
end
|
122
127
|
end
|
data/lib/action_view/flows.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/string/output_safety"
|
2
4
|
|
3
5
|
module ActionView
|
4
6
|
class OutputFlow #:nodoc:
|
5
7
|
attr_reader :content
|
6
8
|
|
7
9
|
def initialize
|
8
|
-
@content = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new }
|
10
|
+
@content = Hash.new { |h, k| h[k] = ActiveSupport::SafeBuffer.new }
|
9
11
|
end
|
10
12
|
|
11
13
|
# Called by _layout_for to read stored values.
|
@@ -15,7 +17,7 @@ module ActionView
|
|
15
17
|
|
16
18
|
# Called by each renderer object to set the layout contents.
|
17
19
|
def set(key, value)
|
18
|
-
@content[key] = value
|
20
|
+
@content[key] = ActiveSupport::SafeBuffer.new(value)
|
19
21
|
end
|
20
22
|
|
21
23
|
# Called by content_for
|
@@ -23,7 +25,6 @@ module ActionView
|
|
23
25
|
@content[key] << value
|
24
26
|
end
|
25
27
|
alias_method :append!, :append
|
26
|
-
|
27
28
|
end
|
28
29
|
|
29
30
|
class StreamingFlow < OutputFlow #:nodoc:
|
@@ -37,9 +38,8 @@ module ActionView
|
|
37
38
|
end
|
38
39
|
|
39
40
|
# Try to get stored content. If the content
|
40
|
-
# is not available and we
|
41
|
-
#
|
42
|
-
# key and yield.
|
41
|
+
# is not available and we're inside the layout fiber,
|
42
|
+
# then it will begin waiting for the given key and yield.
|
43
43
|
def get(key)
|
44
44
|
return super if @content.key?(key)
|
45
45
|
|
@@ -60,17 +60,16 @@ module ActionView
|
|
60
60
|
end
|
61
61
|
|
62
62
|
# Appends the contents for the given key. This is called
|
63
|
-
# by
|
64
|
-
# the key it
|
63
|
+
# by providing and resuming back to the fiber,
|
64
|
+
# if that's the key it's waiting for.
|
65
65
|
def append!(key, value)
|
66
66
|
super
|
67
67
|
@fiber.resume if @waiting_for == key
|
68
68
|
end
|
69
69
|
|
70
70
|
private
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
end
|
71
|
+
def inside_fiber?
|
72
|
+
Fiber.current.object_id != @root
|
73
|
+
end
|
75
74
|
end
|
76
75
|
end
|
@@ -1,14 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionView
|
2
|
-
# Returns the version of the currently loaded
|
4
|
+
# Returns the version of the currently loaded Action View as a <tt>Gem::Version</tt>
|
3
5
|
def self.gem_version
|
4
6
|
Gem::Version.new VERSION::STRING
|
5
7
|
end
|
6
8
|
|
7
9
|
module VERSION
|
8
|
-
MAJOR =
|
10
|
+
MAJOR = 6
|
9
11
|
MINOR = 1
|
10
|
-
TINY =
|
11
|
-
PRE =
|
12
|
+
TINY = 3
|
13
|
+
PRE = "1"
|
12
14
|
|
13
15
|
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
|
14
16
|
end
|
@@ -1,9 +1,11 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/module/attribute_accessors"
|
4
|
+
require "active_support/core_ext/enumerable"
|
3
5
|
|
4
6
|
module ActionView
|
5
7
|
# = Active Model Helpers
|
6
|
-
module Helpers
|
8
|
+
module Helpers #:nodoc:
|
7
9
|
module ActiveModelHelper
|
8
10
|
end
|
9
11
|
|
@@ -15,8 +17,8 @@ module ActionView
|
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
18
|
-
def content_tag(*)
|
19
|
-
error_wrapping(super)
|
20
|
+
def content_tag(type, options, *)
|
21
|
+
select_markup_helper?(type) ? super : error_wrapping(super)
|
20
22
|
end
|
21
23
|
|
22
24
|
def tag(type, options, *)
|
@@ -36,14 +38,17 @@ module ActionView
|
|
36
38
|
end
|
37
39
|
|
38
40
|
private
|
41
|
+
def object_has_errors?
|
42
|
+
object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present?
|
43
|
+
end
|
39
44
|
|
40
|
-
|
41
|
-
|
42
|
-
|
45
|
+
def select_markup_helper?(type)
|
46
|
+
["optgroup", "option"].include?(type)
|
47
|
+
end
|
43
48
|
|
44
|
-
|
45
|
-
|
46
|
-
|
49
|
+
def tag_generate_errors?(options)
|
50
|
+
options["type"] != "hidden"
|
51
|
+
end
|
47
52
|
end
|
48
53
|
end
|
49
54
|
end
|
@@ -1,17 +1,20 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/array/extract_options"
|
4
|
+
require "active_support/core_ext/hash/keys"
|
5
|
+
require "active_support/core_ext/object/inclusion"
|
6
|
+
require "action_view/helpers/asset_url_helper"
|
7
|
+
require "action_view/helpers/tag_helper"
|
5
8
|
|
6
9
|
module ActionView
|
7
10
|
# = Action View Asset Tag Helpers
|
8
11
|
module Helpers #:nodoc:
|
9
12
|
# This module provides methods for generating HTML that links views to assets such
|
10
|
-
# as images,
|
13
|
+
# as images, JavaScripts, stylesheets, and feeds. These methods do not verify
|
11
14
|
# the assets exist before linking to them:
|
12
15
|
#
|
13
16
|
# image_tag("rails.png")
|
14
|
-
# # => <img
|
17
|
+
# # => <img src="/assets/rails.png" />
|
15
18
|
# stylesheet_link_tag("application")
|
16
19
|
# # => <link href="/assets/application.css?body=1" media="screen" rel="stylesheet" />
|
17
20
|
module AssetTagHelper
|
@@ -20,13 +23,15 @@ module ActionView
|
|
20
23
|
include AssetUrlHelper
|
21
24
|
include TagHelper
|
22
25
|
|
26
|
+
mattr_accessor :preload_links_header
|
27
|
+
|
23
28
|
# Returns an HTML script tag for each of the +sources+ provided.
|
24
29
|
#
|
25
30
|
# Sources may be paths to JavaScript files. Relative paths are assumed to be relative
|
26
31
|
# to <tt>assets/javascripts</tt>, full paths are assumed to be relative to the document
|
27
32
|
# root. Relative paths are idiomatic, use absolute paths only when needed.
|
28
33
|
#
|
29
|
-
# When passing paths, the ".js" extension is optional.
|
34
|
+
# When passing paths, the ".js" extension is optional. If you do not want ".js"
|
30
35
|
# appended to the path <tt>extname: false</tt> can be set on the options.
|
31
36
|
#
|
32
37
|
# You can modify the HTML attributes of the script tag by passing a hash as the
|
@@ -35,33 +40,84 @@ module ActionView
|
|
35
40
|
# When the Asset Pipeline is enabled, you can pass the name of your manifest as
|
36
41
|
# source, and include other JavaScript or CoffeeScript files inside the manifest.
|
37
42
|
#
|
43
|
+
# If the server supports Early Hints header links for these assets will be
|
44
|
+
# automatically pushed.
|
45
|
+
#
|
46
|
+
# ==== Options
|
47
|
+
#
|
48
|
+
# When the last parameter is a hash you can add HTML attributes using that
|
49
|
+
# parameter. The following options are supported:
|
50
|
+
#
|
51
|
+
# * <tt>:extname</tt> - Append an extension to the generated URL unless the extension
|
52
|
+
# already exists. This only applies for relative URLs.
|
53
|
+
# * <tt>:protocol</tt> - Sets the protocol of the generated URL. This option only
|
54
|
+
# applies when a relative URL and +host+ options are provided.
|
55
|
+
# * <tt>:host</tt> - When a relative URL is provided the host is added to the
|
56
|
+
# that path.
|
57
|
+
# * <tt>:skip_pipeline</tt> - This option is used to bypass the asset pipeline
|
58
|
+
# when it is set to true.
|
59
|
+
# * <tt>:nonce</tt> - When set to true, adds an automatic nonce value if
|
60
|
+
# you have Content Security Policy enabled.
|
61
|
+
#
|
62
|
+
# ==== Examples
|
63
|
+
#
|
38
64
|
# javascript_include_tag "xmlhr"
|
39
|
-
# # => <script src="/assets/xmlhr.js
|
65
|
+
# # => <script src="/assets/xmlhr.debug-1284139606.js"></script>
|
66
|
+
#
|
67
|
+
# javascript_include_tag "xmlhr", host: "localhost", protocol: "https"
|
68
|
+
# # => <script src="https://localhost/assets/xmlhr.debug-1284139606.js"></script>
|
40
69
|
#
|
41
70
|
# javascript_include_tag "template.jst", extname: false
|
42
|
-
# # => <script src="/assets/template.jst
|
71
|
+
# # => <script src="/assets/template.debug-1284139606.jst"></script>
|
43
72
|
#
|
44
73
|
# javascript_include_tag "xmlhr.js"
|
45
|
-
# # => <script src="/assets/xmlhr.js
|
74
|
+
# # => <script src="/assets/xmlhr.debug-1284139606.js"></script>
|
46
75
|
#
|
47
76
|
# javascript_include_tag "common.javascript", "/elsewhere/cools"
|
48
|
-
# # => <script src="/assets/common.javascript
|
49
|
-
# # <script src="/elsewhere/cools.js
|
77
|
+
# # => <script src="/assets/common.javascript.debug-1284139606.js"></script>
|
78
|
+
# # <script src="/elsewhere/cools.debug-1284139606.js"></script>
|
50
79
|
#
|
51
80
|
# javascript_include_tag "http://www.example.com/xmlhr"
|
52
81
|
# # => <script src="http://www.example.com/xmlhr"></script>
|
53
82
|
#
|
54
83
|
# javascript_include_tag "http://www.example.com/xmlhr.js"
|
55
84
|
# # => <script src="http://www.example.com/xmlhr.js"></script>
|
85
|
+
#
|
86
|
+
# javascript_include_tag "http://www.example.com/xmlhr.js", nonce: true
|
87
|
+
# # => <script src="http://www.example.com/xmlhr.js" nonce="..."></script>
|
56
88
|
def javascript_include_tag(*sources)
|
57
89
|
options = sources.extract_options!.stringify_keys
|
58
|
-
path_options = options.extract!(
|
59
|
-
|
90
|
+
path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
|
91
|
+
preload_links = []
|
92
|
+
nopush = options["nopush"].nil? ? true : options.delete("nopush")
|
93
|
+
crossorigin = options.delete("crossorigin")
|
94
|
+
crossorigin = "anonymous" if crossorigin == true
|
95
|
+
integrity = options["integrity"]
|
96
|
+
|
97
|
+
sources_tags = sources.uniq.map { |source|
|
98
|
+
href = path_to_javascript(source, path_options)
|
99
|
+
if preload_links_header && !options["defer"]
|
100
|
+
preload_link = "<#{href}>; rel=preload; as=script"
|
101
|
+
preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
|
102
|
+
preload_link += "; integrity=#{integrity}" unless integrity.nil?
|
103
|
+
preload_link += "; nopush" if nopush
|
104
|
+
preload_links << preload_link
|
105
|
+
end
|
60
106
|
tag_options = {
|
61
|
-
"src" =>
|
107
|
+
"src" => href,
|
108
|
+
"crossorigin" => crossorigin
|
62
109
|
}.merge!(options)
|
63
|
-
|
110
|
+
if tag_options["nonce"] == true
|
111
|
+
tag_options["nonce"] = content_security_policy_nonce
|
112
|
+
end
|
113
|
+
content_tag("script", "", tag_options)
|
64
114
|
}.join("\n").html_safe
|
115
|
+
|
116
|
+
if preload_links_header
|
117
|
+
send_preload_links_header(preload_links)
|
118
|
+
end
|
119
|
+
|
120
|
+
sources_tags
|
65
121
|
end
|
66
122
|
|
67
123
|
# Returns a stylesheet link tag for the sources specified as arguments. If
|
@@ -71,6 +127,9 @@ module ActionView
|
|
71
127
|
# to "screen", so you must explicitly set it to "all" for the stylesheet(s) to
|
72
128
|
# apply to all media types.
|
73
129
|
#
|
130
|
+
# If the server supports Early Hints header links for these assets will be
|
131
|
+
# automatically pushed.
|
132
|
+
#
|
74
133
|
# stylesheet_link_tag "style"
|
75
134
|
# # => <link href="/assets/style.css" media="screen" rel="stylesheet" />
|
76
135
|
#
|
@@ -91,22 +150,42 @@ module ActionView
|
|
91
150
|
# # <link href="/css/stylish.css" media="screen" rel="stylesheet" />
|
92
151
|
def stylesheet_link_tag(*sources)
|
93
152
|
options = sources.extract_options!.stringify_keys
|
94
|
-
path_options = options.extract!(
|
153
|
+
path_options = options.extract!("protocol", "host", "skip_pipeline").symbolize_keys
|
154
|
+
preload_links = []
|
155
|
+
crossorigin = options.delete("crossorigin")
|
156
|
+
crossorigin = "anonymous" if crossorigin == true
|
157
|
+
nopush = options["nopush"].nil? ? true : options.delete("nopush")
|
158
|
+
integrity = options["integrity"]
|
95
159
|
|
96
|
-
sources.uniq.map { |source|
|
160
|
+
sources_tags = sources.uniq.map { |source|
|
161
|
+
href = path_to_stylesheet(source, path_options)
|
162
|
+
if preload_links_header
|
163
|
+
preload_link = "<#{href}>; rel=preload; as=style"
|
164
|
+
preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
|
165
|
+
preload_link += "; integrity=#{integrity}" unless integrity.nil?
|
166
|
+
preload_link += "; nopush" if nopush
|
167
|
+
preload_links << preload_link
|
168
|
+
end
|
97
169
|
tag_options = {
|
98
170
|
"rel" => "stylesheet",
|
99
171
|
"media" => "screen",
|
100
|
-
"
|
172
|
+
"crossorigin" => crossorigin,
|
173
|
+
"href" => href
|
101
174
|
}.merge!(options)
|
102
175
|
tag(:link, tag_options)
|
103
176
|
}.join("\n").html_safe
|
177
|
+
|
178
|
+
if preload_links_header
|
179
|
+
send_preload_links_header(preload_links)
|
180
|
+
end
|
181
|
+
|
182
|
+
sources_tags
|
104
183
|
end
|
105
184
|
|
106
185
|
# Returns a link tag that browsers and feed readers can use to auto-detect
|
107
|
-
# an RSS or
|
108
|
-
# <tt>:atom</tt>. Control the link options in url_for format
|
109
|
-
# +url_options+. You can modify the LINK tag itself in +tag_options+.
|
186
|
+
# an RSS, Atom, or JSON feed. The +type+ can be <tt>:rss</tt> (default),
|
187
|
+
# <tt>:atom</tt>, or <tt>:json</tt>. Control the link options in url_for format
|
188
|
+
# using the +url_options+. You can modify the LINK tag itself in +tag_options+.
|
110
189
|
#
|
111
190
|
# ==== Options
|
112
191
|
#
|
@@ -120,6 +199,8 @@ module ActionView
|
|
120
199
|
# # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" />
|
121
200
|
# auto_discovery_link_tag(:atom)
|
122
201
|
# # => <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.currenthost.com/controller/action" />
|
202
|
+
# auto_discovery_link_tag(:json)
|
203
|
+
# # => <link rel="alternate" type="application/json" title="JSON" href="http://www.currenthost.com/controller/action" />
|
123
204
|
# auto_discovery_link_tag(:rss, {action: "feed"})
|
124
205
|
# # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/feed" />
|
125
206
|
# auto_discovery_link_tag(:rss, {action: "feed"}, {title: "My RSS"})
|
@@ -127,143 +208,220 @@ module ActionView
|
|
127
208
|
# auto_discovery_link_tag(:rss, {controller: "news", action: "feed"})
|
128
209
|
# # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/news/feed" />
|
129
210
|
# auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {title: "Example RSS"})
|
130
|
-
# # => <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed" />
|
211
|
+
# # => <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed.rss" />
|
131
212
|
def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
|
132
|
-
if !(type == :rss || type == :atom) && tag_options[:type].blank?
|
133
|
-
raise ArgumentError.new("You should pass :type tag_option key explicitly, because you have passed #{type} type other than :rss or :
|
213
|
+
if !(type == :rss || type == :atom || type == :json) && tag_options[:type].blank?
|
214
|
+
raise ArgumentError.new("You should pass :type tag_option key explicitly, because you have passed #{type} type other than :rss, :atom, or :json.")
|
134
215
|
end
|
135
216
|
|
136
217
|
tag(
|
137
218
|
"link",
|
138
219
|
"rel" => tag_options[:rel] || "alternate",
|
139
|
-
"type" => tag_options[:type] ||
|
220
|
+
"type" => tag_options[:type] || Template::Types[type].to_s,
|
140
221
|
"title" => tag_options[:title] || type.to_s.upcase,
|
141
|
-
"href" => url_options.is_a?(Hash) ? url_for(url_options.merge(:
|
222
|
+
"href" => url_options.is_a?(Hash) ? url_for(url_options.merge(only_path: false)) : url_options
|
142
223
|
)
|
143
224
|
end
|
144
225
|
|
145
|
-
# Returns a link
|
146
|
-
# in the first argument. The helper accepts an additional options hash where
|
147
|
-
# you can override "rel" and "type".
|
226
|
+
# Returns a link tag for a favicon managed by the asset pipeline.
|
148
227
|
#
|
149
|
-
#
|
228
|
+
# If a page has no link like the one generated by this helper, browsers
|
229
|
+
# ask for <tt>/favicon.ico</tt> automatically, and cache the file if the
|
230
|
+
# request succeeds. If the favicon changes it is hard to get it updated.
|
150
231
|
#
|
151
|
-
#
|
152
|
-
#
|
232
|
+
# To have better control applications may let the asset pipeline manage
|
233
|
+
# their favicon storing the file under <tt>app/assets/images</tt>, and
|
234
|
+
# using this helper to generate its corresponding link tag.
|
153
235
|
#
|
154
|
-
#
|
236
|
+
# The helper gets the name of the favicon file as first argument, which
|
237
|
+
# defaults to "favicon.ico", and also supports +:rel+ and +:type+ options
|
238
|
+
# to override their defaults, "shortcut icon" and "image/x-icon"
|
239
|
+
# respectively:
|
240
|
+
#
|
241
|
+
# favicon_link_tag
|
242
|
+
# # => <link href="/assets/favicon.ico" rel="shortcut icon" type="image/x-icon" />
|
155
243
|
#
|
156
244
|
# favicon_link_tag 'myicon.ico'
|
157
|
-
# # => <link href="/assets/myicon.ico" rel="shortcut icon" type="image/
|
245
|
+
# # => <link href="/assets/myicon.ico" rel="shortcut icon" type="image/x-icon" />
|
158
246
|
#
|
159
|
-
# Mobile Safari looks for a different
|
160
|
-
# will be used if you add the page to the home screen of an
|
247
|
+
# Mobile Safari looks for a different link tag, pointing to an image that
|
248
|
+
# will be used if you add the page to the home screen of an iOS device.
|
161
249
|
# The following call would generate such a tag:
|
162
250
|
#
|
163
251
|
# favicon_link_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png'
|
164
252
|
# # => <link href="/assets/mb-icon.png" rel="apple-touch-icon" type="image/png" />
|
165
|
-
def favicon_link_tag(source=
|
166
|
-
tag(
|
167
|
-
:
|
168
|
-
:
|
169
|
-
:
|
253
|
+
def favicon_link_tag(source = "favicon.ico", options = {})
|
254
|
+
tag("link", {
|
255
|
+
rel: "shortcut icon",
|
256
|
+
type: "image/x-icon",
|
257
|
+
href: path_to_image(source, skip_pipeline: options.delete(:skip_pipeline))
|
170
258
|
}.merge!(options.symbolize_keys))
|
171
259
|
end
|
172
260
|
|
261
|
+
# Returns a link tag that browsers can use to preload the +source+.
|
262
|
+
# The +source+ can be the path of a resource managed by asset pipeline,
|
263
|
+
# a full path, or an URI.
|
264
|
+
#
|
265
|
+
# ==== Options
|
266
|
+
#
|
267
|
+
# * <tt>:type</tt> - Override the auto-generated mime type, defaults to the mime type for +source+ extension.
|
268
|
+
# * <tt>:as</tt> - Override the auto-generated value for as attribute, calculated using +source+ extension and mime type.
|
269
|
+
# * <tt>:crossorigin</tt> - Specify the crossorigin attribute, required to load cross-origin resources.
|
270
|
+
# * <tt>:nopush</tt> - Specify if the use of server push is not desired for the resource. Defaults to +false+.
|
271
|
+
# * <tt>:integrity</tt> - Specify the integrity attribute.
|
272
|
+
#
|
273
|
+
# ==== Examples
|
274
|
+
#
|
275
|
+
# preload_link_tag("custom_theme.css")
|
276
|
+
# # => <link rel="preload" href="/assets/custom_theme.css" as="style" type="text/css" />
|
277
|
+
#
|
278
|
+
# preload_link_tag("/videos/video.webm")
|
279
|
+
# # => <link rel="preload" href="/videos/video.mp4" as="video" type="video/webm" />
|
280
|
+
#
|
281
|
+
# preload_link_tag(post_path(format: :json), as: "fetch")
|
282
|
+
# # => <link rel="preload" href="/posts.json" as="fetch" type="application/json" />
|
283
|
+
#
|
284
|
+
# preload_link_tag("worker.js", as: "worker")
|
285
|
+
# # => <link rel="preload" href="/assets/worker.js" as="worker" type="text/javascript" />
|
286
|
+
#
|
287
|
+
# preload_link_tag("//example.com/font.woff2")
|
288
|
+
# # => <link rel="preload" href="//example.com/font.woff2" as="font" type="font/woff2" crossorigin="anonymous"/>
|
289
|
+
#
|
290
|
+
# preload_link_tag("//example.com/font.woff2", crossorigin: "use-credentials")
|
291
|
+
# # => <link rel="preload" href="//example.com/font.woff2" as="font" type="font/woff2" crossorigin="use-credentials" />
|
292
|
+
#
|
293
|
+
# preload_link_tag("/media/audio.ogg", nopush: true)
|
294
|
+
# # => <link rel="preload" href="/media/audio.ogg" as="audio" type="audio/ogg" />
|
295
|
+
#
|
296
|
+
def preload_link_tag(source, options = {})
|
297
|
+
href = asset_path(source, skip_pipeline: options.delete(:skip_pipeline))
|
298
|
+
extname = File.extname(source).downcase.delete(".")
|
299
|
+
mime_type = options.delete(:type) || Template::Types[extname]&.to_s
|
300
|
+
as_type = options.delete(:as) || resolve_link_as(extname, mime_type)
|
301
|
+
crossorigin = options.delete(:crossorigin)
|
302
|
+
crossorigin = "anonymous" if crossorigin == true || (crossorigin.blank? && as_type == "font")
|
303
|
+
integrity = options[:integrity]
|
304
|
+
nopush = options.delete(:nopush) || false
|
305
|
+
|
306
|
+
link_tag = tag.link(**{
|
307
|
+
rel: "preload",
|
308
|
+
href: href,
|
309
|
+
as: as_type,
|
310
|
+
type: mime_type,
|
311
|
+
crossorigin: crossorigin
|
312
|
+
}.merge!(options.symbolize_keys))
|
313
|
+
|
314
|
+
preload_link = "<#{href}>; rel=preload; as=#{as_type}"
|
315
|
+
preload_link += "; type=#{mime_type}" if mime_type
|
316
|
+
preload_link += "; crossorigin=#{crossorigin}" if crossorigin
|
317
|
+
preload_link += "; integrity=#{integrity}" if integrity
|
318
|
+
preload_link += "; nopush" if nopush
|
319
|
+
|
320
|
+
send_preload_links_header([preload_link])
|
321
|
+
|
322
|
+
link_tag
|
323
|
+
end
|
324
|
+
|
173
325
|
# Returns an HTML image tag for the +source+. The +source+ can be a full
|
174
|
-
# path or
|
326
|
+
# path, a file, or an Active Storage attachment.
|
175
327
|
#
|
176
328
|
# ==== Options
|
177
329
|
#
|
178
330
|
# You can add HTML attributes using the +options+. The +options+ supports
|
179
|
-
#
|
331
|
+
# additional keys for convenience and conformance:
|
180
332
|
#
|
181
|
-
# * <tt>:alt</tt> - If no alt text is given, the file name part of the
|
182
|
-
# +source+ is used (capitalized and without the extension)
|
183
333
|
# * <tt>:size</tt> - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes
|
184
334
|
# width="30" and height="45", and "50" becomes width="50" and height="50".
|
185
335
|
# <tt>:size</tt> will be ignored if the value is not in the correct format.
|
336
|
+
# * <tt>:srcset</tt> - If supplied as a hash or array of <tt>[source, descriptor]</tt>
|
337
|
+
# pairs, each image path will be expanded before the list is formatted as a string.
|
186
338
|
#
|
187
339
|
# ==== Examples
|
188
340
|
#
|
341
|
+
# Assets (images that are part of your app):
|
342
|
+
#
|
189
343
|
# image_tag("icon")
|
190
|
-
# # => <img
|
344
|
+
# # => <img src="/assets/icon" />
|
191
345
|
# image_tag("icon.png")
|
192
|
-
# # => <img
|
346
|
+
# # => <img src="/assets/icon.png" />
|
193
347
|
# image_tag("icon.png", size: "16x10", alt: "Edit Entry")
|
194
348
|
# # => <img src="/assets/icon.png" width="16" height="10" alt="Edit Entry" />
|
195
349
|
# image_tag("/icons/icon.gif", size: "16")
|
196
|
-
# # => <img src="/icons/icon.gif" width="16" height="16"
|
350
|
+
# # => <img src="/icons/icon.gif" width="16" height="16" />
|
197
351
|
# image_tag("/icons/icon.gif", height: '32', width: '32')
|
198
|
-
# # => <img
|
352
|
+
# # => <img height="32" src="/icons/icon.gif" width="32" />
|
199
353
|
# image_tag("/icons/icon.gif", class: "menu_icon")
|
200
|
-
# # => <img
|
201
|
-
|
354
|
+
# # => <img class="menu_icon" src="/icons/icon.gif" />
|
355
|
+
# image_tag("/icons/icon.gif", data: { title: 'Rails Application' })
|
356
|
+
# # => <img data-title="Rails Application" src="/icons/icon.gif" />
|
357
|
+
# image_tag("icon.png", srcset: { "icon_2x.png" => "2x", "icon_4x.png" => "4x" })
|
358
|
+
# # => <img src="/assets/icon.png" srcset="/assets/icon_2x.png 2x, /assets/icon_4x.png 4x">
|
359
|
+
# image_tag("pic.jpg", srcset: [["pic_1024.jpg", "1024w"], ["pic_1980.jpg", "1980w"]], sizes: "100vw")
|
360
|
+
# # => <img src="/assets/pic.jpg" srcset="/assets/pic_1024.jpg 1024w, /assets/pic_1980.jpg 1980w" sizes="100vw">
|
361
|
+
#
|
362
|
+
# Active Storage blobs (images that are uploaded by the users of your app):
|
363
|
+
#
|
364
|
+
# image_tag(user.avatar)
|
365
|
+
# # => <img src="/rails/active_storage/blobs/.../tiger.jpg" />
|
366
|
+
# image_tag(user.avatar.variant(resize_to_limit: [100, 100]))
|
367
|
+
# # => <img src="/rails/active_storage/representations/.../tiger.jpg" />
|
368
|
+
# image_tag(user.avatar.variant(resize_to_limit: [100, 100]), size: '100')
|
369
|
+
# # => <img width="100" height="100" src="/rails/active_storage/representations/.../tiger.jpg" />
|
370
|
+
def image_tag(source, options = {})
|
202
371
|
options = options.symbolize_keys
|
372
|
+
check_for_image_tag_errors(options)
|
373
|
+
skip_pipeline = options.delete(:skip_pipeline)
|
203
374
|
|
204
|
-
|
375
|
+
options[:src] = resolve_image_source(source, skip_pipeline)
|
205
376
|
|
206
|
-
|
207
|
-
options[:
|
377
|
+
if options[:srcset] && !options[:srcset].is_a?(String)
|
378
|
+
options[:srcset] = options[:srcset].map do |src_path, size|
|
379
|
+
src_path = path_to_image(src_path, skip_pipeline: skip_pipeline)
|
380
|
+
"#{src_path} #{size}"
|
381
|
+
end.join(", ")
|
208
382
|
end
|
209
383
|
|
210
384
|
options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size]
|
211
385
|
tag("img", options)
|
212
386
|
end
|
213
387
|
|
214
|
-
# Returns
|
215
|
-
# The +src+ argument is meant to be an image file path.
|
216
|
-
# The method removes the basename of the file path and the digest,
|
217
|
-
# if any. It also removes hyphens and underscores from file names and
|
218
|
-
# replaces them with spaces, returning a space-separated, titleized
|
219
|
-
# string.
|
220
|
-
#
|
221
|
-
# ==== Examples
|
222
|
-
#
|
223
|
-
# image_alt('rails.png')
|
224
|
-
# # => Rails
|
225
|
-
#
|
226
|
-
# image_alt('hyphenated-file-name.png')
|
227
|
-
# # => Hyphenated file name
|
228
|
-
#
|
229
|
-
# image_alt('underscored_file_name.png')
|
230
|
-
# # => Underscored file name
|
231
|
-
def image_alt(src)
|
232
|
-
File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').tr('-_', ' ').capitalize
|
233
|
-
end
|
234
|
-
|
235
|
-
# Returns an html video tag for the +sources+. If +sources+ is a string,
|
388
|
+
# Returns an HTML video tag for the +sources+. If +sources+ is a string,
|
236
389
|
# a single video tag will be returned. If +sources+ is an array, a video
|
237
390
|
# tag with nested source tags for each source will be returned. The
|
238
|
-
# +sources+ can be full paths or files that
|
391
|
+
# +sources+ can be full paths or files that exist in your public videos
|
239
392
|
# directory.
|
240
393
|
#
|
241
394
|
# ==== Options
|
242
|
-
#
|
243
|
-
#
|
395
|
+
#
|
396
|
+
# When the last parameter is a hash you can add HTML attributes using that
|
397
|
+
# parameter. The following options are supported:
|
244
398
|
#
|
245
399
|
# * <tt>:poster</tt> - Set an image (like a screenshot) to be shown
|
246
400
|
# before the video loads. The path is calculated like the +src+ of +image_tag+.
|
247
401
|
# * <tt>:size</tt> - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes
|
248
402
|
# width="30" and height="45", and "50" becomes width="50" and height="50".
|
249
403
|
# <tt>:size</tt> will be ignored if the value is not in the correct format.
|
404
|
+
# * <tt>:poster_skip_pipeline</tt> will bypass the asset pipeline when using
|
405
|
+
# the <tt>:poster</tt> option instead using an asset in the public folder.
|
250
406
|
#
|
251
407
|
# ==== Examples
|
252
408
|
#
|
253
409
|
# video_tag("trailer")
|
254
|
-
# # => <video src="/videos/trailer"
|
410
|
+
# # => <video src="/videos/trailer"></video>
|
255
411
|
# video_tag("trailer.ogg")
|
256
|
-
# # => <video src="/videos/trailer.ogg"
|
257
|
-
# video_tag("trailer.ogg", controls: true,
|
258
|
-
# # => <video
|
412
|
+
# # => <video src="/videos/trailer.ogg"></video>
|
413
|
+
# video_tag("trailer.ogg", controls: true, preload: 'none')
|
414
|
+
# # => <video preload="none" controls="controls" src="/videos/trailer.ogg"></video>
|
259
415
|
# video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png")
|
260
|
-
# # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png"
|
416
|
+
# # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png"></video>
|
417
|
+
# video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png", poster_skip_pipeline: true)
|
418
|
+
# # => <video src="/videos/trailer.m4v" width="16" height="10" poster="screenshot.png"></video>
|
261
419
|
# video_tag("/trailers/hd.avi", size: "16x16")
|
262
|
-
# # => <video src="/trailers/hd.avi" width="16" height="16"
|
420
|
+
# # => <video src="/trailers/hd.avi" width="16" height="16"></video>
|
263
421
|
# video_tag("/trailers/hd.avi", size: "16")
|
264
|
-
# # => <video height="16" src="/trailers/hd.avi" width="16"
|
422
|
+
# # => <video height="16" src="/trailers/hd.avi" width="16"></video>
|
265
423
|
# video_tag("/trailers/hd.avi", height: '32', width: '32')
|
266
|
-
# # => <video height="32" src="/trailers/hd.avi" width="32"
|
424
|
+
# # => <video height="32" src="/trailers/hd.avi" width="32"></video>
|
267
425
|
# video_tag("trailer.ogg", "trailer.flv")
|
268
426
|
# # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
|
269
427
|
# video_tag(["trailer.ogg", "trailer.flv"])
|
@@ -271,52 +429,100 @@ module ActionView
|
|
271
429
|
# video_tag(["trailer.ogg", "trailer.flv"], size: "160x120")
|
272
430
|
# # => <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
|
273
431
|
def video_tag(*sources)
|
274
|
-
|
275
|
-
|
276
|
-
|
432
|
+
options = sources.extract_options!.symbolize_keys
|
433
|
+
public_poster_folder = options.delete(:poster_skip_pipeline)
|
434
|
+
sources << options
|
435
|
+
multiple_sources_tag_builder("video", sources) do |tag_options|
|
436
|
+
tag_options[:poster] = path_to_image(tag_options[:poster], skip_pipeline: public_poster_folder) if tag_options[:poster]
|
437
|
+
tag_options[:width], tag_options[:height] = extract_dimensions(tag_options.delete(:size)) if tag_options[:size]
|
277
438
|
end
|
278
439
|
end
|
279
440
|
|
280
|
-
# Returns an HTML audio tag for the +
|
281
|
-
#
|
282
|
-
#
|
441
|
+
# Returns an HTML audio tag for the +sources+. If +sources+ is a string,
|
442
|
+
# a single audio tag will be returned. If +sources+ is an array, an audio
|
443
|
+
# tag with nested source tags for each source will be returned. The
|
444
|
+
# +sources+ can be full paths or files that exist in your public audios
|
445
|
+
# directory.
|
446
|
+
#
|
447
|
+
# When the last parameter is a hash you can add HTML attributes using that
|
448
|
+
# parameter.
|
283
449
|
#
|
284
450
|
# audio_tag("sound")
|
285
|
-
# # => <audio src="/audios/sound"
|
451
|
+
# # => <audio src="/audios/sound"></audio>
|
286
452
|
# audio_tag("sound.wav")
|
287
|
-
# # => <audio src="/audios/sound.wav"
|
453
|
+
# # => <audio src="/audios/sound.wav"></audio>
|
288
454
|
# audio_tag("sound.wav", autoplay: true, controls: true)
|
289
|
-
# # => <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav"
|
455
|
+
# # => <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav"></audio>
|
290
456
|
# audio_tag("sound.wav", "sound.mid")
|
291
457
|
# # => <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>
|
292
458
|
def audio_tag(*sources)
|
293
|
-
|
459
|
+
multiple_sources_tag_builder("audio", sources)
|
294
460
|
end
|
295
461
|
|
296
462
|
private
|
297
|
-
def
|
298
|
-
options
|
463
|
+
def multiple_sources_tag_builder(type, sources)
|
464
|
+
options = sources.extract_options!.symbolize_keys
|
465
|
+
skip_pipeline = options.delete(:skip_pipeline)
|
299
466
|
sources.flatten!
|
300
467
|
|
301
468
|
yield options if block_given?
|
302
469
|
|
303
470
|
if sources.size > 1
|
304
471
|
content_tag(type, options) do
|
305
|
-
safe_join sources.map { |source| tag("source", :
|
472
|
+
safe_join sources.map { |source| tag("source", src: send("path_to_#{type}", source, skip_pipeline: skip_pipeline)) }
|
306
473
|
end
|
307
474
|
else
|
308
|
-
options[:src] = send("path_to_#{type}", sources.first)
|
475
|
+
options[:src] = send("path_to_#{type}", sources.first, skip_pipeline: skip_pipeline)
|
309
476
|
content_tag(type, nil, options)
|
310
477
|
end
|
311
478
|
end
|
312
479
|
|
480
|
+
def resolve_image_source(source, skip_pipeline)
|
481
|
+
if source.is_a?(Symbol) || source.is_a?(String)
|
482
|
+
path_to_image(source, skip_pipeline: skip_pipeline)
|
483
|
+
else
|
484
|
+
polymorphic_url(source)
|
485
|
+
end
|
486
|
+
rescue NoMethodError => e
|
487
|
+
raise ArgumentError, "Can't resolve image into URL: #{e}"
|
488
|
+
end
|
489
|
+
|
313
490
|
def extract_dimensions(size)
|
314
|
-
|
315
|
-
|
316
|
-
|
491
|
+
size = size.to_s
|
492
|
+
if /\A\d+x\d+\z/.match?(size)
|
493
|
+
size.split("x")
|
494
|
+
elsif /\A\d+\z/.match?(size)
|
317
495
|
[size, size]
|
318
496
|
end
|
319
497
|
end
|
498
|
+
|
499
|
+
def check_for_image_tag_errors(options)
|
500
|
+
if options[:size] && (options[:height] || options[:width])
|
501
|
+
raise ArgumentError, "Cannot pass a :size option with a :height or :width option"
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
def resolve_link_as(extname, mime_type)
|
506
|
+
if extname == "js"
|
507
|
+
"script"
|
508
|
+
elsif extname == "css"
|
509
|
+
"style"
|
510
|
+
elsif extname == "vtt"
|
511
|
+
"track"
|
512
|
+
elsif (type = mime_type.to_s.split("/")[0]) && type.in?(%w(audio video font))
|
513
|
+
type
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
def send_preload_links_header(preload_links)
|
518
|
+
if respond_to?(:request) && request
|
519
|
+
request.send_early_hints("Link" => preload_links.join("\n"))
|
520
|
+
end
|
521
|
+
|
522
|
+
if respond_to?(:response) && response
|
523
|
+
response.headers["Link"] = [response.headers["Link"].presence, *preload_links].compact.join(",")
|
524
|
+
end
|
525
|
+
end
|
320
526
|
end
|
321
527
|
end
|
322
528
|
end
|