view_component 2.49.0 → 2.51.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of view_component might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/app/helpers/preview_helper.rb +2 -2
- data/docs/CHANGELOG.md +82 -0
- data/lib/rails/generators/abstract_generator.rb +1 -1
- data/lib/rails/generators/component/component_generator.rb +4 -2
- data/lib/rails/generators/locale/component_generator.rb +1 -1
- data/lib/rails/generators/preview/component_generator.rb +7 -0
- data/lib/rails/generators/preview/templates/component_preview.rb.tt +3 -1
- data/lib/rails/generators/rspec/templates/component_spec.rb.tt +2 -0
- data/lib/rails/generators/test_unit/templates/component_test.rb.tt +2 -0
- data/lib/view_component/base.rb +66 -37
- data/lib/view_component/compile_cache.rb +2 -1
- data/lib/view_component/compiler.rb +13 -10
- data/lib/view_component/content_areas.rb +1 -1
- data/lib/view_component/deprecation.rb +8 -0
- data/lib/view_component/docs_builder_component.html.erb +18 -0
- data/lib/view_component/docs_builder_component.rb +77 -0
- data/lib/view_component/engine.rb +4 -2
- data/lib/view_component/polymorphic_slots.rb +8 -5
- data/lib/view_component/preview.rb +2 -2
- data/lib/view_component/slotable.rb +1 -1
- data/lib/view_component/slotable_v2.rb +30 -4
- data/lib/view_component/translatable.rb +31 -1
- data/lib/view_component/version.rb +1 -1
- data/lib/view_component.rb +3 -2
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 163414625d8e2df6f2afddc821a6c849aef71cc8cd1ba458a533d90840e545cc
|
4
|
+
data.tar.gz: 3518269c96d4e62d3bcedae22871c7b260d2abf42e0c1e32703115185e69eeea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 531785b833a4d1a83b6929095b6a6a34076cb6576820884487a247d70c37bde2fedc47914633674c59a888dc7270526660e121f4980f2bcb85625ed6009607c4
|
7
|
+
data.tar.gz: e4c8b2694c7f70c529d56dcced18f66d511e909e1f36abc604a58402d1332caecde59d40c065668454af8abf6d53578469539ac1f4366650ad286856b50776fa
|
@@ -27,8 +27,8 @@ module PreviewHelper
|
|
27
27
|
end.flatten
|
28
28
|
|
29
29
|
# Search for templates the contain `html`.
|
30
|
-
matching_templates = all_template_paths.find_all do |
|
31
|
-
|
30
|
+
matching_templates = all_template_paths.find_all do |path|
|
31
|
+
path =~ /#{template_identifier}*.(html)/
|
32
32
|
end
|
33
33
|
|
34
34
|
# In-case of a conflict due to multiple template files with
|
data/docs/CHANGELOG.md
CHANGED
@@ -7,6 +7,82 @@ title: Changelog
|
|
7
7
|
|
8
8
|
## main
|
9
9
|
|
10
|
+
## 2.51.0
|
11
|
+
|
12
|
+
* Update the docs only when releasing a new version.
|
13
|
+
|
14
|
+
*Hans Lemuet*
|
15
|
+
|
16
|
+
* Alphabetize companies using ViewComponent and add Brightline to the list.
|
17
|
+
|
18
|
+
*Jack Schuss*
|
19
|
+
|
20
|
+
* Add CMYK value for ViewComponent Red color on logo page.
|
21
|
+
|
22
|
+
*Dylan Smith*
|
23
|
+
|
24
|
+
* Improve performance by moving template compilation from `#render_in` to `#render_template_for`.
|
25
|
+
|
26
|
+
*Cameron Dutro*
|
27
|
+
|
28
|
+
## 2.50.0
|
29
|
+
|
30
|
+
* Add tests for `layout` usage when rendering via controller.
|
31
|
+
|
32
|
+
*Felipe Sateler*
|
33
|
+
|
34
|
+
* Support returning Arrays from i18n files, and support marking them as HTML-safe translations.
|
35
|
+
|
36
|
+
*foca*
|
37
|
+
|
38
|
+
* Add Cometeer and Framework to users list.
|
39
|
+
|
40
|
+
*Elia Schito*
|
41
|
+
|
42
|
+
* Update Microsoft Vale styles.
|
43
|
+
|
44
|
+
*Simon Fish*
|
45
|
+
|
46
|
+
* Fix example in testing guide for how to setup default Rails tests.
|
47
|
+
|
48
|
+
*Steven Hansen*
|
49
|
+
|
50
|
+
* Update benchmark script to render multiple components/partials instead of a single instance per-run.
|
51
|
+
|
52
|
+
*Blake Williams*
|
53
|
+
|
54
|
+
* Add predicate methods `#{slot_name}?` to slots.
|
55
|
+
|
56
|
+
*Hans Lemuet*
|
57
|
+
|
58
|
+
* Use a dedicated deprecation instance, silence it while testing.
|
59
|
+
|
60
|
+
*Max Beizer, Hans Lemuet, Elia Schito*
|
61
|
+
|
62
|
+
* Fix Ruby warnings.
|
63
|
+
|
64
|
+
*Hans Lemuet*
|
65
|
+
|
66
|
+
* Place all generator options under `config.generate` namespace.
|
67
|
+
|
68
|
+
*Simon Fish*
|
69
|
+
|
70
|
+
* Allow preview generator to use provided component attributes.
|
71
|
+
* Add config option `config.view_component.generate.preview` to enable project-wide preview generation.
|
72
|
+
* Ensure all generated `.rb` files include `# frozen_string_literal: true` statement.
|
73
|
+
|
74
|
+
*Bob Maerten*
|
75
|
+
|
76
|
+
* Add Shogun to users list.
|
77
|
+
|
78
|
+
*Bernie Chiu*
|
79
|
+
|
80
|
+
## 2.49.1
|
81
|
+
|
82
|
+
* Patch XSS vulnerability in `ViewComponent::Translatable` module caused by improperly escaped interpolation arguments.
|
83
|
+
|
84
|
+
*Cameron Dutro*
|
85
|
+
|
10
86
|
## 2.49.0
|
11
87
|
|
12
88
|
* Fix path handling for evaluated view components that broke in Ruby 3.1.
|
@@ -613,6 +689,12 @@ title: Changelog
|
|
613
689
|
|
614
690
|
*Joel Hawksley*
|
615
691
|
|
692
|
+
## 2.31.2
|
693
|
+
|
694
|
+
* Patch XSS vulnerability in `ViewComponent::Translatable` module caused by improperly escaped interpolation arguments.
|
695
|
+
|
696
|
+
*Cameron Dutro*
|
697
|
+
|
616
698
|
## 2.31.1
|
617
699
|
|
618
700
|
* Fix `DEPRECATION WARNING: before_render_check` when compiling `ViewComponent::Base`
|
@@ -11,11 +11,13 @@ module Rails
|
|
11
11
|
|
12
12
|
argument :attributes, type: :array, default: [], banner: "attribute"
|
13
13
|
check_class_collision suffix: "Component"
|
14
|
+
|
14
15
|
class_option :inline, type: :boolean, default: false
|
16
|
+
class_option :locale, type: :boolean, default: ViewComponent::Base.generate.locale
|
15
17
|
class_option :parent, type: :string, desc: "The parent class for the generated component"
|
16
|
-
class_option :
|
18
|
+
class_option :preview, type: :boolean, default: ViewComponent::Base.generate.preview
|
17
19
|
class_option :sidecar, type: :boolean, default: false
|
18
|
-
class_option :
|
20
|
+
class_option :stimulus, type: :boolean, default: ViewComponent::Base.generate.stimulus_controller
|
19
21
|
|
20
22
|
def create_component_file
|
21
23
|
template "component.rb", File.join(component_path, class_path, "#{file_name}_component.rb")
|
@@ -12,7 +12,7 @@ module Locale
|
|
12
12
|
class_option :sidecar, type: :boolean, default: false
|
13
13
|
|
14
14
|
def create_locale_file
|
15
|
-
if ViewComponent::Base.
|
15
|
+
if ViewComponent::Base.generate.distinct_locale_files
|
16
16
|
I18n.available_locales.each do |locale|
|
17
17
|
create_file destination(locale), translations_hash([locale]).to_yaml
|
18
18
|
end
|
@@ -5,6 +5,7 @@ module Preview
|
|
5
5
|
class ComponentGenerator < ::Rails::Generators::NamedBase
|
6
6
|
source_root File.expand_path("templates", __dir__)
|
7
7
|
|
8
|
+
argument :attributes, type: :array, default: [], banner: "attribute"
|
8
9
|
check_class_collision suffix: "ComponentPreview"
|
9
10
|
|
10
11
|
def create_preview_file
|
@@ -20,6 +21,12 @@ module Preview
|
|
20
21
|
def file_name
|
21
22
|
@_file_name ||= super.sub(/_component\z/i, "")
|
22
23
|
end
|
24
|
+
|
25
|
+
def render_signature
|
26
|
+
return if attributes.blank?
|
27
|
+
|
28
|
+
attributes.map { |attr| %(#{attr.name}: "#{attr.name}") }.join(", ")
|
29
|
+
end
|
23
30
|
end
|
24
31
|
end
|
25
32
|
end
|
data/lib/view_component/base.rb
CHANGED
@@ -40,6 +40,23 @@ module ViewComponent
|
|
40
40
|
# noop
|
41
41
|
end
|
42
42
|
|
43
|
+
# @!macro [attach] deprecated_generate_mattr_accessor
|
44
|
+
# @method generate_$1
|
45
|
+
# @deprecated Use `#generate.$1` instead. Will be removed in v3.0.0.
|
46
|
+
def self._deprecated_generate_mattr_accessor(name)
|
47
|
+
define_singleton_method("generate_#{name}".to_sym) do
|
48
|
+
generate.public_send(name)
|
49
|
+
end
|
50
|
+
define_singleton_method("generate_#{name}=".to_sym) do |value|
|
51
|
+
generate.public_send("#{name}=".to_sym, value)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
_deprecated_generate_mattr_accessor :distinct_locale_files
|
56
|
+
_deprecated_generate_mattr_accessor :locale
|
57
|
+
_deprecated_generate_mattr_accessor :sidecar
|
58
|
+
_deprecated_generate_mattr_accessor :stimulus_controller
|
59
|
+
|
43
60
|
# Entrypoint for rendering components.
|
44
61
|
#
|
45
62
|
# - `view_context`: ActionView context from calling view
|
@@ -49,8 +66,6 @@ module ViewComponent
|
|
49
66
|
#
|
50
67
|
# @return [String]
|
51
68
|
def render_in(view_context, &block)
|
52
|
-
self.class.compile(raise_errors: true)
|
53
|
-
|
54
69
|
@view_context = view_context
|
55
70
|
self.__vc_original_view_context ||= view_context
|
56
71
|
|
@@ -95,6 +110,16 @@ module ViewComponent
|
|
95
110
|
@current_template = old_current_template
|
96
111
|
end
|
97
112
|
|
113
|
+
# :nocov:
|
114
|
+
def render_template_for(variant = nil)
|
115
|
+
# Force compilation here so the compiler always redefines render_template_for.
|
116
|
+
# This is mostly a safeguard to prevent infinite recursion.
|
117
|
+
self.class.compile(raise_errors: true, force: true)
|
118
|
+
# .compile replaces this method; call the new one
|
119
|
+
render_template_for(variant)
|
120
|
+
end
|
121
|
+
# :nocov:
|
122
|
+
|
98
123
|
# EXPERIMENTAL: Optional content to be returned after the rendered template.
|
99
124
|
#
|
100
125
|
# @return [String]
|
@@ -218,14 +243,11 @@ module ViewComponent
|
|
218
243
|
# @param variant [Symbol] The variant to be used by the component.
|
219
244
|
# @return [self]
|
220
245
|
def with_variant(variant)
|
221
|
-
ActiveSupport::Deprecation.warn(
|
222
|
-
"`with_variant` is deprecated and will be removed in ViewComponent v3.0.0."
|
223
|
-
)
|
224
|
-
|
225
246
|
@__vc_variant = variant
|
226
247
|
|
227
248
|
self
|
228
249
|
end
|
250
|
+
deprecate :with_variant, deprecator: ViewComponent::Deprecation
|
229
251
|
|
230
252
|
# The current request. Use sparingly as doing so introduces coupling that
|
231
253
|
# inhibits encapsulation & reuse, often making testing difficult.
|
@@ -271,56 +293,63 @@ module ViewComponent
|
|
271
293
|
#
|
272
294
|
mattr_accessor :render_monkey_patch_enabled, instance_writer: false, default: true
|
273
295
|
|
274
|
-
#
|
296
|
+
# Path for component files
|
275
297
|
#
|
276
|
-
# config.view_component.
|
298
|
+
# config.view_component.view_component_path = "app/my_components"
|
277
299
|
#
|
278
|
-
# Defaults to `
|
300
|
+
# Defaults to `app/components`.
|
279
301
|
#
|
280
|
-
mattr_accessor :
|
302
|
+
mattr_accessor :view_component_path, instance_writer: false, default: "app/components"
|
281
303
|
|
282
|
-
#
|
304
|
+
# Parent class for generated components
|
283
305
|
#
|
284
|
-
# config.view_component.
|
306
|
+
# config.view_component.component_parent_class = "MyBaseComponent"
|
285
307
|
#
|
286
|
-
# Defaults to
|
308
|
+
# Defaults to nil. If this is falsy, generators will use
|
309
|
+
# "ApplicationComponent" if defined, "ViewComponent::Base" otherwise.
|
287
310
|
#
|
288
|
-
mattr_accessor :
|
311
|
+
mattr_accessor :component_parent_class, instance_writer: false
|
289
312
|
|
290
|
-
#
|
313
|
+
# Configuration for generators.
|
291
314
|
#
|
292
|
-
#
|
315
|
+
# All options under this namespace default to `false` unless otherwise
|
316
|
+
# stated.
|
293
317
|
#
|
294
|
-
#
|
318
|
+
# #### #sidecar
|
295
319
|
#
|
296
|
-
#
|
297
|
-
# Fallback on `[:en]` when no available_locales is defined.
|
320
|
+
# Always generate a component with a sidecar directory:
|
298
321
|
#
|
299
|
-
|
300
|
-
|
301
|
-
# Path for component files
|
322
|
+
# config.view_component.generate.sidecar = true
|
302
323
|
#
|
303
|
-
#
|
324
|
+
# #### #stimulus_controller
|
304
325
|
#
|
305
|
-
#
|
326
|
+
# Always generate a Stimulus controller alongside the component:
|
306
327
|
#
|
307
|
-
|
308
|
-
|
309
|
-
# Parent class for generated components
|
328
|
+
# config.view_component.generate.stimulus_controller = true
|
310
329
|
#
|
311
|
-
#
|
330
|
+
# #### #locale
|
312
331
|
#
|
313
|
-
#
|
332
|
+
# Always generate translations file alongside the component:
|
314
333
|
#
|
315
|
-
|
316
|
-
|
317
|
-
#
|
334
|
+
# config.view_component.generate.locale = true
|
335
|
+
#
|
336
|
+
# #### #distinct_locale_files
|
337
|
+
#
|
338
|
+
# Always generate as many translations files as available locales:
|
339
|
+
#
|
340
|
+
# config.view_component.generate.distinct_locale_files = true
|
341
|
+
#
|
342
|
+
# One file will be generated for each configured `I18n.available_locales`,
|
343
|
+
# falling back to `[:en]` when no `available_locales` is defined.
|
344
|
+
#
|
345
|
+
# #### #preview
|
318
346
|
#
|
319
|
-
#
|
347
|
+
# Always generate preview alongside the component:
|
320
348
|
#
|
321
|
-
#
|
349
|
+
# config.view_component.generate.preview = true
|
322
350
|
#
|
323
|
-
|
351
|
+
# Defaults to `false`.
|
352
|
+
mattr_accessor :generate, instance_writer: false, default: ActiveSupport::OrderedOptions.new(false)
|
324
353
|
|
325
354
|
class << self
|
326
355
|
# @private
|
@@ -424,8 +453,8 @@ module ViewComponent
|
|
424
453
|
# Do as much work as possible in this step, as doing so reduces the amount
|
425
454
|
# of work done each time a component is rendered.
|
426
455
|
# @private
|
427
|
-
def compile(raise_errors: false)
|
428
|
-
compiler.compile(raise_errors: raise_errors)
|
456
|
+
def compile(raise_errors: false, force: false)
|
457
|
+
compiler.compile(raise_errors: raise_errors, force: force)
|
429
458
|
end
|
430
459
|
|
431
460
|
# @private
|
@@ -27,12 +27,11 @@ module ViewComponent
|
|
27
27
|
self.class.mode == DEVELOPMENT_MODE
|
28
28
|
end
|
29
29
|
|
30
|
-
def compile(raise_errors: false)
|
31
|
-
return if compiled?
|
30
|
+
def compile(raise_errors: false, force: false)
|
31
|
+
return if compiled? && !force
|
32
|
+
return if component_class == ViewComponent::Base
|
32
33
|
|
33
34
|
with_lock do
|
34
|
-
CompileCache.invalidate_class!(component_class)
|
35
|
-
|
36
35
|
subclass_instance_methods = component_class.instance_methods(false)
|
37
36
|
|
38
37
|
if subclass_instance_methods.include?(:with_content) && raise_errors
|
@@ -49,7 +48,7 @@ module ViewComponent
|
|
49
48
|
end
|
50
49
|
|
51
50
|
if subclass_instance_methods.include?(:before_render_check)
|
52
|
-
|
51
|
+
ViewComponent::Deprecation.warn(
|
53
52
|
"`#before_render_check` will be removed in v3.0.0.\n\n" \
|
54
53
|
"To fix this issue, use `#before_render` instead."
|
55
54
|
)
|
@@ -65,8 +64,8 @@ module ViewComponent
|
|
65
64
|
# as Ruby warns when redefining a method.
|
66
65
|
method_name = call_method_name(template[:variant])
|
67
66
|
|
68
|
-
if component_class.instance_methods.include?(method_name.to_sym)
|
69
|
-
component_class.send(:
|
67
|
+
if component_class.instance_methods(false).include?(method_name.to_sym)
|
68
|
+
component_class.send(:remove_method, method_name.to_sym)
|
70
69
|
end
|
71
70
|
|
72
71
|
component_class.class_eval <<-RUBY, template[:path], -1
|
@@ -93,14 +92,18 @@ module ViewComponent
|
|
93
92
|
end
|
94
93
|
end
|
95
94
|
|
95
|
+
def reset_render_template_for
|
96
|
+
if component_class.instance_methods(false).include?(:render_template_for)
|
97
|
+
component_class.send(:remove_method, :render_template_for)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
96
101
|
private
|
97
102
|
|
98
103
|
attr_reader :component_class
|
99
104
|
|
100
105
|
def define_render_template_for
|
101
|
-
|
102
|
-
component_class.send(:undef_method, :render_template_for)
|
103
|
-
end
|
106
|
+
reset_render_template_for
|
104
107
|
|
105
108
|
variant_elsifs = variants.compact.uniq.map do |variant|
|
106
109
|
"elsif variant.to_sym == :#{variant}\n #{call_method_name(variant)}"
|
@@ -31,7 +31,7 @@ module ViewComponent
|
|
31
31
|
|
32
32
|
class_methods do
|
33
33
|
def with_content_areas(*areas)
|
34
|
-
|
34
|
+
ViewComponent::Deprecation.warn(
|
35
35
|
"`with_content_areas` is deprecated and will be removed in ViewComponent v3.0.0.\n\n" \
|
36
36
|
"Use slots (https://viewcomponent.org/guide/slots.html) instead."
|
37
37
|
)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: API
|
4
|
+
nav_order: 3
|
5
|
+
---
|
6
|
+
|
7
|
+
<!-- Warning: AUTO-GENERATED file, don't edit. Add code comments to your Ruby instead <3 -->
|
8
|
+
|
9
|
+
# API
|
10
|
+
|
11
|
+
<% @sections.each do |section| %>
|
12
|
+
## <%= section.heading %>
|
13
|
+
|
14
|
+
<% section.methods.each do |method| %>
|
15
|
+
### <%== render ViewComponent::DocsBuilderComponent::MethodDoc.new(method) %>
|
16
|
+
|
17
|
+
<% end %>
|
18
|
+
<% end %>
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
class DocsBuilderComponent < Base
|
5
|
+
class Section < Struct.new(:heading, :methods, :show_types, keyword_init: true)
|
6
|
+
def initialize(heading: nil, methods: [], show_types: true)
|
7
|
+
methods.sort_by! { |method| method[:name] }
|
8
|
+
super
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class MethodDoc < ViewComponent::Base
|
13
|
+
def initialize(method, section: Section.new(show_types: true))
|
14
|
+
@method = method
|
15
|
+
@section = section
|
16
|
+
end
|
17
|
+
|
18
|
+
def show_types?
|
19
|
+
@section.show_types
|
20
|
+
end
|
21
|
+
|
22
|
+
def deprecated?
|
23
|
+
@method.tag(:deprecated).present?
|
24
|
+
end
|
25
|
+
|
26
|
+
def suffix
|
27
|
+
" (Deprecated)" if deprecated?
|
28
|
+
end
|
29
|
+
|
30
|
+
def types
|
31
|
+
" → [#{@method.tag(:return).types.join(',')}]" if @method.tag(:return)&.types && show_types?
|
32
|
+
end
|
33
|
+
|
34
|
+
def signature_or_name
|
35
|
+
@method.signature ? @method.signature.gsub("def ", "") : @method.name
|
36
|
+
end
|
37
|
+
|
38
|
+
def separator
|
39
|
+
@method.sep
|
40
|
+
end
|
41
|
+
|
42
|
+
def docstring
|
43
|
+
@method.docstring
|
44
|
+
end
|
45
|
+
|
46
|
+
def deprecation_text
|
47
|
+
@method.tag(:deprecated)&.text
|
48
|
+
end
|
49
|
+
|
50
|
+
def docstring_and_deprecation_text
|
51
|
+
<<~DOCS.strip
|
52
|
+
#{docstring}
|
53
|
+
|
54
|
+
#{"_#{deprecation_text}_" if deprecated?}
|
55
|
+
DOCS
|
56
|
+
end
|
57
|
+
|
58
|
+
def call
|
59
|
+
<<~DOCS.chomp
|
60
|
+
#{separator}#{signature_or_name}#{types}#{suffix}
|
61
|
+
|
62
|
+
#{docstring_and_deprecation_text}
|
63
|
+
DOCS
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# { heading: String, public_only: Boolean, show_types: Boolean}
|
68
|
+
def initialize(sections: [])
|
69
|
+
@sections = sections
|
70
|
+
end
|
71
|
+
|
72
|
+
# deprecation
|
73
|
+
# return
|
74
|
+
# only public methods
|
75
|
+
# sig with types or name
|
76
|
+
end
|
77
|
+
end
|
@@ -27,7 +27,7 @@ module ViewComponent
|
|
27
27
|
)
|
28
28
|
|
29
29
|
if options.preview_path.present?
|
30
|
-
|
30
|
+
ViewComponent::Deprecation.warn(
|
31
31
|
"`preview_path` will be removed in v3.0.0. Use `preview_paths` instead."
|
32
32
|
)
|
33
33
|
options.preview_paths << options.preview_path
|
@@ -155,7 +155,9 @@ end
|
|
155
155
|
|
156
156
|
# :nocov:
|
157
157
|
unless defined?(ViewComponent::Base)
|
158
|
-
|
158
|
+
require "view_component/deprecation"
|
159
|
+
|
160
|
+
ViewComponent::Deprecation.warn(
|
159
161
|
"This manually engine loading is deprecated and will be removed in v3.0.0. " \
|
160
162
|
"Remove `require \"view_component/engine\"`."
|
161
163
|
)
|
@@ -25,12 +25,19 @@ module ViewComponent
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def register_polymorphic_slot(slot_name, types, collection:)
|
28
|
+
unless types.empty?
|
29
|
+
getter_name = slot_name
|
30
|
+
|
31
|
+
define_method(getter_name) do
|
32
|
+
get_slot(slot_name)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
28
36
|
renderable_hash = types.each_with_object({}) do |(poly_type, poly_callable), memo|
|
29
37
|
memo[poly_type] = define_slot(
|
30
38
|
"#{slot_name}_#{poly_type}", collection: collection, callable: poly_callable
|
31
39
|
)
|
32
40
|
|
33
|
-
getter_name = slot_name
|
34
41
|
setter_name =
|
35
42
|
if collection
|
36
43
|
"#{ActiveSupport::Inflector.singularize(slot_name)}_#{poly_type}"
|
@@ -38,10 +45,6 @@ module ViewComponent
|
|
38
45
|
"#{slot_name}_#{poly_type}"
|
39
46
|
end
|
40
47
|
|
41
|
-
define_method(getter_name) do
|
42
|
-
get_slot(slot_name)
|
43
|
-
end
|
44
|
-
|
45
48
|
define_method(setter_name) do |*args, &block|
|
46
49
|
set_polymorphic_slot(slot_name, poly_type, *args, &block)
|
47
50
|
end
|
@@ -73,8 +73,8 @@ module ViewComponent # :nodoc:
|
|
73
73
|
# Returns the relative path (from preview_path) to the preview example template if the template exists
|
74
74
|
def preview_example_template_path(example)
|
75
75
|
preview_path =
|
76
|
-
Array(preview_paths).detect do |
|
77
|
-
Dir["#{
|
76
|
+
Array(preview_paths).detect do |path|
|
77
|
+
Dir["#{path}/#{preview_name}_preview/#{example}.html.*"].first
|
78
78
|
end
|
79
79
|
|
80
80
|
if preview_path.nil?
|
@@ -23,7 +23,7 @@ module ViewComponent
|
|
23
23
|
# class_name: "Header" # class name string, used to instantiate Slot
|
24
24
|
# )
|
25
25
|
def with_slot(*slot_names, collection: false, class_name: nil)
|
26
|
-
|
26
|
+
ViewComponent::Deprecation.warn(
|
27
27
|
"`with_slot` is deprecated and will be removed in ViewComponent v3.0.0.\n" \
|
28
28
|
"Use the new slots API (https://viewcomponent.org/guide/slots.html) instead."
|
29
29
|
)
|
@@ -7,6 +7,11 @@ module ViewComponent
|
|
7
7
|
module SlotableV2
|
8
8
|
extend ActiveSupport::Concern
|
9
9
|
|
10
|
+
RESERVED_NAMES = {
|
11
|
+
singular: %i[content render].freeze,
|
12
|
+
plural: %i[contents renders].freeze,
|
13
|
+
}.freeze
|
14
|
+
|
10
15
|
# Setup component slot state
|
11
16
|
included do
|
12
17
|
# Hash of registered Slots
|
@@ -75,6 +80,10 @@ module ViewComponent
|
|
75
80
|
end
|
76
81
|
ruby2_keywords(slot_name.to_sym) if respond_to?(:ruby2_keywords, true)
|
77
82
|
|
83
|
+
define_method "#{slot_name}?" do
|
84
|
+
get_slot(slot_name).present?
|
85
|
+
end
|
86
|
+
|
78
87
|
register_slot(slot_name, collection: false, callable: callable)
|
79
88
|
end
|
80
89
|
|
@@ -140,6 +149,10 @@ module ViewComponent
|
|
140
149
|
end
|
141
150
|
end
|
142
151
|
|
152
|
+
define_method "#{slot_name}?" do
|
153
|
+
get_slot(slot_name).present?
|
154
|
+
end
|
155
|
+
|
143
156
|
register_slot(slot_name, collection: true, callable: callable)
|
144
157
|
end
|
145
158
|
|
@@ -197,24 +210,26 @@ module ViewComponent
|
|
197
210
|
end
|
198
211
|
|
199
212
|
def validate_plural_slot_name(slot_name)
|
200
|
-
if slot_name.to_sym
|
213
|
+
if RESERVED_NAMES[:plural].include?(slot_name.to_sym)
|
201
214
|
raise ArgumentError.new(
|
202
215
|
"#{self} declares a slot named #{slot_name}, which is a reserved word in the ViewComponent framework.\n\n" \
|
203
216
|
"To fix this issue, choose a different name."
|
204
217
|
)
|
205
218
|
end
|
206
219
|
|
220
|
+
raise_if_slot_ends_with_question_mark(slot_name)
|
207
221
|
raise_if_slot_registered(slot_name)
|
208
222
|
end
|
209
223
|
|
210
224
|
def validate_singular_slot_name(slot_name)
|
211
|
-
if slot_name.to_sym
|
225
|
+
if RESERVED_NAMES[:singular].include?(slot_name.to_sym)
|
212
226
|
raise ArgumentError.new(
|
213
227
|
"#{self} declares a slot named #{slot_name}, which is a reserved word in the ViewComponent framework.\n\n" \
|
214
228
|
"To fix this issue, choose a different name."
|
215
229
|
)
|
216
230
|
end
|
217
231
|
|
232
|
+
raise_if_slot_ends_with_question_mark(slot_name)
|
218
233
|
raise_if_slot_registered(slot_name)
|
219
234
|
end
|
220
235
|
|
@@ -227,6 +242,17 @@ module ViewComponent
|
|
227
242
|
)
|
228
243
|
end
|
229
244
|
end
|
245
|
+
|
246
|
+
def raise_if_slot_ends_with_question_mark(slot_name)
|
247
|
+
if slot_name.to_s.ends_with?("?")
|
248
|
+
raise ArgumentError.new(
|
249
|
+
"#{self} declares a slot named #{slot_name}, which ends with a question mark.\n\n"\
|
250
|
+
"This is not allowed because the ViewComponent framework already provides predicate "\
|
251
|
+
"methods ending in `?`.\n\n" \
|
252
|
+
"To fix this issue, choose a different name."
|
253
|
+
)
|
254
|
+
end
|
255
|
+
end
|
230
256
|
end
|
231
257
|
|
232
258
|
def get_slot(slot_name)
|
@@ -276,8 +302,8 @@ module ViewComponent
|
|
276
302
|
renderable_function = slot_definition[:renderable_function].bind(self)
|
277
303
|
renderable_value =
|
278
304
|
if block_given?
|
279
|
-
renderable_function.call(*args) do |*
|
280
|
-
view_context.capture(*
|
305
|
+
renderable_function.call(*args) do |*rargs|
|
306
|
+
view_context.capture(*rargs, &block)
|
281
307
|
end
|
282
308
|
else
|
283
309
|
renderable_function.call(*args)
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "erb"
|
3
4
|
require "set"
|
4
5
|
require "i18n"
|
5
6
|
require "action_view/helpers/translation_helper"
|
@@ -70,6 +71,10 @@ module ViewComponent
|
|
70
71
|
key = key&.to_s unless key.is_a?(String)
|
71
72
|
key = "#{i18n_scope}#{key}" if key.start_with?(".")
|
72
73
|
|
74
|
+
if HTML_SAFE_TRANSLATION_KEY.match?(key)
|
75
|
+
html_escape_translation_options!(options)
|
76
|
+
end
|
77
|
+
|
73
78
|
if key.start_with?(i18n_scope + ".")
|
74
79
|
translated =
|
75
80
|
catch(:exception) do
|
@@ -82,7 +87,7 @@ module ViewComponent
|
|
82
87
|
end
|
83
88
|
|
84
89
|
if HTML_SAFE_TRANSLATION_KEY.match?(key)
|
85
|
-
translated = translated
|
90
|
+
translated = html_safe_translation(translated)
|
86
91
|
end
|
87
92
|
|
88
93
|
translated
|
@@ -96,5 +101,30 @@ module ViewComponent
|
|
96
101
|
def i18n_scope
|
97
102
|
self.class.i18n_scope
|
98
103
|
end
|
104
|
+
|
105
|
+
def html_safe_translation(translation)
|
106
|
+
if translation.respond_to?(:map)
|
107
|
+
translation.map { |element| html_safe_translation(element) }
|
108
|
+
else
|
109
|
+
# It's assumed here that objects loaded by the i18n backend will respond to `#html_safe?`.
|
110
|
+
# It's reasonable that if we're in Rails, `active_support/core_ext/string/output_safety.rb`
|
111
|
+
# will provide this to `Object`.
|
112
|
+
translation.html_safe # rubocop:disable Rails/OutputSafety
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def html_escape_translation_options!(options)
|
119
|
+
options.each do |name, value|
|
120
|
+
unless i18n_option?(name) || (name == :count && value.is_a?(Numeric))
|
121
|
+
options[name] = ERB::Util.html_escape(value.to_s)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def i18n_option?(name)
|
127
|
+
(@i18n_option_names ||= I18n::RESERVED_KEYS.to_set).include?(name)
|
128
|
+
end
|
99
129
|
end
|
100
130
|
end
|
data/lib/view_component.rb
CHANGED
@@ -10,6 +10,7 @@ module ViewComponent
|
|
10
10
|
autoload :Compiler
|
11
11
|
autoload :CompileCache
|
12
12
|
autoload :ComponentError
|
13
|
+
autoload :Deprecation
|
13
14
|
autoload :Instrumentation
|
14
15
|
autoload :Preview
|
15
16
|
autoload :PreviewTemplateError
|
@@ -21,8 +22,8 @@ end
|
|
21
22
|
|
22
23
|
# :nocov:
|
23
24
|
if defined?(ViewComponent::Engine)
|
24
|
-
|
25
|
-
"
|
25
|
+
ViewComponent::Deprecation.warn(
|
26
|
+
"Manually loading the engine is deprecated and will be removed in v3.0.0. " \
|
26
27
|
"Remove `require \"view_component/engine\"`."
|
27
28
|
)
|
28
29
|
elsif defined?(Rails::Engine)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: view_component
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.51.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub Open Source
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-03-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -329,6 +329,9 @@ files:
|
|
329
329
|
- lib/view_component/compiler.rb
|
330
330
|
- lib/view_component/component_error.rb
|
331
331
|
- lib/view_component/content_areas.rb
|
332
|
+
- lib/view_component/deprecation.rb
|
333
|
+
- lib/view_component/docs_builder_component.html.erb
|
334
|
+
- lib/view_component/docs_builder_component.rb
|
332
335
|
- lib/view_component/engine.rb
|
333
336
|
- lib/view_component/instrumentation.rb
|
334
337
|
- lib/view_component/polymorphic_slots.rb
|
@@ -373,7 +376,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
373
376
|
- !ruby/object:Gem::Version
|
374
377
|
version: '0'
|
375
378
|
requirements: []
|
376
|
-
rubygems_version: 3.
|
379
|
+
rubygems_version: 3.2.32
|
377
380
|
signing_key:
|
378
381
|
specification_version: 4
|
379
382
|
summary: View components for Rails
|