headmin 0.5.5 → 0.5.6

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3445263c38deb23953f15bd095f0ab4d35acdba03e57863990afc270b3b0ec2d
4
- data.tar.gz: 7ff3cbb8b497e7abfec49ee1222f3980b08779627179d788cd332794076c3ec7
3
+ metadata.gz: 7f5b560711076cca763871af2499728101e4f8d7a27a04f5c702671165e134d7
4
+ data.tar.gz: ed2d18ec0405786fd9b4f18984e750571062124828fc1102a662f78e0333c03e
5
5
  SHA512:
6
- metadata.gz: 6b6d3c45da2fc0d1e3d9d3f69daccfdb689eae2785cc46d19fdec80ffff497987193fcb251eb2576f95d2d7927179d2c0264fa0548c3363240193a0c51e7efcc
7
- data.tar.gz: 1badf3f039045aaf9ddee10bcf1f2686bd3cd44f0e04db6f893c8e61c4cf8974b3464a427bcf6c66cbc7bfd03e2b502d48bd4edd8a4c26527484feefac74691b
6
+ metadata.gz: 8afa230d5e31dad3cbaa288fe46849672af9c4df118b4e9a9ed8024eea60da7d1089865b3678ee67860cfe88d34f67ba3a3db0c2b8ee1e37ebfbe515e2ae07fc
7
+ data.tar.gz: 106023130df278d025a608345e763aac87b6586736e511fd05866703876b296de9ae08d43ea5fd86d3f7cb75d378935e3aa86ff2d9f519d50434ee02e6453dc8
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- headmin (0.5.4)
4
+ headmin (0.5.5)
5
5
  closure_tree (~> 7.4)
6
6
  inline_svg (~> 1.7)
7
7
  redcarpet (~> 3.5)
@@ -226,7 +226,7 @@ GEM
226
226
  actionpack (>= 5.0)
227
227
  railties (>= 5.0)
228
228
  rexml (3.2.5)
229
- rouge (3.29.0)
229
+ rouge (3.30.0)
230
230
  route_translator (12.1.0)
231
231
  actionpack (>= 5.2, < 7.1)
232
232
  activesupport (>= 5.2, < 7.1)
@@ -3,7 +3,7 @@ import Sortable from 'sortablejs'
3
3
 
4
4
  export default class extends Controller {
5
5
  static get targets () {
6
- return ['item', 'template', 'thumbnails', 'modalButton', 'placeholder', 'validationInput', 'count', 'editButton']
6
+ return ['item', 'template', 'thumbnails', 'modalButton', 'placeholder', 'count', 'editButton']
7
7
  }
8
8
 
9
9
  connect () {
@@ -17,8 +17,6 @@ export default class extends Controller {
17
17
  if (this.hasSorting()) {
18
18
  this.initSortable()
19
19
  }
20
-
21
- this.validate()
22
20
  }
23
21
 
24
22
  // Actions
@@ -83,42 +81,6 @@ export default class extends Controller {
83
81
 
84
82
  // Toggle placeholder
85
83
  this.togglePlaceholder()
86
-
87
- // Validate
88
- this.validate()
89
- }
90
-
91
- validate () {
92
- this.clearValidation()
93
- if (this.element.dataset.required === '0') return
94
- this.validateMinimum()
95
- this.validateMaximum()
96
- }
97
-
98
- clearValidation () {
99
- this.validationInputTarget.setCustomValidity('')
100
- }
101
-
102
- validateMinimum () {
103
- const count = this.activeItems().length
104
- if (count < this.minActiveItems()) {
105
- this.validationInputTarget.setCustomValidity(this.validationInputTarget.dataset.minMessage)
106
- }
107
- }
108
-
109
- validateMaximum () {
110
- const count = this.activeItems().length
111
- if (count > this.maxActiveItems()) {
112
- this.validationInputTarget.setCustomValidity(this.validationInputTarget.dataset.maxMessage)
113
- }
114
- }
115
-
116
- minActiveItems () {
117
- return parseInt(this.element.dataset.min, 10) || 0
118
- }
119
-
120
- maxActiveItems () {
121
- return parseInt(this.element.dataset.max, 10) || Infinity
122
84
  }
123
85
 
124
86
  resetPositions () {
@@ -80,8 +80,8 @@ export default class extends Controller {
80
80
  row.remove()
81
81
  } else {
82
82
  // Existing records are hidden and flagged for deletion
83
- row.querySelector("input[name*='_destroy']").value = 1
84
- row.style.display = 'none'
83
+ this.flagRowForDeletion(row)
84
+ row.remove()
85
85
  }
86
86
 
87
87
  this.resetIndices()
@@ -89,6 +89,18 @@ export default class extends Controller {
89
89
  this.toggleEmpty()
90
90
  }
91
91
 
92
+ flagRowForDeletion (row) {
93
+ const destroyInput = row.querySelector('input[name*=\'_destroy\']')
94
+ const idInput = row.querySelector('input[name*=\'[id]\']')
95
+
96
+ // Update _destroy value
97
+ destroyInput.value = 1
98
+
99
+ // Move away from row
100
+ this.listTarget.parentNode.appendChild(destroyInput)
101
+ this.listTarget.parentNode.appendChild(idInput)
102
+ }
103
+
92
104
  getTemplate (name) {
93
105
  return this.templateTargets.filter((template) => {
94
106
  return template.dataset.templateName === name
@@ -107,7 +119,7 @@ export default class extends Controller {
107
119
  visibleRows () {
108
120
  const rows = this.rowTargets
109
121
  return rows.filter((row) => {
110
- return row.querySelector("input[name*='_destroy']").value !== '1'
122
+ return row.querySelector('input[name*=\'_destroy\']').value !== '1'
111
123
  })
112
124
  }
113
125
 
@@ -121,7 +133,7 @@ export default class extends Controller {
121
133
 
122
134
  resetPositions () {
123
135
  this.visibleRows().forEach((row, index) => {
124
- const positionInput = row.querySelector("input[name*='position']")
136
+ const positionInput = row.querySelector('input[name*=\'position\']')
125
137
  if (positionInput) {
126
138
  positionInput.value = index
127
139
  }
@@ -10096,7 +10096,7 @@ var hello_controller_default = class extends Controller {
10096
10096
  // app/assets/javascripts/headmin/controllers/media_controller.js
10097
10097
  var media_controller_default = class extends Controller {
10098
10098
  static get targets() {
10099
- return ["item", "template", "thumbnails", "modalButton", "placeholder", "validationInput", "count", "editButton"];
10099
+ return ["item", "template", "thumbnails", "modalButton", "placeholder", "count", "editButton"];
10100
10100
  }
10101
10101
  connect() {
10102
10102
  document.addEventListener("mediaSelectionSubmitted", (event) => {
@@ -10107,7 +10107,6 @@ var media_controller_default = class extends Controller {
10107
10107
  if (this.hasSorting()) {
10108
10108
  this.initSortable();
10109
10109
  }
10110
- this.validate();
10111
10110
  }
10112
10111
  destroy(event) {
10113
10112
  const item = event.currentTarget.closest("[data-media-target='item']");
@@ -10148,35 +10147,6 @@ var media_controller_default = class extends Controller {
10148
10147
  this.resetPositions();
10149
10148
  this.syncIds();
10150
10149
  this.togglePlaceholder();
10151
- this.validate();
10152
- }
10153
- validate() {
10154
- this.clearValidation();
10155
- if (this.element.dataset.required === "0")
10156
- return;
10157
- this.validateMinimum();
10158
- this.validateMaximum();
10159
- }
10160
- clearValidation() {
10161
- this.validationInputTarget.setCustomValidity("");
10162
- }
10163
- validateMinimum() {
10164
- const count = this.activeItems().length;
10165
- if (count < this.minActiveItems()) {
10166
- this.validationInputTarget.setCustomValidity(this.validationInputTarget.dataset.minMessage);
10167
- }
10168
- }
10169
- validateMaximum() {
10170
- const count = this.activeItems().length;
10171
- if (count > this.maxActiveItems()) {
10172
- this.validationInputTarget.setCustomValidity(this.validationInputTarget.dataset.maxMessage);
10173
- }
10174
- }
10175
- minActiveItems() {
10176
- return parseInt(this.element.dataset.min, 10) || 0;
10177
- }
10178
- maxActiveItems() {
10179
- return parseInt(this.element.dataset.max, 10) || Infinity;
10180
10150
  }
10181
10151
  resetPositions() {
10182
10152
  this.activeItems().forEach((item, index2) => {
@@ -15559,13 +15529,20 @@ var repeater_controller_default = class extends Controller {
15559
15529
  if (row.dataset.newRecord === "true") {
15560
15530
  row.remove();
15561
15531
  } else {
15562
- row.querySelector("input[name*='_destroy']").value = 1;
15563
- row.style.display = "none";
15532
+ this.flagRowForDeletion(row);
15533
+ row.remove();
15564
15534
  }
15565
15535
  this.resetIndices();
15566
15536
  this.resetPositions();
15567
15537
  this.toggleEmpty();
15568
15538
  }
15539
+ flagRowForDeletion(row) {
15540
+ const destroyInput = row.querySelector("input[name*='_destroy']");
15541
+ const idInput = row.querySelector("input[name*='[id]']");
15542
+ destroyInput.value = 1;
15543
+ this.listTarget.parentNode.appendChild(destroyInput);
15544
+ this.listTarget.parentNode.appendChild(idInput);
15545
+ }
15569
15546
  getTemplate(name) {
15570
15547
  return this.templateTargets.filter((template) => {
15571
15548
  return template.dataset.templateName === name;
@@ -1,7 +1,7 @@
1
1
  @charset "UTF-8";
2
2
  @import "https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css";
3
3
 
4
- /* sass-plugin-0:/usr/local/var/www/headmin/src/scss/headmin.scss */
4
+ /* sass-plugin-0:/opt/homebrew/var/www/headmin/src/scss/headmin.scss */
5
5
  :root {
6
6
  --bs-blue: #0d6efd;
7
7
  --bs-indigo: #6610f2;
@@ -4,12 +4,12 @@ class Headmin::MediaController < HeadminController
4
4
  def index
5
5
  @blobs =
6
6
  ActiveStorage::Blob
7
- .left_outer_joins(:attachments)
8
- .where.not(active_storage_attachments: {record_type: "ActiveStorage::VariantRecord"}) # Not a variant
9
- .or(ActiveStorage::Blob.where(active_storage_attachments: {id: nil})) # Or an orphan
7
+ .not_attached_to_variant
8
+ .by_mimetypes_string(media_params[:mimetype])
10
9
  .order(created_at: :desc)
11
10
  .group(:id)
12
11
  .all
12
+ @mimetypes = media_params[:mimetype]
13
13
  end
14
14
 
15
15
  def create
@@ -42,6 +42,11 @@ class Headmin::MediaController < HeadminController
42
42
 
43
43
  def media_params
44
44
  params.permit(
45
+ :min,
46
+ :max,
47
+ :name,
48
+ :mimetype,
49
+ ids: [],
45
50
  files: []
46
51
  )
47
52
  end
@@ -0,0 +1,36 @@
1
+ module Headmin
2
+ module Blob
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class << self
7
+ def not_attached_to_variant
8
+ left_outer_joins(:attachments)
9
+ .where.not(active_storage_attachments: {record_type: "ActiveStorage::VariantRecord"})
10
+ .or(is_orphan)
11
+ end
12
+
13
+ def is_orphan
14
+ left_outer_joins(:attachments)
15
+ .where(active_storage_attachments: {id: nil})
16
+ end
17
+
18
+ def by_mimetypes_string(mimetype_string)
19
+ by_mimetypes(mimetype_string.split(","))
20
+ end
21
+
22
+ def by_mimetypes(mimetypes = [])
23
+ results = self
24
+
25
+ mimetypes.map.with_index do |mimetype, index|
26
+ content_type = mimetype.tr("*", "%")
27
+ query = where(arel_table[:content_type].matches(content_type))
28
+ results = index == 0 ? query : results.or(query)
29
+ end
30
+
31
+ results
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -4,7 +4,7 @@ module Headmin
4
4
 
5
5
  included do
6
6
  # Configuration
7
- has_closure_tree order: "position", numeric_order: true
7
+ has_closure_tree order: "position", numeric_order: true, dependent: :destroy
8
8
 
9
9
  # Associations
10
10
  belongs_to :fieldable, polymorphic: true, optional: true, touch: true
@@ -28,7 +28,10 @@ module Headmin
28
28
  private
29
29
 
30
30
  def parse_fields
31
- hash_tree = fields.includes(default_include_tables | include_tables).hash_tree
31
+ hash_tree = {}
32
+ fields.roots.includes(default_include_tables | include_tables).each do |field|
33
+ field.hash_tree.map { |key, value| hash_tree[key] = value }
34
+ end
32
35
  parse_hash_tree(hash_tree)
33
36
  end
34
37
 
@@ -27,7 +27,7 @@ module Headmin
27
27
  {
28
28
  label: label,
29
29
  name: "field_#{attribute}".to_sym,
30
- filter: Headmin::Filter::Field.new("field_#{attribute}".to_sym, @params),
30
+ filter: Headmin::Filter::Field.new(attribute.to_s.to_sym, @params),
31
31
  allowed_operators: Headmin::Filter::Field::OPERATORS - %w[in not_in]
32
32
  }
33
33
  end
@@ -31,6 +31,10 @@ module Headmin
31
31
  attachment.blob&.filename&.to_s
32
32
  end
33
33
 
34
+ def content_type
35
+ attachment.blob&.content_type
36
+ end
37
+
34
38
  def size
35
39
  number_to_human_size(attachment.blob&.byte_size || 0)
36
40
  end
@@ -23,6 +23,7 @@ module Headmin
23
23
  min: min,
24
24
  max: max,
25
25
  sort: sort,
26
+ accept: accept,
26
27
  required: required.nil? ? 0 : required
27
28
  }
28
29
  }).deep_merge(@wrapper || {})
@@ -45,7 +46,8 @@ module Headmin
45
46
  form: form,
46
47
  attribute: attribute,
47
48
  min: min,
48
- max: max
49
+ max: max,
50
+ accept: accept
49
51
  }
50
52
  end
51
53
 
@@ -117,6 +119,8 @@ module Headmin
117
119
  end
118
120
  end
119
121
 
122
+ attr_reader :accept
123
+
120
124
  def required
121
125
  @required ? 1 : nil
122
126
  end
@@ -31,16 +31,14 @@
31
31
  <%= render "headmin/forms/wrapper", media.wrapper_options do %>
32
32
  <%= render "headmin/forms/label", media.label_options if media.prepend_label? %>
33
33
  <div class="h-form-file-thumbnails" data-media-target="thumbnails">
34
- <%= render "headmin/forms/media/validation", media.custom_validation_options %>
35
-
36
34
  <!-- Render previews for attachments -->
37
35
  <%= form.fields_for(media.nested_attribute, media.association_object) do |ff| %>
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)) %>
36
+ <%= 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, mimetype: media.accept)) %>
39
37
  <% end %>
40
38
 
41
39
  <!-- Placeholder -->
42
40
  <div class="<%= "d-none" if media.attachments.any? %>" data-media-target="placeholder">
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">
41
+ <a href="<%= headmin_media_url(name: media.name, ids: media.blob_ids, min: media.min, max: media.max, mimetype: media.accept) %>" data-turbo-frame="remote_modal" data-media-target="modalButton">
44
42
  <%= render "headmin/thumbnail", media.thumbnail_options %>
45
43
  </a>
46
44
  </div>
@@ -50,7 +48,7 @@
50
48
  <% association_object = ActiveStorage::Attachment.new %>
51
49
  <template data-media-target="template" data-template-id-regex="<%= association_object.object_id %>">
52
50
  <%= form.fields_for(media.nested_attribute, ActiveStorage::Attachment.new, child_index: association_object.object_id) do |ff| %>
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)) %>
51
+ <%= 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, mimetype: media.accept)) %>
54
52
  <% end %>
55
53
  </template>
56
54
 
@@ -22,7 +22,7 @@
22
22
  <%= form.label :files, class: "btn h-btn-outline-light" do %>
23
23
  <%= bootstrap_icon("upload") %>
24
24
  <%= t(".upload") %>
25
- <%= form.file_field :files, class: "d-none", multiple: true, data: {action: "change->media-modal#submitForm"} %>
25
+ <%= form.file_field :files, accept: mimetypes, class: "d-none", multiple: true, data: {action: "change->media-modal#submitForm"} %>
26
26
  <% end %>
27
27
  <% end %>
28
28
  <button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><%= t(".close") %></button>
@@ -1,3 +1,3 @@
1
1
  <%= turbo_frame_tag "remote_modal" do %>
2
- <%= render "headmin/media/modal", blobs: @blobs, name: params[:name], min: params[:min], max: params[:max] %>
2
+ <%= render "headmin/media/modal", blobs: @blobs, mimetypes: @mimetypes, name: params[:name], min: params[:min], max: params[:max] %>
3
3
  <% end %>
@@ -0,0 +1,3 @@
1
+ Rails.configuration.to_prepare do
2
+ ActiveStorage::Blob.include Headmin::Blob
3
+ end
@@ -18,14 +18,6 @@ en:
18
18
  remove:
19
19
  title: Delete
20
20
  confirm: Are you sure you want to delete this?
21
- media:
22
- validation:
23
- min:
24
- one: "Please select at least 1 item"
25
- other: "Please select at least %{count} items"
26
- max:
27
- one: "Please limit your selection to maximum 1 item"
28
- other: "Please limit your selection to maximum %{count} items"
29
21
  select:
30
22
  blank: Make a choice
31
23
  repeater:
@@ -17,14 +17,6 @@ nl:
17
17
  remove:
18
18
  title: Verwijderen
19
19
  confirm: Ben je zeker dat je dit wil verwijderen?
20
- media:
21
- validation:
22
- min:
23
- one: "Gelieve minstens 1 item te selecteren"
24
- other: "Gelieve minstens %{count} items te selecteren"
25
- max:
26
- one: "Gelieve maximum 1 item te selecteren"
27
- other: "Gelieve maximum %{count} items te selecteren"
28
20
  select:
29
21
  blank: Maak een keuze
30
22
  repeater:
@@ -1,3 +1,3 @@
1
1
  module Headmin
2
- VERSION = "0.5.5"
2
+ VERSION = "0.5.6"
3
3
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "headmin",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "description": "Admin component library",
5
5
  "module": "app/assets/javascripts/headmin.js",
6
6
  "main": "app/assets/javascripts/headmin.js",
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: headmin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.5
4
+ version: 0.5.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jef Vlamings
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-09-29 00:00:00.000000000 Z
11
+ date: 2022-11-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: closure_tree
@@ -244,6 +244,7 @@ files:
244
244
  - app/helpers/headmin/form_helper.rb
245
245
  - app/helpers/headmin/notification_helper.rb
246
246
  - app/helpers/headmin/request_helper.rb
247
+ - app/models/concerns/headmin/blob.rb
247
248
  - app/models/concerns/headmin/block.rb
248
249
  - app/models/concerns/headmin/blockable.rb
249
250
  - app/models/concerns/headmin/field.rb
@@ -393,7 +394,6 @@ files:
393
394
  - app/views/headmin/forms/fields/_list.html.erb
394
395
  - app/views/headmin/forms/fields/_text.html.erb
395
396
  - app/views/headmin/forms/media/_item.html.erb
396
- - app/views/headmin/forms/media/_validation.html.erb
397
397
  - app/views/headmin/forms/repeater/_row.html.erb
398
398
  - app/views/headmin/heading/_title.html.erb
399
399
  - app/views/headmin/layout/_body.html.erb
@@ -466,6 +466,7 @@ files:
466
466
  - bin/setup
467
467
  - config/importmap.rb
468
468
  - config/initializers/customize_input_error.rb
469
+ - config/initializers/extend_active_storage_blob.rb
469
470
  - config/locales/activerecord/en.yml
470
471
  - config/locales/activerecord/nl.yml
471
472
  - config/locales/defaults/en.yml
@@ -1,10 +0,0 @@
1
- <!-- Custom validation field -->
2
- <%= form.text_field :"validation_#{attribute}",
3
- name: nil,
4
- value: nil,
5
- class: "h-form-media-validation",
6
- data: {
7
- "media-target": "validationInput",
8
- "min-message": t(".min", count: min),
9
- "max-message": t(".max", count: max),
10
- } %>