headmin 0.5.5 → 0.5.6

Sign up to get free protection for your applications and to get access to all the features.
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
- } %>