view_component 2.48.0 → 2.50.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 +94 -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 +55 -34
- data/lib/view_component/compiler.rb +1 -1
- 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/instrumentation.rb +5 -3
- 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 +46 -12
- 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: 4ea90ad2ed037528865b7e6cf8b5850df938b624883a1508d85de52d8978c4fb
|
4
|
+
data.tar.gz: f2c1e558a700b9f79b3939785f3ff6c2f812a6e03b697f9b60f4aa6f6eaf54a0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9fd97f786e4382098f81e5ec6230b6ae550991641f83d74ecfd67d3eacea515cd899ab21264c8ac5b5de1caed7f35304eb9b6cba13f092e5abdb3a6dc45f6b06
|
7
|
+
data.tar.gz: b70d4f308f47c6937d8aa2e518564ebbc6313366168e6382f4c296823ed07489c7be333f8f47822e65d3a5159753b5d3934ead8cd4323c3777d0c22dde9870f8
|
@@ -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,94 @@ title: Changelog
|
|
7
7
|
|
8
8
|
## main
|
9
9
|
|
10
|
+
## 2.50.0
|
11
|
+
|
12
|
+
* Add tests for `layout` usage when rendering via controller.
|
13
|
+
|
14
|
+
*Felipe Sateler*
|
15
|
+
|
16
|
+
* Support returning Arrays from i18n files, and support marking them as HTML-safe translations.
|
17
|
+
|
18
|
+
*foca*
|
19
|
+
|
20
|
+
* Add Cometeer and Framework to users list.
|
21
|
+
|
22
|
+
*Elia Schito*
|
23
|
+
|
24
|
+
* Update Microsoft Vale styles.
|
25
|
+
|
26
|
+
*Simon Fish*
|
27
|
+
|
28
|
+
* Fix example in testing guide for how to setup default Rails tests.
|
29
|
+
|
30
|
+
*Steven Hansen*
|
31
|
+
|
32
|
+
* Update benchmark script to render multiple components/partials instead of a single instance per-run.
|
33
|
+
|
34
|
+
*Blake Williams*
|
35
|
+
|
36
|
+
* Add predicate methods `#{slot_name}?` to slots.
|
37
|
+
|
38
|
+
*Hans Lemuet*
|
39
|
+
|
40
|
+
* Use a dedicated deprecation instance, silence it while testing.
|
41
|
+
|
42
|
+
*Max Beizer, Hans Lemuet, Elia Schito*
|
43
|
+
|
44
|
+
* Fix Ruby warnings.
|
45
|
+
|
46
|
+
*Hans Lemuet*
|
47
|
+
|
48
|
+
* Place all generator options under `config.generate` namespace.
|
49
|
+
|
50
|
+
*Simon Fish*
|
51
|
+
|
52
|
+
* Allow preview generator to use provided component attributes.
|
53
|
+
* Add config option `config.view_component.generate.preview` to enable project-wide preview generation.
|
54
|
+
* Ensure all generated `.rb` files include `# frozen_string_literal: true` statement.
|
55
|
+
|
56
|
+
*Bob Maerten*
|
57
|
+
|
58
|
+
* Add Shogun to users list.
|
59
|
+
|
60
|
+
*Bernie Chiu*
|
61
|
+
|
62
|
+
## 2.49.1
|
63
|
+
|
64
|
+
* Patch XSS vulnerability in `ViewComponent::Translatable` module caused by improperly escaped interpolation arguments.
|
65
|
+
|
66
|
+
*Cameron Dutro*
|
67
|
+
|
68
|
+
## 2.49.0
|
69
|
+
|
70
|
+
* Fix path handling for evaluated view components that broke in Ruby 3.1.
|
71
|
+
|
72
|
+
*Adam Hess*
|
73
|
+
|
74
|
+
* Fix support for the `default:` option for a global translation.
|
75
|
+
|
76
|
+
*Elia Schito*
|
77
|
+
|
78
|
+
* Ensure i18n scope is a symbol to protect lookups.
|
79
|
+
|
80
|
+
*Simon Fish*
|
81
|
+
|
82
|
+
* Small update to preview docs to include rspec mention.
|
83
|
+
|
84
|
+
*Leigh Halliday*
|
85
|
+
|
86
|
+
* Small improvements to collection iteration docs.
|
87
|
+
|
88
|
+
*Brian O'Rourke*
|
89
|
+
|
90
|
+
* Add good and bad examples to `ViewComponents in practice`.
|
91
|
+
|
92
|
+
*Joel Hawksley*
|
93
|
+
|
94
|
+
* Add Ruby 3.1 and Rails 7.0 to CI
|
95
|
+
|
96
|
+
*Peter Goldstein*
|
97
|
+
|
10
98
|
## 2.48.0
|
11
99
|
|
12
100
|
* Correct path in example test command in Contributing docs.
|
@@ -583,6 +671,12 @@ title: Changelog
|
|
583
671
|
|
584
672
|
*Joel Hawksley*
|
585
673
|
|
674
|
+
## 2.31.2
|
675
|
+
|
676
|
+
* Patch XSS vulnerability in `ViewComponent::Translatable` module caused by improperly escaped interpolation arguments.
|
677
|
+
|
678
|
+
*Cameron Dutro*
|
679
|
+
|
586
680
|
## 2.31.1
|
587
681
|
|
588
682
|
* 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
|
@@ -218,14 +235,11 @@ module ViewComponent
|
|
218
235
|
# @param variant [Symbol] The variant to be used by the component.
|
219
236
|
# @return [self]
|
220
237
|
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
238
|
@__vc_variant = variant
|
226
239
|
|
227
240
|
self
|
228
241
|
end
|
242
|
+
deprecate :with_variant, deprecator: ViewComponent::Deprecation
|
229
243
|
|
230
244
|
# The current request. Use sparingly as doing so introduces coupling that
|
231
245
|
# inhibits encapsulation & reuse, often making testing difficult.
|
@@ -271,56 +285,63 @@ module ViewComponent
|
|
271
285
|
#
|
272
286
|
mattr_accessor :render_monkey_patch_enabled, instance_writer: false, default: true
|
273
287
|
|
274
|
-
#
|
288
|
+
# Path for component files
|
275
289
|
#
|
276
|
-
# config.view_component.
|
290
|
+
# config.view_component.view_component_path = "app/my_components"
|
277
291
|
#
|
278
|
-
# Defaults to `
|
292
|
+
# Defaults to `app/components`.
|
279
293
|
#
|
280
|
-
mattr_accessor :
|
294
|
+
mattr_accessor :view_component_path, instance_writer: false, default: "app/components"
|
281
295
|
|
282
|
-
#
|
296
|
+
# Parent class for generated components
|
283
297
|
#
|
284
|
-
# config.view_component.
|
298
|
+
# config.view_component.component_parent_class = "MyBaseComponent"
|
285
299
|
#
|
286
|
-
# Defaults to
|
300
|
+
# Defaults to nil. If this is falsy, generators will use
|
301
|
+
# "ApplicationComponent" if defined, "ViewComponent::Base" otherwise.
|
287
302
|
#
|
288
|
-
mattr_accessor :
|
303
|
+
mattr_accessor :component_parent_class, instance_writer: false
|
289
304
|
|
290
|
-
#
|
305
|
+
# Configuration for generators.
|
291
306
|
#
|
292
|
-
#
|
307
|
+
# All options under this namespace default to `false` unless otherwise
|
308
|
+
# stated.
|
293
309
|
#
|
294
|
-
#
|
310
|
+
# #### #sidecar
|
295
311
|
#
|
296
|
-
#
|
297
|
-
# Fallback on `[:en]` when no available_locales is defined.
|
312
|
+
# Always generate a component with a sidecar directory:
|
298
313
|
#
|
299
|
-
|
300
|
-
|
301
|
-
# Path for component files
|
314
|
+
# config.view_component.generate.sidecar = true
|
302
315
|
#
|
303
|
-
#
|
316
|
+
# #### #stimulus_controller
|
304
317
|
#
|
305
|
-
#
|
318
|
+
# Always generate a Stimulus controller alongside the component:
|
306
319
|
#
|
307
|
-
|
308
|
-
|
309
|
-
# Parent class for generated components
|
320
|
+
# config.view_component.generate.stimulus_controller = true
|
310
321
|
#
|
311
|
-
#
|
322
|
+
# #### #locale
|
323
|
+
#
|
324
|
+
# Always generate translations file alongside the component:
|
312
325
|
#
|
313
|
-
#
|
326
|
+
# config.view_component.generate.locale = true
|
314
327
|
#
|
315
|
-
|
316
|
-
|
317
|
-
# Always generate
|
328
|
+
# #### #distinct_locale_files
|
329
|
+
#
|
330
|
+
# Always generate as many translations files as available locales:
|
331
|
+
#
|
332
|
+
# config.view_component.generate.distinct_locale_files = true
|
333
|
+
#
|
334
|
+
# One file will be generated for each configured `I18n.available_locales`,
|
335
|
+
# falling back to `[:en]` when no `available_locales` is defined.
|
336
|
+
#
|
337
|
+
# #### #preview
|
318
338
|
#
|
319
|
-
#
|
339
|
+
# Always generate preview alongside the component:
|
320
340
|
#
|
321
|
-
#
|
341
|
+
# config.view_component.generate.preview = true
|
322
342
|
#
|
323
|
-
|
343
|
+
# Defaults to `false`.
|
344
|
+
mattr_accessor :generate, instance_writer: false, default: ActiveSupport::OrderedOptions.new(false)
|
324
345
|
|
325
346
|
class << self
|
326
347
|
# @private
|
@@ -401,7 +422,7 @@ module ViewComponent
|
|
401
422
|
# Derive the source location of the component Ruby file from the call stack.
|
402
423
|
# We need to ignore `inherited` frames here as they indicate that `inherited`
|
403
424
|
# has been re-defined by the consuming application, likely in ApplicationComponent.
|
404
|
-
child.source_location = caller_locations(1, 10).reject { |l| l.label == "inherited" }[0].
|
425
|
+
child.source_location = caller_locations(1, 10).reject { |l| l.label == "inherited" }[0].path
|
405
426
|
|
406
427
|
# Removes the first part of the path and the extension.
|
407
428
|
child.virtual_path = child.source_location.gsub(
|
@@ -49,7 +49,7 @@ module ViewComponent
|
|
49
49
|
end
|
50
50
|
|
51
51
|
if subclass_instance_methods.include?(:before_render_check)
|
52
|
-
|
52
|
+
ViewComponent::Deprecation.warn(
|
53
53
|
"`#before_render_check` will be removed in v3.0.0.\n\n" \
|
54
54
|
"To fix this issue, use `#before_render` instead."
|
55
55
|
)
|
@@ -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
|
)
|
@@ -5,14 +5,16 @@ require "active_support/notifications"
|
|
5
5
|
module ViewComponent # :nodoc:
|
6
6
|
module Instrumentation
|
7
7
|
def self.included(mod)
|
8
|
-
mod.prepend(self)
|
8
|
+
mod.prepend(self) unless ancestors.include?(ViewComponent::Instrumentation)
|
9
9
|
end
|
10
10
|
|
11
11
|
def render_in(view_context, &block)
|
12
12
|
ActiveSupport::Notifications.instrument(
|
13
13
|
"!render.view_component",
|
14
|
-
|
15
|
-
|
14
|
+
{
|
15
|
+
name: self.class.name,
|
16
|
+
identifier: self.class.identifier
|
17
|
+
}
|
16
18
|
) do
|
17
19
|
super(view_context, &block)
|
18
20
|
end
|
@@ -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"
|
@@ -41,7 +42,7 @@ module ViewComponent
|
|
41
42
|
EMPTY_HASH = {}.freeze
|
42
43
|
|
43
44
|
def initialize(i18n_scope:, load_paths:)
|
44
|
-
@i18n_scope = i18n_scope.split(".")
|
45
|
+
@i18n_scope = i18n_scope.split(".").map(&:to_sym)
|
45
46
|
@load_paths = load_paths
|
46
47
|
end
|
47
48
|
|
@@ -70,21 +71,29 @@ 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
|
|
73
|
-
|
74
|
-
|
75
|
-
|
74
|
+
if HTML_SAFE_TRANSLATION_KEY.match?(key)
|
75
|
+
html_escape_translation_options!(options)
|
76
|
+
end
|
77
|
+
|
78
|
+
if key.start_with?(i18n_scope + ".")
|
79
|
+
translated =
|
80
|
+
catch(:exception) do
|
81
|
+
i18n_backend.translate(locale, key, options)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Fallback to the global translations
|
85
|
+
if translated.is_a? ::I18n::MissingTranslation
|
86
|
+
return super(key, locale: locale, **options)
|
76
87
|
end
|
77
88
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
end
|
89
|
+
if HTML_SAFE_TRANSLATION_KEY.match?(key)
|
90
|
+
translated = html_safe_translation(translated)
|
91
|
+
end
|
82
92
|
|
83
|
-
|
84
|
-
|
93
|
+
translated
|
94
|
+
else
|
95
|
+
super(key, locale: locale, **options)
|
85
96
|
end
|
86
|
-
|
87
|
-
translated
|
88
97
|
end
|
89
98
|
alias :t :translate
|
90
99
|
|
@@ -92,5 +101,30 @@ module ViewComponent
|
|
92
101
|
def i18n_scope
|
93
102
|
self.class.i18n_scope
|
94
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
|
95
129
|
end
|
96
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.50.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-10 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
|