headmin 0.5.3 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/app/assets/javascripts/headmin/controllers/media_controller.js +14 -14
  4. data/app/assets/javascripts/headmin/controllers/media_modal_controller.js +5 -5
  5. data/app/assets/javascripts/headmin/controllers/remote_modal_controller.js +1 -2
  6. data/app/assets/javascripts/headmin/controllers/textarea_controller.js +3 -3
  7. data/app/assets/javascripts/headmin.js +11 -11
  8. data/app/assets/stylesheets/headmin/forms/repeater.scss +0 -4
  9. data/app/assets/stylesheets/headmin/forms.scss +0 -6
  10. data/app/assets/stylesheets/headmin/overrides/redactorx.scss +1 -1
  11. data/app/assets/stylesheets/headmin/vendor/{tom-select-bootstrap.css → tom-select-bootstrap.scss} +0 -1
  12. data/app/assets/stylesheets/headmin.css +5 -9
  13. data/app/models/concerns/headmin/field.rb +1 -1
  14. data/app/models/headmin/filter/association.rb +86 -0
  15. data/app/models/headmin/filter/association_view.rb +74 -0
  16. data/app/models/headmin/filter/base.rb +5 -2
  17. data/app/models/headmin/filter/boolean_view.rb +1 -0
  18. data/app/models/headmin/filter/date_view.rb +1 -0
  19. data/app/models/headmin/filter/flatpickr_view.rb +1 -0
  20. data/app/models/headmin/filter/number_view.rb +1 -0
  21. data/app/models/headmin/filter/operator_view.rb +3 -1
  22. data/app/models/headmin/filter/options_view.rb +1 -0
  23. data/app/models/headmin/filter/text_view.rb +1 -0
  24. data/app/models/headmin/form/association_view.rb +102 -0
  25. data/app/models/headmin/form/blocks_view.rb +4 -1
  26. data/app/models/headmin/form/file_view.rb +0 -8
  27. data/app/models/headmin/form/flatpickr_view.rb +2 -1
  28. data/app/models/headmin/form/media_item_view.rb +39 -0
  29. data/app/models/headmin/form/media_view.rb +27 -3
  30. data/app/models/headmin/form/select_view.rb +2 -1
  31. data/app/models/headmin/thumbnail_view.rb +40 -19
  32. data/app/models/view_model.rb +4 -0
  33. data/app/views/headmin/_filters.html.erb +7 -2
  34. data/app/views/headmin/_thumbnail.html.erb +32 -8
  35. data/app/views/headmin/filters/_association.html.erb +24 -0
  36. data/app/views/headmin/filters/_options.html.erb +1 -1
  37. data/app/views/headmin/forms/_association.html.erb +30 -0
  38. data/app/views/headmin/forms/_file.html.erb +4 -5
  39. data/app/views/headmin/forms/_media.html.erb +7 -5
  40. data/app/views/headmin/forms/_repeater.html.erb +10 -8
  41. data/app/views/headmin/forms/_wrapper.html.erb +0 -1
  42. data/app/views/headmin/forms/fields/_list.html.erb +6 -4
  43. data/app/views/headmin/forms/media/_item.html.erb +18 -12
  44. data/app/views/headmin/forms/repeater/_row.html.erb +2 -1
  45. data/app/views/headmin/media/_item.html.erb +1 -2
  46. data/app/views/headmin/media/_media_item_modal.html.erb +2 -2
  47. data/app/views/headmin/table/body/_association.html.erb +17 -3
  48. data/app/views/headmin/views/devise/shared/_links.html.erb +14 -20
  49. data/config/locales/activerecord/en.yml +1 -0
  50. data/config/locales/activerecord/nl.yml +1 -0
  51. data/config/locales/headmin/filters/en.yml +3 -1
  52. data/config/locales/headmin/filters/nl.yml +2 -0
  53. data/config/locales/headmin/media/en.yml +2 -2
  54. data/config/locales/headmin/media/nl.yml +2 -1
  55. data/lib/headmin/version.rb +1 -1
  56. data/package.json +1 -1
  57. metadata +9 -3
@@ -0,0 +1,102 @@
1
+ module Headmin
2
+ module Form
3
+ class AssociationView < ViewModel
4
+ include Headmin::Form::Hintable
5
+ include Headmin::Form::InputGroupable
6
+ include Headmin::Form::Labelable
7
+ include Headmin::Form::Listable
8
+ include Headmin::Form::Placeholderable
9
+ include Headmin::Form::Validatable
10
+ include Headmin::Form::Wrappable
11
+
12
+ def input_options
13
+ keys = attributes - %i[append attribute collection float form input_group include_blank label prepend validate selected tags wrapper]
14
+ options = to_h.slice(*keys)
15
+ def_input_options = default_input_options
16
+ def_input_options = def_input_options.deep_merge(options)
17
+ def_input_options = def_input_options.merge({multiple: true}) if collection?
18
+ def_input_options
19
+ end
20
+
21
+ def input_group_options
22
+ default_input_group_options
23
+ .deep_merge(label_input_group_options)
24
+ .deep_merge(@input_group || {})
25
+ end
26
+
27
+ def wrapper_options
28
+ default_wrapper_options.deep_merge({
29
+ class: ["mb-3", ("form-floating" if float)]
30
+ }).deep_merge(@wrapper || {})
31
+ end
32
+
33
+ def select_options
34
+ keys = %i[include_blank selected]
35
+ options = to_h.slice(*keys)
36
+ default_options.deep_merge(options)
37
+ end
38
+
39
+ def attribute_with_id
40
+ attribute_with_id = collection? ? "#{association_foreign_key}s" : foreign_key
41
+
42
+ if attribute_with_id.nil?
43
+ raise(AssociationDoesNotExistError, "Association attribute that was passed does not exist.")
44
+ else
45
+ attribute_with_id
46
+ end
47
+ end
48
+
49
+ def collection
50
+ association_class.all.map { |item| [item.to_s, item.id] }
51
+ end
52
+
53
+ private
54
+
55
+ def association_foreign_key
56
+ reflection.association_foreign_key
57
+ end
58
+
59
+ def foreign_key
60
+ reflection.foreign_key
61
+ end
62
+
63
+ def reflection
64
+ form.object.class.reflect_on_association(attribute)
65
+ end
66
+
67
+ def association_class
68
+ reflection.klass
69
+ end
70
+
71
+ def collection?
72
+ reflection.collection?
73
+ end
74
+
75
+ def default_options
76
+ {
77
+ selected: form.object&.send(attribute_with_id)
78
+ }
79
+ end
80
+
81
+ def default_input_options
82
+ {
83
+ aria: {describedby: validation_id},
84
+ class: [form_control_class, validation_class],
85
+ data: {
86
+ tags: tags,
87
+ controller: "select"
88
+ },
89
+ multiple: tags,
90
+ placeholder: placeholder
91
+ }
92
+ end
93
+
94
+ def form_control_class
95
+ plaintext ? "form-control-plaintext" : "form-select"
96
+ end
97
+
98
+ class AssociationDoesNotExistError < StandardError
99
+ end
100
+ end
101
+ end
102
+ end
@@ -21,7 +21,10 @@ module Headmin
21
21
  {
22
22
  attribute: "blocks",
23
23
  label: false,
24
- templates: names
24
+ templates: names,
25
+ row: {
26
+ class: "repeater-row list-group-item pt-3"
27
+ }
25
28
  }
26
29
  end
27
30
  end
@@ -55,14 +55,6 @@ module Headmin
55
55
  attached.is_a?(ActiveStorage::Attached::Many) ? :"#{attribute}_attachments" : :"#{attribute}_attachment"
56
56
  end
57
57
 
58
- def thumbnail_width
59
- 100
60
- end
61
-
62
- def thumbnail_height
63
- 100
64
- end
65
-
66
58
  def dropzone_options
67
59
  if dropzone
68
60
  {
@@ -18,8 +18,9 @@ module Headmin
18
18
  def default_data
19
19
  {
20
20
  controller: "flatpickr",
21
+
21
22
  flatpickr: {
22
- defaultDate: form.object&.send(attribute)&.strftime("%d/%m/%Y")
23
+ defaultDate: attribute.nil? ? Date.today : form.object&.send(attribute)&.strftime("%d/%m/%Y")
23
24
  }
24
25
  }
25
26
  end
@@ -0,0 +1,39 @@
1
+ module Headmin
2
+ module Form
3
+ class MediaItemView < ViewModel
4
+ include Rails.application.routes.url_helpers
5
+
6
+ def thumbnail_options
7
+ options = {
8
+ file: attachment
9
+ }
10
+
11
+ # Don't pass width or height if it was not defined
12
+ options = options.merge(width: width) if is_defined?(:width)
13
+ options = options.merge(height: height) if is_defined?(:height)
14
+
15
+ options
16
+ end
17
+
18
+ def attachment
19
+ form.object
20
+ end
21
+
22
+ def position_value
23
+ attachment.new_record? ? nil : attachment.position
24
+ end
25
+
26
+ def id
27
+ attachment.blob ? attachment.blob.id : "$1"
28
+ end
29
+
30
+ def filename
31
+ attachment.blob&.filename&.to_s
32
+ end
33
+
34
+ def size
35
+ number_to_human_size(attachment.blob&.byte_size || 0)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -19,7 +19,7 @@ module Headmin
19
19
  class: ["mb-3", ("form-floating" if float)],
20
20
  data: {
21
21
  controller: "media",
22
- name: "#{attribute}_#{object_id}",
22
+ name: name,
23
23
  min: min,
24
24
  max: max,
25
25
  sort: sort,
@@ -28,6 +28,18 @@ module Headmin
28
28
  }).deep_merge(@wrapper || {})
29
29
  end
30
30
 
31
+ def item_options
32
+ options = {
33
+ sort: sort
34
+ }
35
+
36
+ # Don't pass width or height if it was not defined
37
+ options = options.merge(width: width) if is_defined?(:width)
38
+ options = options.merge(height: height) if is_defined?(:height)
39
+
40
+ options
41
+ end
42
+
31
43
  def custom_validation_options
32
44
  {
33
45
  form: form,
@@ -37,6 +49,18 @@ module Headmin
37
49
  }
38
50
  end
39
51
 
52
+ def thumbnail_options
53
+ options = {
54
+ icon: "plus"
55
+ }
56
+
57
+ # Don't pass width or height if it was not defined
58
+ options = options.merge(width: width) unless is_defined?(:width)
59
+ options = options.merge(height: height) unless is_defined?(:height)
60
+
61
+ options
62
+ end
63
+
40
64
  def association_object
41
65
  if attached.is_a?(ActiveStorage::Attached::Many)
42
66
  result = form.object.send(nested_attribute)
@@ -101,8 +125,8 @@ module Headmin
101
125
  attachments.map { |attachment| attachment.blob_id }
102
126
  end
103
127
 
104
- def media_modal_url
105
- headmin_media_url(name: "#{attribute}_#{object_id}", ids: blob_ids, min: min, max: max)
128
+ def name
129
+ "#{attribute}_#{object_id}"
106
130
  end
107
131
 
108
132
  def sort
@@ -36,8 +36,9 @@ module Headmin
36
36
  private
37
37
 
38
38
  def default_options
39
+ selected = attribute.nil? ? nil : form.object&.send(attribute)
39
40
  {
40
- selected: form.object&.send(attribute)
41
+ selected: selected
41
42
  }
42
43
  end
43
44
 
@@ -1,41 +1,62 @@
1
1
  module Headmin
2
- class ThumbnailView
3
- def initialize(local_assigns)
4
- @local_assigns = local_assigns
5
- end
6
-
2
+ class ThumbnailView < ViewModel
7
3
  def class_names
8
- class_names = [@local_assigns[:class]]
4
+ class_names = [@class]
9
5
  class_names << "img-thumbnail h-thumbnail"
10
6
  class_names.join(" ")
11
7
  end
12
8
 
13
9
  def width
14
- @local_assigns[:width] || 150
10
+ if is_defined?(:width)
11
+ @width
12
+ else
13
+ @width || 100
14
+ end
15
15
  end
16
16
 
17
17
  def height
18
- @local_assigns[:height] || 150
18
+ if is_defined?(:height)
19
+ @height
20
+ else
21
+ @height || 100
22
+ end
19
23
  end
20
24
 
21
- def src
22
- @local_assigns[:src]
25
+ def blob
26
+ file.is_a?(ActiveStorage::Blob) ? file : file&.blob
23
27
  end
24
28
 
25
- def icon
26
- @local_assigns[:icon]
29
+ def mime_type
30
+ blob&.content_type
27
31
  end
28
32
 
29
- def mime_type
30
- if src
31
- stripped_path = URI.parse(src).path
32
- extension = File.extname(stripped_path)
33
- Rack::Mime.mime_type(extension)
33
+ def variant_options
34
+ if width.nil? || height.nil?
35
+ default_variant_options.merge({
36
+ resize_to_fit: [width, height]
37
+ })
38
+ else
39
+ default_variant_options.merge({
40
+ resize_to_fill: [width, height, crop: :attention]
41
+ })
34
42
  end
35
43
  end
36
44
 
37
- def image?
38
- mime_type&.match?(/^image/)
45
+ def default_variant_options
46
+ {
47
+ saver: {
48
+ quality: 80
49
+ }
50
+ }
51
+ end
52
+
53
+ # Is it possible to create a ActiveStorage::Variant?
54
+ def variable?
55
+ blob&.variable?
56
+ end
57
+
58
+ def icon?
59
+ is_defined?(:icon)
39
60
  end
40
61
 
41
62
  # https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
@@ -40,6 +40,10 @@ class ViewModel
40
40
 
41
41
  private
42
42
 
43
+ def is_defined?(attribute)
44
+ attributes.include?(attribute.to_sym)
45
+ end
46
+
43
47
  def value_for(attribute)
44
48
  reserved_methods.include?(attribute) ? instance_variable_get("@#{attribute}") : send(attribute)
45
49
  end
@@ -14,10 +14,15 @@
14
14
  # <%= render "headmin/filters", url: admin_polls_path %#>
15
15
 
16
16
  action = local_assigns.has_key?(:url) ? url : request.path
17
- %>
18
17
 
19
- <%= form_with url: action, method: :get, data: {controller: "filters", filters_target: "form"} do |form| %>
18
+ begin
19
+ model = controller_name.singularize.capitalize.constantize
20
+ rescue
21
+ raise "Cannot find class!"
22
+ end
23
+ %>
20
24
 
25
+ <%= form_with model: model.new, url: action, method: :get, data: {controller: "filters", filters_target: "form"} do |form| %>
21
26
  <%# Perform yield in order to capture content blocks, pass form so we can use headmin form inputs %>
22
27
  <%= yield(form) %>
23
28
 
@@ -1,11 +1,35 @@
1
- <% thumbnail = Headmin::ThumbnailView.new(local_assigns) %>
1
+ <%
2
+ # headmin/thumbnail
3
+ #
4
+ # ==== Required parameters
5
+ # * +file+ - Name of the attribute of the form model
6
+ #
7
+ # ==== Optional parameters
8
+ # * +width+ - Width of the thumbnail. Set to nil to fill according to the original aspect ratio
9
+ # * +height+ - Height of the thumbnail. Set to nil to fill according to the original aspect ratio
10
+ # * +icon+ - Force icon to be shown instead of a thumbnail
11
+ #
12
+ # ==== References
13
+ # https://headmin.dev/docs/thumbnail
14
+ #
15
+ # ==== Examples
16
+ # Basic version
17
+ # <%= render "headmin/thumbnail", file: file %#>
18
+ #
19
+ # Custom size (200px x 100px)
20
+ # <%= render "headmin/thumbnail", file: file, width: 200, height: 100 %#>
21
+ #
22
+ # Custom (bootstrap) icon
23
+ # <%= render "headmin/thumbnail", file: file, icon: "file-earmark-excel" %#>
24
+ thumbnail = Headmin::ThumbnailView.new(local_assigns)
25
+ %>
2
26
 
3
- <div class="<%= thumbnail.class_names %>" style="width: <%= thumbnail.width %>px; height: <%= thumbnail.height %>px;">
4
- <% if thumbnail.image? %>
5
- <div class="h-thumbnail-bg" style="background-image: url('<%= thumbnail.src %>');"></div>
6
- <% else %>
7
- <div class="h-thumbnail-bg">
27
+ <div class="<%= thumbnail.class_names %>">
28
+ <div class="h-thumbnail-bg" style="min-width: <%= thumbnail.width || 100 %>px; min-height: <%= thumbnail.height || 100 %>px;">
29
+ <% if thumbnail.variable? && !thumbnail.icon? %>
30
+ <%= image_tag(thumbnail.file.variant(thumbnail.variant_options)) %>
31
+ <% else %>
8
32
  <%= bootstrap_icon(thumbnail.icon_name, class: "h-thumbnail-icon") %>
9
- </div>
10
- <% end %>
33
+ <% end %>
34
+ </div>
11
35
  </div>
@@ -0,0 +1,24 @@
1
+ <%
2
+ # headmin/filters/association
3
+ #
4
+ # ==== Required parameters
5
+ # * +attribute+ - Name of the attribute to be filtered
6
+ # * +collection+ - Values to create option tags for
7
+ # * +form+ - Form object
8
+ #
9
+ # ==== Optional parameters
10
+ # * +label+ - Display label
11
+ # * +name+ - Name of the filter parameter
12
+ #
13
+ # ==== Examples
14
+ # Basic version
15
+ # <%= render "headmin/filters", url: admin_orders_path do |form| %#>
16
+ # <%= render "headmin/filters/association", form: form, attribute: :product %#>
17
+ # <% end %#>
18
+
19
+ association = Headmin::Filter::AssociationView.new(local_assigns.merge(params: params))
20
+ %>
21
+
22
+ <%= render "headmin/filters/base", association.base_options do |value| %>
23
+ <%= render "headmin/forms/select", association.input_options.merge(selected: value) %>
24
+ <% end %>
@@ -1,5 +1,5 @@
1
1
  <%
2
- # headmin/filters/boolean
2
+ # headmin/filters/options
3
3
  #
4
4
  # ==== Required parameters
5
5
  # * +attribute+ - Name of the attribute to be filtered
@@ -0,0 +1,30 @@
1
+ <%
2
+ # headmin/forms/association
3
+ #
4
+ # ==== Required parameters
5
+ # * +attribute+ - Name of the association
6
+ # * +form+ - Form object
7
+ #
8
+ # ==== Optional parameters
9
+ # * +collection+ - Values to create option tags for
10
+ #
11
+ #
12
+ # ==== Examples
13
+ # Basic version
14
+ # <%= form_with do |form| %#>
15
+ # <%= render "headmin/forms/association", form: form, attribute: :product %#>
16
+ # <% end %#>
17
+
18
+ association = Headmin::Form::AssociationView.new(local_assigns)
19
+
20
+ %>
21
+
22
+ <%= render "headmin/forms/wrapper", association.wrapper_options do %>
23
+ <%= render "headmin/forms/label", association.label_options if association.prepend_label? %>
24
+ <%= render "headmin/forms/input_group", association.input_group_options do %>
25
+ <%= form.select(association.attribute_with_id, association.collection, association.select_options, association.input_options) %>
26
+ <% end %>
27
+ <%= render "headmin/forms/validation", association.validation_options if association.validate? %>
28
+ <%= render "headmin/forms/hint", association.hint_options if association.hint? %>
29
+ <%= render "headmin/forms/label", association.label_options if association.append_label? %>
30
+ <% end %>
@@ -45,12 +45,11 @@
45
45
  next unless attachment
46
46
  filename = attachment.blob.filename.to_s
47
47
  size = number_to_human_size(attachment.blob.byte_size)
48
- src = attachment.image? ? url_for(attachment.variant(resize_to_fill: [file.thumbnail_width, file.thumbnail_height])) : url_for(attachment)
49
48
  %>
50
49
  <div class="h-form-file-thumbnail" title="<%= "#{filename} (#{size})" %>" data-file-preview-target="thumbnail">
51
50
  <%= ff.hidden_field(:id, disabled: !file.destroy) %>
52
51
  <%= ff.hidden_field(:_destroy, data: {"file-preview-target": "thumbnailDestroy"}, disabled: !file.destroy) %>
53
- <%= render "headmin/thumbnail", src: src, width: file.thumbnail_width, height: file.thumbnail_height %>
52
+ <%= render "headmin/thumbnail", file: attachment %>
54
53
 
55
54
  <% if file.destroy %>
56
55
  <div class="h-form-file-thumbnail-remove" data-action="click->file-preview#remove" data-file-preview-name-param="<%= filename %>">
@@ -62,12 +61,12 @@
62
61
 
63
62
  <!-- Placeholder -->
64
63
  <% if file.dropzone %>
65
- <div class="h-dropzone-placeholder <%= "d-none" if file.attachments.any? %>" data-file-preview-target="placeholder" style="height: <%= file.thumbnail_height %>px;">
64
+ <div class="h-dropzone-placeholder <%= "d-none" if file.attachments.any? %>" data-file-preview-target="placeholder" style="height: 100px;">
66
65
  <%= t("headmin.forms.file.placeholder", count: file.number_of_files) %>
67
66
  </div>
68
67
  <% else %>
69
68
  <div class="h-form-file-thumbnail <%= "d-none" if file.attachments.any? %>" title="<%= t("headmin.forms.file.not_found") %>" data-file-preview-target="placeholder">
70
- <%= render "headmin/thumbnail", width: file.thumbnail_width, height: file.thumbnail_height, icon: "plus" %>
69
+ <%= render "headmin/thumbnail", icon: "plus" %>
71
70
  </div>
72
71
  <% end %>
73
72
  </div>
@@ -76,7 +75,7 @@
76
75
  <!-- Template -->
77
76
  <template data-file-preview-target="template">
78
77
  <div class="h-form-file-thumbnail" title="" data-file-preview-target="thumbnail">
79
- <%= render "headmin/thumbnail", src: nil, width: file.thumbnail_width, height: file.thumbnail_height %>
78
+ <%= render "headmin/thumbnail" %>
80
79
 
81
80
  <% if file.destroy %>
82
81
  <div class="h-form-file-thumbnail-remove" data-action="click->file-preview#remove">
@@ -1,5 +1,5 @@
1
1
  <%
2
- # headmin/forms/file
2
+ # headmin/forms/media
3
3
  #
4
4
  # ==== Required parameters
5
5
  # * +attribute+ - Name of the attribute of the form model
@@ -13,6 +13,8 @@
13
13
  # * +max+ - Limit the selection to a maximum amount of items.
14
14
  # * +sort+ - Allow sorting by dragging items. `active_storage_attachments` must have a position column.
15
15
  # * +wrapper+ - Hash with all options for the surrounding html tag
16
+ # * +width+ - Width of the thumbnail
17
+ # * +height+ - Height of the thumbnail
16
18
  #
17
19
  # ==== References
18
20
  # https://headmin.dev/docs/forms/media
@@ -33,13 +35,13 @@
33
35
 
34
36
  <!-- Render previews for attachments -->
35
37
  <%= form.fields_for(media.nested_attribute, media.association_object) do |ff| %>
36
- <%= render "headmin/forms/media/item", form: ff, url: media.media_modal_url, sort: media.sort %>
38
+ <%= render "headmin/forms/media/item", media.item_options.merge(form: ff, url: headmin_media_url(name: media.name, ids: media.blob_ids, min: media.min, max: media.max)) %>
37
39
  <% end %>
38
40
 
39
41
  <!-- Placeholder -->
40
42
  <div class="<%= "d-none" if media.attachments.any? %>" data-media-target="placeholder">
41
- <a href="<%= media.media_modal_url %>" data-turbo-frame="remote_modal" data-media-target="modalButton">
42
- <%= render "headmin/thumbnail", src: nil, width: 100, height: 100, icon: "plus" %>
43
+ <a href="<%= headmin_media_url(name: media.name, ids: media.blob_ids, min: media.min, max: media.max) %>" data-turbo-frame="remote_modal" data-media-target="modalButton">
44
+ <%= render "headmin/thumbnail", media.thumbnail_options %>
43
45
  </a>
44
46
  </div>
45
47
  </div>
@@ -48,7 +50,7 @@
48
50
  <% association_object = ActiveStorage::Attachment.new %>
49
51
  <template data-media-target="template" data-template-id-regex="<%= association_object.object_id %>">
50
52
  <%= form.fields_for(media.nested_attribute, ActiveStorage::Attachment.new, child_index: association_object.object_id) do |ff| %>
51
- <%= render "headmin/forms/media/item", form: ff, url: media.media_modal_url, sort: media.sort %>
53
+ <%= render "headmin/forms/media/item", media.item_options.merge(form: ff, url: headmin_media_url(name: media.name, ids: media.blob_ids, min: media.min, max: media.max)) %>
52
54
  <% end %>
53
55
  </template>
54
56
 
@@ -2,12 +2,13 @@
2
2
  # headmin/forms/repeater
3
3
  #
4
4
  # ==== Options
5
- # * +form</tt> - Form object
6
- # * +attribute</tt> - Name of the attribute of the form model
7
- # * +header</tt> - Name of the template to use as header
5
+ # * +form+ - Form object
6
+ # * +attribute+ - Name of the attribute of the form model
7
+ # * +header+ - Name of the template to use as header
8
8
  # * +label+ - Text to show as label. Label will be hidden if value is false
9
- # * +templates</tt> - List of all views that can be used as a template for a new block
10
- # * +flush</tt> - Set to true if you want the list items to sit flush with its parent.
9
+ # * +templates+ - List of all views that can be used as a template for a new block
10
+ # * +flush+ - Set to true if you want the list items to sit flush with its parent.
11
+ # * +row+ - Pass hash with options to pass to the row template.
11
12
  #
12
13
  # ==== Examples
13
14
  # # Basic version
@@ -36,6 +37,7 @@
36
37
  header = local_assigns.has_key?(:header) ? header : nil
37
38
  templates = local_assigns.has_key?(:templates) ? templates : []
38
39
  flush = local_assigns.has_key?(:flush) ? flush : true
40
+ row_options = local_assigns.has_key?(:row) ? row : {}
39
41
 
40
42
  template_names = templates.map { |template| File.basename(template, ".html.erb") }
41
43
  template_names = template_names.any? ? template_names : ["new"]
@@ -55,7 +57,7 @@
55
57
  <%= render "headmin/forms/label", form: form, attribute: attribute, text: label, required: required %>
56
58
  <% end %>
57
59
 
58
- <ul class="repeater list-group <%= "list-group-flush" if flush %>" data-controller="repeater" data-repeater-target="list" data-repeater-id-value="<%= repeater_id %>">
60
+ <ul class="repeater list-group <%= "list-group-flush" if flush %> mb-3" data-controller="repeater" data-repeater-target="list" data-repeater-id-value="<%= repeater_id %>">
59
61
 
60
62
  <!-- Header -->
61
63
  <% if header %>
@@ -66,7 +68,7 @@
66
68
 
67
69
  <!-- Rows -->
68
70
  <%= form.fields_for attribute, associations do |ff| %>
69
- <%= render "headmin/forms/repeater/row", pass_thru: pass_thru, form: ff, repeater_id: repeater_id do %>
71
+ <%= render "headmin/forms/repeater/row", row_options.merge(pass_thru: pass_thru, form: ff, repeater_id: repeater_id) do %>
70
72
  <%= yield(ff) %>
71
73
  <% end %>
72
74
  <% end %>
@@ -114,7 +116,7 @@
114
116
  <% template_names.each do |name| %>
115
117
  <template data-repeater-target="template" data-template-name="<%= name %>" data-template-id-regex="<%= association_object.object_id %>">
116
118
  <%= form.fields_for attribute, association_object, child_index: association_object.object_id do |ff| %>
117
- <%= render "headmin/forms/repeater/row", form: ff, pass_thru: pass_thru, repeater_id: repeater_id do %>
119
+ <%= render "headmin/forms/repeater/row", row_options.merge(form: ff, pass_thru: pass_thru, repeater_id: repeater_id) do %>
118
120
  <% yield(ff, name) %>
119
121
  <% end %>
120
122
  <% end %>
@@ -1,5 +1,4 @@
1
1
  <% wrapper = Headmin::Form::WrapperView.new(local_assigns) %>
2
-
3
2
  <% if wrapper.bypass %>
4
3
  <%= yield %>
5
4
  <% else %>
@@ -19,8 +19,10 @@
19
19
  %>
20
20
 
21
21
  <%= render "headmin/forms/fields/base", form: form, name: name, field_type: :list do |list, field_label| %>
22
- <%= render "headmin/forms/repeater", form: list, attribute: :fields, label: show_label ? label || field_label : false, flush: false do |field| %>
23
- <%= field.hidden_field :field_type, value: :group %>
24
- <%= yield field %>
25
- <% end %>
22
+ <div class="mb-3">
23
+ <%= render "headmin/forms/repeater", form: list, attribute: :fields, label: show_label ? label || field_label : false, flush: false do |field| %>
24
+ <%= field.hidden_field :field_type, value: :group %>
25
+ <%= yield field %>
26
+ <% end %>
27
+ </div>
26
28
  <% end %>