avo 2.26.0 → 2.26.2.pre.pr1579

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of avo might be problematic. Click here for more details.

Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +15 -10
  4. data/app/assets/stylesheets/css/tags.css +9 -0
  5. data/app/components/avo/base_component.rb +2 -2
  6. data/app/components/avo/field_wrapper_component.rb +2 -1
  7. data/app/components/avo/fields/belongs_to_field/edit_component.rb +1 -1
  8. data/app/components/avo/fields/belongs_to_field/show_component.rb +1 -1
  9. data/app/components/avo/fields/has_one_field/show_component.rb +3 -3
  10. data/app/components/avo/fields/index_component.rb +1 -1
  11. data/app/components/avo/fields/tags_field/edit_component.html.erb +13 -10
  12. data/app/components/avo/index/grid_item_component.rb +1 -1
  13. data/app/components/avo/index/ordering/button_component.rb +1 -1
  14. data/app/components/avo/index/resource_controls_component.rb +2 -2
  15. data/app/components/avo/resource_component.rb +1 -1
  16. data/app/components/avo/views/resource_index_component.rb +6 -4
  17. data/app/controllers/avo/actions_controller.rb +1 -1
  18. data/app/controllers/avo/application_controller.rb +2 -3
  19. data/app/controllers/avo/associations_controller.rb +2 -2
  20. data/app/controllers/avo/base_controller.rb +8 -5
  21. data/app/javascript/js/controllers/fields/tags_field_controller.js +62 -25
  22. data/app/views/avo/associations/new.html.erb +6 -1
  23. data/app/views/avo/partials/_footer.html.erb +1 -1
  24. data/app/views/layouts/avo/application.html.erb +2 -0
  25. data/config/master.key +1 -0
  26. data/lib/avo/base_resource.rb +15 -1
  27. data/lib/avo/engine.rb +6 -0
  28. data/lib/avo/fields/tags_field.rb +20 -13
  29. data/lib/avo/services/uri_service.rb +10 -9
  30. data/lib/avo/version.rb +1 -1
  31. data/public/avo-assets/avo.base.css +79 -66
  32. data/public/avo-assets/avo.base.js +161 -161
  33. data/public/avo-assets/avo.base.js.map +3 -3
  34. metadata +5 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b61c2b5a2648aa08f89b080fe7f8a8c982ef10c51cb361aa24aa53f979dd060
4
- data.tar.gz: 723c552907d3c861383006edea41a9b73da6c609bc87add39f4349569e427b9e
3
+ metadata.gz: ff9b60d0d2f1fc8e8f3536899e3559d78886342441ab558991dc2b374fbc45ac
4
+ data.tar.gz: 3bab366736547f5d041c5e1283b76b569d8ae788d25eafc4bf919ffb17d3a3e8
5
5
  SHA512:
6
- metadata.gz: dfbeee5e9548d1c94fee23facf94685393e0cd58fd0079e980ef67707ac52b0545d5aebd7f4e6b3b6b44962d1a3ca540f966c3586660b4582e92484159ac8a3d
7
- data.tar.gz: eebb9b58090526d78af41e3736985788dc69aabe53b0fd9cc02e4b926a7c8debb7daf5b4a9d17e91b357b126500804ef8cf72152d834cfe1b68a04303c1bda88
6
+ metadata.gz: b159c53be13567f56c149d7cf5d7c16af84dcf1f55f69662d09317c54b5151b628798fc26cf02175a6dd9b19b734e7a4fc3918cbf4b860aff50f53f82d1b9f46
7
+ data.tar.gz: e74aa9525374ede47f68c0ca70b0ecf4824ddaae4bb2430538462c4226d53a431f35dbfd54ed6dfbd8dad528e21174ada30f9dec34459a5480ad3dd0ebb71a23
data/Gemfile CHANGED
@@ -162,3 +162,5 @@ gem "sprockets-rails"
162
162
  # Avo file filed requires this gem
163
163
  # Use Active Storage variant
164
164
  gem "image_processing", "~> 1.12"
165
+
166
+ gem "prefixed_ids"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- avo (2.26.0)
4
+ avo (2.26.2.pre.pr1579)
5
5
  actionview (>= 6.0)
6
6
  active_link_to
7
7
  activerecord (>= 6.0)
@@ -141,7 +141,6 @@ GEM
141
141
  regexp_parser (>= 1.5, < 3.0)
142
142
  xpath (~> 3.2)
143
143
  chartkick (4.2.1)
144
- childprocess (4.1.0)
145
144
  concurrent-ruby (1.2.0)
146
145
  countries (4.2.3)
147
146
  i18n_data (~> 0.16.0)
@@ -192,6 +191,7 @@ GEM
192
191
  groupdate (6.1.0)
193
192
  activesupport (>= 5.2)
194
193
  hashdiff (1.0.1)
194
+ hashids (1.0.6)
195
195
  highline (2.1.0)
196
196
  hightop (0.3.0)
197
197
  activesupport (>= 5.2)
@@ -267,10 +267,10 @@ GEM
267
267
  net-smtp (0.3.3)
268
268
  net-protocol
269
269
  nio4r (2.5.8)
270
- nokogiri (1.14.1)
270
+ nokogiri (1.14.2)
271
271
  mini_portile2 (~> 2.8.0)
272
272
  racc (~> 1.4)
273
- nokogiri (1.14.1-x86_64-linux)
273
+ nokogiri (1.14.2-x86_64-linux)
274
274
  racc (~> 1.4)
275
275
  orm_adapter (0.5.0)
276
276
  pagy (6.0.1)
@@ -278,7 +278,10 @@ GEM
278
278
  parser (3.2.0.0)
279
279
  ast (~> 2.4.1)
280
280
  pg (1.4.5)
281
- public_suffix (5.0.0)
281
+ prefixed_ids (1.3.0)
282
+ hashids (>= 1.0.0, < 2.0.0)
283
+ rails (>= 6.0.0)
284
+ public_suffix (5.0.1)
282
285
  puma (5.6.5)
283
286
  nio4r (~> 2.0)
284
287
  pundit (2.2.0)
@@ -330,7 +333,7 @@ GEM
330
333
  rb-inotify (0.10.1)
331
334
  ffi (~> 1.0)
332
335
  redis (4.8.0)
333
- regexp_parser (2.5.0)
336
+ regexp_parser (2.7.0)
334
337
  responders (3.0.1)
335
338
  actionpack (>= 5.0)
336
339
  railties (>= 5.0)
@@ -375,10 +378,10 @@ GEM
375
378
  ruby-vips (2.1.4)
376
379
  ffi (~> 1.12)
377
380
  rubyzip (2.3.2)
378
- selenium-webdriver (4.1.0)
379
- childprocess (>= 0.5, < 5.0)
381
+ selenium-webdriver (4.8.1)
380
382
  rexml (~> 3.2, >= 3.2.5)
381
- rubyzip (>= 1.2.2)
383
+ rubyzip (>= 1.2.2, < 3.0)
384
+ websocket (~> 1.0)
382
385
  simple_po_parser (1.1.6)
383
386
  simplecov (0.22.0)
384
387
  docile (~> 1.1)
@@ -409,7 +412,7 @@ GEM
409
412
  test-prof (1.0.10)
410
413
  thor (1.2.1)
411
414
  timeout (0.3.0)
412
- turbo-rails (1.3.2)
415
+ turbo-rails (1.3.3)
413
416
  actionpack (>= 6.0.0)
414
417
  activejob (>= 6.0.0)
415
418
  railties (>= 6.0.0)
@@ -435,6 +438,7 @@ GEM
435
438
  addressable (>= 2.8.0)
436
439
  crack (>= 0.3.2)
437
440
  hashdiff (>= 0.4.0, < 2.0.0)
441
+ websocket (1.2.9)
438
442
  websocket-driver (0.7.5)
439
443
  websocket-extensions (>= 0.1.0)
440
444
  websocket-extensions (0.1.5)
@@ -491,6 +495,7 @@ DEPENDENCIES
491
495
  meta-tags
492
496
  net-smtp
493
497
  pg (>= 0.18, < 2.0)
498
+ prefixed_ids
494
499
  puma (~> 5.6.4)
495
500
  pundit
496
501
  rails (~> 6.1.0)
@@ -14,6 +14,11 @@ tags.tagify {
14
14
 
15
15
  span.tagify__input {
16
16
  @apply my-1;
17
+
18
+ &:after {
19
+ /* The loader is not centered by default. This will make it look better. */
20
+ margin-top: 0.25rem;
21
+ }
17
22
  }
18
23
  }
19
24
 
@@ -21,3 +26,7 @@ tag.tagify__tag {
21
26
  @apply text-sm my-1 mb-0;
22
27
  }
23
28
 
29
+ /* When the tags field is used to select just one value there's a weird ZeroWidthSpace (\u200B) character that breaks the vertical spacing. */
30
+ [data-tags-field-mode-value="select"] tags.tagify {
31
+ height: 40px;
32
+ }
@@ -23,10 +23,10 @@ class Avo::BaseComponent < ViewComponent::Base
23
23
 
24
24
  model_klass = ::Avo::BaseResource.valid_model_class model_class_name
25
25
 
26
- model = model_klass.find params[:via_resource_id]
27
-
28
26
  resource = ::Avo::App.get_resource_by_model_name model_klass if resource.blank?
29
27
 
28
+ model = resource.find_record params[:via_resource_id], query: model_klass, params: params
29
+
30
30
  resource.dup.hydrate model: model
31
31
  end
32
32
 
@@ -77,7 +77,8 @@ class Avo::FieldWrapperComponent < ViewComponent::Base
77
77
  def data
78
78
  attributes = {
79
79
  field_id: @field.id,
80
- field_type: @field.type
80
+ field_type: @field.type,
81
+ **@data
81
82
  }
82
83
 
83
84
  # Add the built-in stimulus integration data tags.
@@ -44,7 +44,7 @@ class Avo::Fields::BelongsToField::EditComponent < Avo::Fields::EditComponent
44
44
 
45
45
  return @polymorphic_record if @polymorphic_record.present?
46
46
 
47
- @polymorphic_record = polymorphic_class.safe_constantize.find polymorphic_id
47
+ @polymorphic_record = @resource.find_record polymorphic_id, query: polymorphic_class.safe_constantize, params: params
48
48
 
49
49
  @polymorphic_record
50
50
  end
@@ -6,7 +6,7 @@ class Avo::Fields::BelongsToField::ShowComponent < Avo::Fields::ShowComponent
6
6
  model: @field.value,
7
7
  resource: @field.target_resource,
8
8
  via_resource_class: @resource.class.to_s,
9
- via_resource_id: @resource.model.id
9
+ via_resource_id: @resource.model.to_param
10
10
  )
11
11
  end
12
12
  end
@@ -21,11 +21,11 @@ class Avo::Fields::HasOneField::ShowComponent < Avo::Fields::ShowComponent
21
21
  end
22
22
 
23
23
  def attach_path
24
- helpers.avo.resources_associations_new_path(@resource.singular_model_key, @resource.model.id, @field.id)
24
+ helpers.avo.resources_associations_new_path(@resource.singular_model_key, @resource.model.to_param, @field.id)
25
25
  end
26
26
 
27
27
  def can_see_the_create_button?
28
- create = "create_#{@field.id.to_s}?"
28
+ create = "create_#{@field.id}?"
29
29
 
30
30
  authorization_service = @resource.authorization
31
31
 
@@ -39,7 +39,7 @@ class Avo::Fields::HasOneField::ShowComponent < Avo::Fields::ShowComponent
39
39
  args = {
40
40
  via_relation: @resource.singular_model_key,
41
41
  via_relation_class: @resource.model_class.to_s,
42
- via_resource_id: @resource.model.id
42
+ via_resource_id: @resource.model.to_param
43
43
  }
44
44
  helpers.new_resource_path(resource: @field.target_resource, **args)
45
45
  end
@@ -22,7 +22,7 @@ class Avo::Fields::IndexComponent < Avo::BaseComponent
22
22
  if @parent_model.present?
23
23
  args = {
24
24
  via_resource_class: @parent_resource.class,
25
- via_resource_id: @parent_model.id
25
+ via_resource_id: @parent_model.to_param
26
26
  }
27
27
  end
28
28
 
@@ -1,10 +1,19 @@
1
- <%= field_wrapper **field_wrapper_args do %>
2
- <div data-controller="tags-field">
1
+ <%= field_wrapper **field_wrapper_args, data: {
2
+ controller: "tags-field",
3
+ tags_field_mode_value: @field.mode,
4
+ tags_field_whitelist_items_value: @field.suggestions.to_json,
5
+ tags_field_disallowed_items_value: @field.disallowed.to_json,
6
+ tags_field_enforce_suggestions_value: @field.enforce_suggestions,
7
+ tags_field_close_on_select_value: @field.close_on_select,
8
+ tags_field_delimiters_value: @field.delimiters,
9
+ tags_field_fetch_values_from_value: @field.fetch_values_from,
10
+ } do %>
11
+ <%# We use a dummy field so the back action from Turbo does not brake the field %>
3
12
  <%# dummy field %>
4
13
  <%= text_field_tag "#{@field.id}-dummy", '',
5
14
  class: classes("w-full"),
6
15
  data: {
7
- 'tags-field-target': 'fakeInput',
16
+ tags_field_target: :fakeInput,
8
17
  },
9
18
  disabled: disabled?,
10
19
  placeholder: @field.placeholder,
@@ -15,17 +24,11 @@
15
24
  <%= @form.text_field @field.id,
16
25
  class: classes("hidden w-full border-primary-500 focus-within:border-primary-500"),
17
26
  data: {
18
- 'tags-field-target': 'input',
19
- 'whitelist-items': @field.suggestions.to_json,
20
- 'disallowed-items': @field.disallowed.to_json,
21
- 'enforce-suggestions': @field.enforce_suggestions ? 1 : 0,
22
- 'delimiters': @field.delimiters,
23
- 'close-on-select': @field.close_on_select ? 1 : 0,
27
+ tags_field_target: :input,
24
28
  },
25
29
  disabled: disabled?,
26
30
  placeholder: @field.placeholder,
27
31
  style: @field.get_html(:style, view: view, element: :input),
28
32
  value: @field.field_value.to_json
29
33
  %>
30
- </div>
31
34
  <% end %>
@@ -33,7 +33,7 @@ class Avo::Index::GridItemComponent < Avo::BaseComponent
33
33
  if @parent_model.present?
34
34
  args = {
35
35
  via_resource_class: parent_resource.class.to_s,
36
- via_resource_id: @parent_model.id
36
+ via_resource_id: @parent_model.to_param
37
37
  }
38
38
  end
39
39
 
@@ -18,6 +18,6 @@ class Avo::Index::Ordering::ButtonComponent < Avo::Index::Ordering::BaseComponen
18
18
  end
19
19
 
20
20
  def order_path(args)
21
- Avo::App.view_context.avo.reorder_order_path(resource.route_key, resource.model.id, **args)
21
+ Avo::App.view_context.avo.reorder_order_path(resource.route_key, resource.model.to_param, **args)
22
22
  end
23
23
  end
@@ -37,7 +37,7 @@ class Avo::Index::ResourceControlsComponent < Avo::ResourceComponent
37
37
  if @parent_model.present?
38
38
  args = {
39
39
  via_resource_class: parent_resource.class.to_s,
40
- via_resource_id: @parent_model.id
40
+ via_resource_id: @parent_model.to_param
41
41
  }
42
42
  end
43
43
 
@@ -51,7 +51,7 @@ class Avo::Index::ResourceControlsComponent < Avo::ResourceComponent
51
51
  if @parent_model.present?
52
52
  args = {
53
53
  via_resource_class: parent_resource.class.to_s,
54
- via_resource_id: @parent_model.id
54
+ via_resource_id: @parent_model.to_param
55
55
  }
56
56
  end
57
57
 
@@ -26,7 +26,7 @@ class Avo::ResourceComponent < Avo::BaseComponent
26
26
  def detach_path
27
27
  return "/" if @reflection.blank?
28
28
 
29
- helpers.resource_detach_path(params[:resource_name], params[:id], @reflection.name.to_s, @resource.model.id)
29
+ helpers.resource_detach_path(params[:resource_name], params[:id], @reflection.name.to_s, @resource.model.to_param)
30
30
  end
31
31
 
32
32
  def can_see_the_edit_button?
@@ -74,7 +74,7 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
74
74
  if @reflection.present?
75
75
  args = {
76
76
  via_relation_class: reflection_model_class,
77
- via_resource_id: @parent_model.id
77
+ via_resource_id: @parent_model.to_param
78
78
  }
79
79
 
80
80
  if @reflection.is_a? ActiveRecord::Reflection::ThroughReflection
@@ -98,7 +98,9 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
98
98
  end
99
99
 
100
100
  def attach_path
101
- Avo::App.root_path(paths: [request.env["PATH_INFO"], "new"])
101
+ current_path = CGI.unescape(request.env["PATH_INFO"]).split("/").select(&:present?)
102
+
103
+ Avo::App.root_path(paths: [*current_path, "new"])
102
104
  end
103
105
 
104
106
  def singular_resource_name
@@ -142,10 +144,10 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
142
144
  return unless @reflection.present?
143
145
 
144
146
  {
145
- association: 'has_many',
147
+ association: "has_many",
146
148
  association_id: @reflection.name,
147
149
  class: reflection_model_class,
148
- id: @parent_model.id
150
+ id: @parent_model.to_param
149
151
  }
150
152
  end
151
153
  end
@@ -35,7 +35,7 @@ module Avo
35
35
  args[:models] = if @selected_query.present?
36
36
  @resource.model_class.find_by_sql decrypted_query
37
37
  else
38
- @resource.class.find_scope.find resource_ids
38
+ @resource.find_record resource_ids, params: params
39
39
  end
40
40
  end
41
41
 
@@ -128,7 +128,7 @@ module Avo
128
128
  end
129
129
 
130
130
  def set_model
131
- @model = model_find_scope.find params[:id]
131
+ @model = @resource.find_record(params[:id], query: model_find_scope, params: params)
132
132
  end
133
133
 
134
134
  def model_find_scope
@@ -141,11 +141,10 @@ module Avo
141
141
 
142
142
  def set_related_model
143
143
  association_name = BaseResource.valid_association_name(@model, params[:related_name])
144
-
145
144
  @related_model = if @field.is_a? Avo::Fields::HasOneField
146
145
  @model.send association_name
147
146
  else
148
- eager_load_files(@related_resource, @model.send(association_name)).find params[:related_id]
147
+ @related_resource.find_record params[:related_id], query: eager_load_files(@related_resource, @model.send(association_name)), params: params
149
148
  end
150
149
  end
151
150
 
@@ -21,7 +21,7 @@ module Avo
21
21
  def index
22
22
  @parent_resource = @resource.dup
23
23
  @resource = @related_resource
24
- @parent_model = @parent_resource.class.find_scope.find(params[:id])
24
+ @parent_model = @parent_resource.find_record(params[:id], params: params)
25
25
  @parent_resource.hydrate(model: @parent_model)
26
26
  association_name = BaseResource.valid_association_name(@parent_model, params[:related_name])
27
27
  @query = @related_authorization.apply_policy @parent_model.send(association_name)
@@ -113,7 +113,7 @@ module Avo
113
113
  end
114
114
 
115
115
  def set_attachment_model
116
- @attachment_model = @attachment_class.find attachment_id
116
+ @attachment_model = @related_resource.find_record attachment_id, params: params
117
117
  end
118
118
 
119
119
  def set_reflection_field
@@ -90,7 +90,7 @@ module Avo
90
90
  # If we're accessing this resource via another resource add the parent to the breadcrumbs.
91
91
  if params[:via_resource_class].present? && params[:via_resource_id].present?
92
92
  via_resource = Avo::App.get_resource(params[:via_resource_class]).dup
93
- via_model = via_resource.class.find_scope.find params[:via_resource_id]
93
+ via_model = via_resource.find_record params[:via_resource_id], params: params
94
94
  via_resource.hydrate model: via_model
95
95
 
96
96
  add_breadcrumb via_resource.plural_name, resources_path(resource: via_resource)
@@ -113,7 +113,7 @@ module Avo
113
113
 
114
114
  if is_associated_record?
115
115
  via_resource = Avo::App.get_resource_by_model_name(params[:via_relation_class]).dup
116
- via_model = via_resource.class.find_scope.find params[:via_resource_id]
116
+ via_model = via_resource.find_record params[:via_resource_id], params: params
117
117
  via_resource.hydrate model: via_model
118
118
 
119
119
  add_breadcrumb via_resource.plural_name, resources_path(resource: via_resource)
@@ -137,7 +137,10 @@ module Avo
137
137
  # Fills in the required infor for belongs_to and has_many
138
138
  # Get the foreign key and set it to the id we received in the params
139
139
  if @reflection.is_a?(ActiveRecord::Reflection::BelongsToReflection) || @reflection.is_a?(ActiveRecord::Reflection::HasManyReflection)
140
- @model.send("#{@reflection.foreign_key}=", params[:via_resource_id])
140
+ related_resource = Avo::App.get_resource_by_model_name params[:via_relation_class]
141
+ related_record = related_resource.find_record params[:via_resource_id], params: params
142
+
143
+ @model.send("#{@reflection.foreign_key}=", related_record.id)
141
144
  @model.save
142
145
  end
143
146
 
@@ -145,7 +148,7 @@ module Avo
145
148
  if @reflection.is_a? ActiveRecord::Reflection::ThroughReflection
146
149
  # find the record
147
150
  via_resource = ::Avo::App.get_resource_by_model_name(params[:via_relation_class]).dup
148
- @related_record = via_resource.model_class.find params[:via_resource_id]
151
+ @related_record = via_resource.find_record params[:via_resource_id], params: params
149
152
  association_name = BaseResource.valid_association_name(@model, params[:via_relation])
150
153
 
151
154
  @model.send(association_name) << @related_record
@@ -385,7 +388,7 @@ module Avo
385
388
  # If we're accessing this resource via another resource add the parent to the breadcrumbs.
386
389
  if params[:via_resource_class].present? && params[:via_resource_id].present?
387
390
  via_resource = Avo::App.get_resource(params[:via_resource_class]).dup
388
- via_model = via_resource.class.find_scope.find params[:via_resource_id]
391
+ via_model = via_resource.find_record params[:via_resource_id], params: params
389
392
  via_resource.hydrate model: via_model
390
393
 
391
394
  add_breadcrumb via_resource.plural_name, resources_path(resource: @resource)
@@ -1,62 +1,64 @@
1
1
  import { first, isObject, merge } from 'lodash'
2
2
  import Tagify from '@yaireo/tagify'
3
-
4
- import BaseController from '../base_controller'
3
+ import URI from 'urijs'
5
4
 
6
5
  import { suggestionItemTemplate, tagTemplate } from './tags_field_helpers'
6
+ import BaseController from '../base_controller'
7
+ import debouncePromise from '../../helpers/debounce_promise'
7
8
 
8
9
  export default class extends BaseController {
9
10
  static targets = ['input', 'fakeInput'];
10
11
 
11
- tagify = null;
12
-
13
- get whitelistItems() {
14
- return this.getJsonAttribute(this.inputTarget, 'data-whitelist-items', [])
12
+ static values = {
13
+ whitelistItems: { type: Array, default: [] },
14
+ disallowedItems: { type: Array, default: [] },
15
+ enforceSuggestions: { type: Boolean, default: false },
16
+ closeOnSelect: { type: Boolean, default: false },
17
+ delimiters: { type: Array, default: [] },
18
+ mode: String,
19
+ fetchValuesFrom: String,
15
20
  }
16
21
 
17
- get disallowedItems() {
18
- return this.getJsonAttribute(this.inputTarget, 'data-disallowed-items', [])
19
- }
20
-
21
- get enforceSuggestions() {
22
- return this.getBooleanAttribute(this.inputTarget, 'data-enforce-suggestions')
23
- }
22
+ tagify = null;
24
23
 
25
- get closeOnSelect() {
26
- return this.getBooleanAttribute(this.inputTarget, 'data-close-on-select')
27
- }
24
+ searchDebounce = 500
28
25
 
29
- get delimiters() {
30
- return this.getJsonAttribute(this.inputTarget, 'data-delimiters', [])
31
- }
26
+ debouncedFetch = debouncePromise(fetch, this.searchDebounce);
32
27
 
33
28
  get suggestionsAreObjects() {
34
- return isObject(first(this.whitelistItems))
29
+ return isObject(first(this.whitelistItemsValue)) || this.fetchValuesFromValue
35
30
  }
36
31
 
37
32
  get tagifyOptions() {
38
33
  let options = {
39
- whitelist: this.whitelistItems,
40
- blacklist: this.disallowedItems,
41
- enforceWhitelist: this.enforceSuggestions,
42
- delimiters: this.delimiters.join('|'),
34
+ whitelist: this.whitelistItemsValue,
35
+ blacklist: this.disallowedItemsValue,
36
+ enforceWhitelist: this.enforceSuggestionsValue || this.fetchValuesFromValue,
37
+ delimiters: this.delimitersValue.join('|'),
43
38
  dropdown: {
44
39
  maxItems: 20,
45
40
  enabled: 0,
46
- closeOnSelect: this.closeOnSelect,
41
+ searchKeys: [this.labelAttributeValue],
42
+ closeOnSelect: this.closeOnSelectValue,
47
43
  },
48
44
  }
49
45
 
46
+ if (this.modeValue) {
47
+ options.mode = this.modeValue // null or "select"
48
+ }
49
+
50
50
  if (this.suggestionsAreObjects) {
51
51
  options = merge(options, {
52
52
  tagTextProp: 'label',
53
53
  dropdown: {
54
54
  searchKeys: ['label'],
55
+ mapValueTo: 'value',
55
56
  },
56
57
  templates: {
57
58
  tag: tagTemplate,
58
59
  dropdownItem: suggestionItemTemplate,
59
60
  },
61
+ originalInputValueFormat: (valuesArr) => valuesArr.map((item) => item.value),
60
62
  })
61
63
  }
62
64
 
@@ -73,6 +75,41 @@ export default class extends BaseController {
73
75
 
74
76
  initTagify() {
75
77
  this.tagify = new Tagify(this.inputTarget, this.tagifyOptions)
78
+ const that = this
79
+
80
+ function onInput(e) {
81
+ // Create the URL from which to fetch the values
82
+ const query = e.detail.value
83
+ const uri = new URI(that.fetchValuesFromValue)
84
+ uri.addSearch({
85
+ q: query,
86
+ })
87
+
88
+ // reset current whitelist
89
+ that.tagify.whitelist = null
90
+ // show the loader animation
91
+ that.tagify.loading(true)
92
+
93
+ // get new whitelist from a request
94
+ that.fetchResults(uri.toString())
95
+ .then((result) => {
96
+ that.tagify.settings.whitelist = result // add already-existing tags to the new whitelist array
97
+
98
+ that.tagify
99
+ .loading(false)
100
+ .dropdown.show(e.detail.value) // render the suggestions dropdown.
101
+ })
102
+ .catch(() => that.tagify.dropdown.hide())
103
+ }
104
+
105
+ if (this.fetchValuesFromValue) {
106
+ this.tagify.on('input', onInput)
107
+ }
108
+ }
109
+
110
+ fetchResults(endpoint) {
111
+ return this.debouncedFetch(endpoint)
112
+ .then((response) => response.json())
76
113
  }
77
114
 
78
115
  hideFakeInput() {
@@ -1,6 +1,11 @@
1
1
  <%= turbo_frame_tag 'attach_modal' do %>
2
+ <%
3
+ url = Avo::Services::URIService.parse(avo.root_url.to_s)
4
+ .append_paths('resources', params[:resource_name], params[:id], params[:related_name])
5
+ .to_s
6
+ %>
2
7
  <%= form_with scope: 'fields',
3
- url: "#{avo.root_path}resources/#{params[:resource_name]}/#{params[:id]}/#{params[:related_name]}/",
8
+ url: url,
4
9
  local: true,
5
10
  data: {
6
11
  'turbo-frame': '_top'
@@ -1,3 +1,3 @@
1
1
  <div class="text-center text-sm text-gray-700 <%= 'print:hidden' if Avo.configuration.hide_layout_when_printing %>">
2
- <a href="https://avohq.io/" target="_blank">Avo</a> · &copy; <%= Date.today.year %> AvoHQ · <span title="<%= Avo::App.license.valid ? 'valid' : 'invalid'%> <%= Avo::App.license.id %> license">v<%= Avo::VERSION %></span>
2
+ <a href="https://avohq.io/" target="_blank">Avo</a> · &copy; <%= Date.today.year %> AvoHQ · <span title="<%= Avo::App.license.valid ? 'valid' : 'invalid'%> <%= Avo::App.license.id %> license">v<%= Avo::VERSION %></span> · Current.user.id = <%= Current.user&.id %>
3
3
  </div>
@@ -1,4 +1,5 @@
1
1
  <!DOCTYPE html>
2
+ <!-- ✨ Built with Avo • https://www.avohq.io/ -->
2
3
  <html>
3
4
  <head>
4
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
@@ -62,3 +63,4 @@
62
63
  <!-- License valid?: <%= Avo::App.license.valid ? "valid" : "invalid" %> -->
63
64
  </body>
64
65
  </html>
66
+ <!-- ✨ Built with Avo • https://www.avohq.io/ -->
data/config/master.key ADDED
@@ -0,0 +1 @@
1
+ 2aeb23d82b909d9c6b5abb62f7058c2a
@@ -42,6 +42,10 @@ module Avo
42
42
  class_attribute :unscoped_queries_on_index, default: false
43
43
  class_attribute :resolve_query_scope
44
44
  class_attribute :resolve_find_scope
45
+ # TODO: refactor this into a Host without args
46
+ class_attribute :find_record_method, default: ->(model_class:, id:, params:) {
47
+ model_class.find id
48
+ }
45
49
  class_attribute :ordering
46
50
  class_attribute :hide_from_global_search, default: false
47
51
  class_attribute :after_create_path, default: :show
@@ -379,7 +383,11 @@ module Avo
379
383
  # set the value to the actual record
380
384
  value = @params[:via_relation_class].safe_constantize.find(@params[:via_resource_id])
381
385
  elsif reflection.present? && reflection.foreign_key.present? && field.id.to_s == @params[:via_relation].to_s
382
- value = @params[:via_resource_id]
386
+ resource = Avo::App.get_resource_by_model_name params[:via_relation_class]
387
+ model = resource.find_record @params[:via_resource_id], params: params
388
+ id_param = reflection.options[:primary_key] || :id
389
+
390
+ value = model.send(id_param)
383
391
  end
384
392
  end
385
393
 
@@ -488,5 +496,11 @@ module Avo
488
496
  def has_model_id?
489
497
  model.present? && model.id.present?
490
498
  end
499
+
500
+ def find_record(id, query: nil, params: nil)
501
+ query ||= self.class.find_scope
502
+
503
+ self.class.find_record_method.call(model_class: query, id: id, params: params)
504
+ end
491
505
  end
492
506
  end
data/lib/avo/engine.rb CHANGED
@@ -54,6 +54,12 @@ module Avo
54
54
  # app.config.logger = ::Logger.new(STDOUT)
55
55
  end
56
56
 
57
+ initializer "avo.test_buddy" do |app|
58
+ if Avo::IN_DEVELOPMENT
59
+ Rails.autoloaders.main.push_dir Avo::Engine.root.join("spec", "helpers")
60
+ end
61
+ end
62
+
57
63
  config.app_middleware.use(
58
64
  Rack::Static,
59
65
  urls: ["/avo-assets"],