headmin 0.6.1 → 0.6.3

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +5 -4
  3. data/app/assets/javascripts/headmin/controllers/filter_controller.js +15 -3
  4. data/app/assets/javascripts/headmin/controllers/filter_row_controller.js +28 -0
  5. data/app/assets/javascripts/headmin/controllers/infinite_scroller_controller.js +28 -0
  6. data/app/assets/javascripts/headmin/controllers/media_controller.js +24 -8
  7. data/app/assets/javascripts/headmin/controllers/media_modal_controller.js +47 -10
  8. data/app/assets/javascripts/headmin/index.js +2 -0
  9. data/app/assets/javascripts/headmin.js +122 -19
  10. data/app/controllers/headmin/media_controller.rb +11 -0
  11. data/app/helpers/headmin/form_helper.rb +0 -11
  12. data/app/models/headmin/filter/association.rb +0 -12
  13. data/app/models/headmin/filter/association_count.rb +50 -0
  14. data/app/models/headmin/filter/association_count_view.rb +78 -0
  15. data/app/models/headmin/filter/base.rb +10 -0
  16. data/app/models/headmin/filter/date.rb +8 -1
  17. data/app/models/headmin/filter/date_view.rb +1 -1
  18. data/app/models/headmin/filter/number.rb +0 -12
  19. data/app/models/headmin/filter/operator_view.rb +3 -1
  20. data/app/views/headmin/_filters.html.erb +1 -1
  21. data/app/views/headmin/filters/_association_count.html.erb +28 -0
  22. data/app/views/headmin/filters/_date.html.erb +1 -0
  23. data/app/views/headmin/media/_modal.html.erb +8 -3
  24. data/app/views/headmin/media/_thumbnail.html.erb +20 -0
  25. data/app/views/headmin/media/create.turbo_stream.erb +3 -1
  26. data/app/views/headmin/media/index.turbo_stream.erb +11 -0
  27. data/app/views/headmin/media/thumbnail.html.erb +3 -0
  28. data/app/views/headmin/pagination/_infinite.html.erb +7 -0
  29. data/config/locales/headmin/filters/en.yml +2 -0
  30. data/config/locales/headmin/filters/nl.yml +2 -0
  31. data/config/locales/headmin/pagination/en.yml +2 -0
  32. data/config/locales/headmin/pagination/nl.yml +2 -0
  33. data/config/routes.rb +2 -1
  34. data/lib/headmin/version.rb +1 -1
  35. data/package.json +1 -1
  36. metadata +11 -4
  37. data/app/views/headmin/media/_item.html.erb +0 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4f0d94377c6d3cf0d97bdffa08c34d9c95ef9b72036c8e7b1d0d1b89996ed52d
4
- data.tar.gz: 9b263a38b16dacace7769f9eb2cab3b90dc478cc229fead7ae157c4fd48e7f65
3
+ metadata.gz: 819c038f09a0d2dab83f4931d67256dd6130244afcff409af0580d8abf72d02c
4
+ data.tar.gz: 28fa2501518683ed07ce67315a42b62b1f2d8749208196a84ef20642125b815b
5
5
  SHA512:
6
- metadata.gz: 3ee1ebb9dbc530154fd310c910dbab36e13272a0c3433b494588e6f552c5e8af6c6ee4f39b41f2737a7b2321805c02d9a5c22b7dfeea9c62455584ecc0cbc907
7
- data.tar.gz: e88897ec516a9b4f83188f8eb12e1b49d46da547d03b459942da6f77266faee9031f0f3f06c5300529183e6f6cd9aa2112e92ce1b30ba7902edcd919b0f9ceaf
6
+ metadata.gz: a117642e4cd9cc87484aa37ca85c0ba95a0e0747111b51ce87c529c92510a4b7dfa715aa432d8993b37b412aab30fa51f4a0b4876e2058d2fd83086ef1145a54
7
+ data.tar.gz: 06247200375b3fa43820d017fd7a174e29e5acea8a8c4fda239c95a8388e6df33918e1995fa4488e77c8c915ea8f9fc715d50f390e11edffc60794ca6eeeb2e3
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- headmin (0.6.0)
4
+ headmin (0.6.3)
5
5
  closure_tree (~> 7.4)
6
6
  inline_svg (~> 1.7)
7
7
  redcarpet (~> 3.5)
@@ -218,7 +218,7 @@ GEM
218
218
  zeitwerk (~> 2.5)
219
219
  rainbow (3.1.1)
220
220
  rake (13.0.6)
221
- redcarpet (3.5.1)
221
+ redcarpet (3.6.0)
222
222
  regexp_parser (2.6.2)
223
223
  reline (0.3.2)
224
224
  io-console (~> 0.5)
@@ -288,8 +288,9 @@ GEM
288
288
  websocket-driver (0.7.5)
289
289
  websocket-extensions (>= 0.1.0)
290
290
  websocket-extensions (0.1.5)
291
- with_advisory_lock (4.6.0)
292
- activerecord (>= 4.2)
291
+ with_advisory_lock (5.1.0)
292
+ activerecord (>= 6.1)
293
+ zeitwerk (>= 2.6)
293
294
  xpath (3.2.0)
294
295
  nokogiri (~> 1.8)
295
296
  zeitwerk (2.6.6)
@@ -15,8 +15,6 @@ export default class extends Controller {
15
15
  // This allows calling controller methods from the element in other controllers
16
16
  connect () {
17
17
  this.element.controller = this
18
-
19
- this.updateHiddenValue()
20
18
  }
21
19
 
22
20
  toggle (event) {
@@ -89,7 +87,21 @@ export default class extends Controller {
89
87
  for (const row of this.rowTargets) {
90
88
  const conditional = row.previousElementSibling ? row.previousElementSibling.querySelector('[data-filter-target="conditional"]').value : null
91
89
  const operator = row.querySelector('[data-filter-target="operator"]').value
92
- const value = row.querySelector('[data-filter-target="value"]').value
90
+ let values = Array.from(row.querySelectorAll('[data-filter-target="value"]'))
91
+
92
+ // Only the visible elements are of interest
93
+ values = values.filter((element) => {
94
+ return element.style.display
95
+ })
96
+
97
+ // Grab the value of each visible element
98
+ values = values.map((element) => {
99
+ return element.value
100
+ })
101
+
102
+ // Concatenate array to a string
103
+ const value = values.join(',')
104
+
93
105
  string += `${conditional || ''}${operator}:${value}`
94
106
  }
95
107
 
@@ -13,6 +13,8 @@ export default class extends Controller {
13
13
  handleOperatorChange () {
14
14
  if (this.operatorTarget.value === 'is_null' || this.operatorTarget.value === 'is_not_null') {
15
15
  this.toggleNullInput()
16
+ } else if (this.operatorTarget.value === 'between' || this.operatorTarget.value === 'not_between') {
17
+ this.toggleSecondaryInput()
16
18
  } else {
17
19
  this.toggleOriginalInput()
18
20
  }
@@ -20,11 +22,19 @@ export default class extends Controller {
20
22
 
21
23
  toggleNullInput () {
22
24
  this.hideOriginal()
25
+ this.hideSecondary()
23
26
  this.showNull()
24
27
  }
25
28
 
26
29
  toggleOriginalInput () {
27
30
  this.showOriginal()
31
+ this.hideSecondary()
32
+ this.hideNull()
33
+ }
34
+
35
+ toggleSecondaryInput () {
36
+ this.showSecondary()
37
+ this.hideOriginal()
28
38
  this.hideNull()
29
39
  }
30
40
 
@@ -38,6 +48,24 @@ export default class extends Controller {
38
48
  this.originalTarget.setAttribute('data-filter-target', 'value')
39
49
  }
40
50
 
51
+ hideSecondary () {
52
+ for (const [index, value] of this.originalTargets.entries()) {
53
+ if (index !== 0) {
54
+ value.style.display = 'none'
55
+ value.setAttribute('data-filter-target', 'value_original')
56
+ }
57
+ }
58
+ }
59
+
60
+ showSecondary () {
61
+ for (const [index, value] of this.originalTargets.entries()) {
62
+ if (index !== 0) {
63
+ value.style.display = 'block'
64
+ value.setAttribute('data-filter-target', 'value')
65
+ }
66
+ }
67
+ }
68
+
41
69
  hideNull () {
42
70
  this.nullTarget.style.display = 'none'
43
71
  this.nullTarget.setAttribute('data-filter-target', 'value_null')
@@ -0,0 +1,28 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ export default class extends Controller {
4
+ connect () {
5
+ this.clickWhenInViewport()
6
+
7
+ document.querySelector('.modal-body').addEventListener('scroll', () => {
8
+ this.clickWhenInViewport()
9
+ })
10
+ }
11
+
12
+ clickWhenInViewport () {
13
+ if (!this.isLoading() && this.isInViewport()) {
14
+ this.element.setAttribute('clicked', 1)
15
+ this.element.click()
16
+ }
17
+ }
18
+
19
+ isLoading () {
20
+ return this.element.hasAttribute('clicked')
21
+ }
22
+
23
+ isInViewport () {
24
+ const rect = this.element.getBoundingClientRect()
25
+
26
+ return rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth)
27
+ }
28
+ }
@@ -66,10 +66,10 @@ export default class extends Controller {
66
66
 
67
67
  selectItems (items) {
68
68
  // Destroy all deselected items
69
- this.removeAllItems()
69
+ this.removeAllDeselectedItems(items)
70
70
 
71
- // Add all selected items
72
- this.addItems(items)
71
+ // Add all new selected items
72
+ this.addNewItems(items)
73
73
 
74
74
  this.postProcess()
75
75
  }
@@ -129,8 +129,16 @@ export default class extends Controller {
129
129
  })
130
130
  }
131
131
 
132
- addItems (items) {
133
- items.forEach((item) => this.addItem(item))
132
+ addNewItems (items) {
133
+ const itemTargetIds = this.itemTargets.map((i) => { return parseInt(i.querySelectorAll('input')[1].value) })
134
+ items.forEach((item) => {
135
+ if (itemTargetIds.includes(item.blobId)) {
136
+ // Do not add this item (as it is already present)
137
+ return
138
+ }
139
+
140
+ this.addItem(item)
141
+ })
134
142
  }
135
143
 
136
144
  addItem (item) {
@@ -192,12 +200,20 @@ export default class extends Controller {
192
200
  return template.innerHTML.replace(regex, randomNumber)
193
201
  }
194
202
 
195
- removeAllItems () {
196
- this.removeItems(this.itemTargets)
203
+ removeAllDeselectedItems (items) {
204
+ this.removeDeselectedItems(items, this.itemTargets)
197
205
  }
198
206
 
199
- removeItems (items) {
207
+ removeDeselectedItems (elements, items) {
208
+ const returnedBlobIds = elements.map((e) => { return e.blobId })
209
+
200
210
  items.forEach((item) => {
211
+ const blobId = parseInt(item.querySelectorAll('input')[1].value)
212
+ if (returnedBlobIds.includes(blobId)) {
213
+ // Do not delete this one
214
+ return
215
+ }
216
+
201
217
  this.removeItem(item)
202
218
  })
203
219
  }
@@ -6,6 +6,10 @@ export default class extends Controller {
6
6
  return ['idCheckbox', 'item', 'form', 'selectButton', 'placeholder', 'count']
7
7
  }
8
8
 
9
+ static get values () {
10
+ return { ids: Array }
11
+ }
12
+
9
13
  connect () {
10
14
  this.validate()
11
15
  this.updateCount()
@@ -21,8 +25,8 @@ export default class extends Controller {
21
25
  this.triggerFormSubmission()
22
26
  }
23
27
 
24
- inputChange () {
25
- this.handleInputChange()
28
+ inputChange (event) {
29
+ this.handleIdsUpdate(event.target)
26
30
  this.updateCount()
27
31
  }
28
32
 
@@ -31,7 +35,36 @@ export default class extends Controller {
31
35
  this.placeholderTarget.classList.add('d-none')
32
36
  }
33
37
 
34
- handleInputChange () {
38
+ handleIdsUpdate (element) {
39
+ if (element.checked) {
40
+ const arr = this.idsValue
41
+ arr.push(element.value)
42
+ this.idsValue = arr
43
+ } else {
44
+ this.idsValue = this.idsValue.filter((value) => {
45
+ return element.value !== value
46
+ })
47
+ }
48
+ }
49
+
50
+ itemTargetConnected (element) {
51
+ this.updateItem(element.querySelector('input'))
52
+ }
53
+
54
+ updateItem (element) {
55
+ const arr = this.idsValue
56
+
57
+ if (arr.includes(element.value)) {
58
+ element.checked = true
59
+ } else {
60
+ element.checked = false
61
+ }
62
+ }
63
+
64
+ idsValueChanged () {
65
+ for (const item of this.itemTargets) {
66
+ this.updateItem(item.querySelector('input'))
67
+ }
35
68
  this.validate()
36
69
  }
37
70
 
@@ -42,7 +75,7 @@ export default class extends Controller {
42
75
  {
43
76
  detail: {
44
77
  name: this.element.dataset.name,
45
- items: this.renderItemsForEvent(this.selectedItems())
78
+ items: this.renderItemsForEvent()
46
79
  }
47
80
  }
48
81
  )
@@ -53,14 +86,18 @@ export default class extends Controller {
53
86
  this.formTarget.requestSubmit()
54
87
  }
55
88
 
56
- renderItemsForEvent (items) {
57
- return items.map((item) => this.renderItemForEvent(item))
89
+ renderItemsForEvent () {
90
+ return this.idsValue.map((item) => this.renderItemForEvent(item)).filter((i) => { return i !== undefined })
58
91
  }
59
92
 
60
93
  renderItemForEvent (item) {
94
+ const id = parseInt(item)
95
+ const blobId = `#blob_${id}`
96
+ const element = this.element.querySelector(blobId)
97
+
61
98
  return {
62
- blobId: item.querySelector('input[type="checkbox"]').value,
63
- thumbnail: item.querySelector('.h-thumbnail')
99
+ blobId: id,
100
+ thumbnail: element ? element.querySelector('.h-thumbnail') : ''
64
101
  }
65
102
  }
66
103
 
@@ -72,7 +109,7 @@ export default class extends Controller {
72
109
  }
73
110
 
74
111
  selectedItemsCount () {
75
- return this.selectedItems().length
112
+ return this.idsValue.length
76
113
  }
77
114
 
78
115
  minSelectedItems () {
@@ -105,6 +142,6 @@ export default class extends Controller {
105
142
  }
106
143
 
107
144
  updateCount () {
108
- this.countTarget.innerHTML = this.idCheckboxTargets.filter(checkbox => checkbox.checked).length
145
+ this.countTarget.innerHTML = this.selectedItemsCount()
109
146
  }
110
147
  }
@@ -10,6 +10,7 @@ import FilterRowController from './controllers/filter_row_controller'
10
10
  import FiltersController from './controllers/filters_controller'
11
11
  import FlatpickrController from './controllers/flatpickr_controller'
12
12
  import HelloController from './controllers/hello_controller'
13
+ import InfiniteScrollerController from './controllers/infinite_scroller_controller'
13
14
  import MediaController from './controllers/media_controller'
14
15
  import MediaModalController from './controllers/media_modal_controller'
15
16
  import NotificationController from './controllers/notification_controller'
@@ -35,6 +36,7 @@ export class Headmin {
35
36
  Stimulus.register('filters', FiltersController)
36
37
  Stimulus.register('flatpickr', FlatpickrController)
37
38
  Stimulus.register('hello', HelloController)
39
+ Stimulus.register('infinite-scroller', InfiniteScrollerController)
38
40
  Stimulus.register('media', MediaController)
39
41
  Stimulus.register('media-modal', MediaModalController)
40
42
  Stimulus.register('notification', NotificationController)
@@ -7718,7 +7718,6 @@ var filter_controller_default = class extends Controller {
7718
7718
  }
7719
7719
  connect() {
7720
7720
  this.element.controller = this;
7721
- this.updateHiddenValue();
7722
7721
  }
7723
7722
  toggle(event) {
7724
7723
  const expanded = this.buttonTarget.getAttribute("aria-expanded") === "true";
@@ -7776,7 +7775,14 @@ var filter_controller_default = class extends Controller {
7776
7775
  for (const row of this.rowTargets) {
7777
7776
  const conditional = row.previousElementSibling ? row.previousElementSibling.querySelector('[data-filter-target="conditional"]').value : null;
7778
7777
  const operator = row.querySelector('[data-filter-target="operator"]').value;
7779
- const value = row.querySelector('[data-filter-target="value"]').value;
7778
+ let values = Array.from(row.querySelectorAll('[data-filter-target="value"]'));
7779
+ values = values.filter((element) => {
7780
+ return element.style.display;
7781
+ });
7782
+ values = values.map((element) => {
7783
+ return element.value;
7784
+ });
7785
+ const value = values.join(",");
7780
7786
  string += `${conditional || ""}${operator}:${value}`;
7781
7787
  }
7782
7788
  return string;
@@ -7799,16 +7805,25 @@ var filter_row_controller_default = class extends Controller {
7799
7805
  handleOperatorChange() {
7800
7806
  if (this.operatorTarget.value === "is_null" || this.operatorTarget.value === "is_not_null") {
7801
7807
  this.toggleNullInput();
7808
+ } else if (this.operatorTarget.value === "between" || this.operatorTarget.value === "not_between") {
7809
+ this.toggleSecondaryInput();
7802
7810
  } else {
7803
7811
  this.toggleOriginalInput();
7804
7812
  }
7805
7813
  }
7806
7814
  toggleNullInput() {
7807
7815
  this.hideOriginal();
7816
+ this.hideSecondary();
7808
7817
  this.showNull();
7809
7818
  }
7810
7819
  toggleOriginalInput() {
7811
7820
  this.showOriginal();
7821
+ this.hideSecondary();
7822
+ this.hideNull();
7823
+ }
7824
+ toggleSecondaryInput() {
7825
+ this.showSecondary();
7826
+ this.hideOriginal();
7812
7827
  this.hideNull();
7813
7828
  }
7814
7829
  hideOriginal() {
@@ -7819,6 +7834,22 @@ var filter_row_controller_default = class extends Controller {
7819
7834
  this.originalTarget.style.display = "block";
7820
7835
  this.originalTarget.setAttribute("data-filter-target", "value");
7821
7836
  }
7837
+ hideSecondary() {
7838
+ for (const [index2, value] of this.originalTargets.entries()) {
7839
+ if (index2 !== 0) {
7840
+ value.style.display = "none";
7841
+ value.setAttribute("data-filter-target", "value_original");
7842
+ }
7843
+ }
7844
+ }
7845
+ showSecondary() {
7846
+ for (const [index2, value] of this.originalTargets.entries()) {
7847
+ if (index2 !== 0) {
7848
+ value.style.display = "block";
7849
+ value.setAttribute("data-filter-target", "value");
7850
+ }
7851
+ }
7852
+ }
7822
7853
  hideNull() {
7823
7854
  this.nullTarget.style.display = "none";
7824
7855
  this.nullTarget.setAttribute("data-filter-target", "value_null");
@@ -10093,6 +10124,29 @@ var hello_controller_default = class extends Controller {
10093
10124
  }
10094
10125
  };
10095
10126
 
10127
+ // app/assets/javascripts/headmin/controllers/infinite_scroller_controller.js
10128
+ var infinite_scroller_controller_default = class extends Controller {
10129
+ connect() {
10130
+ this.clickWhenInViewport();
10131
+ document.querySelector(".modal-body").addEventListener("scroll", () => {
10132
+ this.clickWhenInViewport();
10133
+ });
10134
+ }
10135
+ clickWhenInViewport() {
10136
+ if (!this.isLoading() && this.isInViewport()) {
10137
+ this.element.setAttribute("clicked", 1);
10138
+ this.element.click();
10139
+ }
10140
+ }
10141
+ isLoading() {
10142
+ return this.element.hasAttribute("clicked");
10143
+ }
10144
+ isInViewport() {
10145
+ const rect = this.element.getBoundingClientRect();
10146
+ return rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth);
10147
+ }
10148
+ };
10149
+
10096
10150
  // app/assets/javascripts/headmin/controllers/media_controller.js
10097
10151
  var media_controller_default = class extends Controller {
10098
10152
  static get targets() {
@@ -10140,8 +10194,8 @@ var media_controller_default = class extends Controller {
10140
10194
  this.postProcess();
10141
10195
  }
10142
10196
  selectItems(items) {
10143
- this.removeAllItems();
10144
- this.addItems(items);
10197
+ this.removeAllDeselectedItems(items);
10198
+ this.addNewItems(items);
10145
10199
  this.postProcess();
10146
10200
  }
10147
10201
  postProcess() {
@@ -10184,8 +10238,16 @@ var media_controller_default = class extends Controller {
10184
10238
  }
10185
10239
  });
10186
10240
  }
10187
- addItems(items) {
10188
- items.forEach((item) => this.addItem(item));
10241
+ addNewItems(items) {
10242
+ const itemTargetIds = this.itemTargets.map((i) => {
10243
+ return parseInt(i.querySelectorAll("input")[1].value);
10244
+ });
10245
+ items.forEach((item) => {
10246
+ if (itemTargetIds.includes(item.blobId)) {
10247
+ return;
10248
+ }
10249
+ this.addItem(item);
10250
+ });
10189
10251
  }
10190
10252
  addItem(item) {
10191
10253
  const currentItem = this.itemByBlobId(item.blobId);
@@ -10230,11 +10292,18 @@ var media_controller_default = class extends Controller {
10230
10292
  const randomNumber = Math.floor(1e8 + Math.random() * 9e8);
10231
10293
  return template.innerHTML.replace(regex, randomNumber);
10232
10294
  }
10233
- removeAllItems() {
10234
- this.removeItems(this.itemTargets);
10295
+ removeAllDeselectedItems(items) {
10296
+ this.removeDeselectedItems(items, this.itemTargets);
10235
10297
  }
10236
- removeItems(items) {
10298
+ removeDeselectedItems(elements, items) {
10299
+ const returnedBlobIds = elements.map((e) => {
10300
+ return e.blobId;
10301
+ });
10237
10302
  items.forEach((item) => {
10303
+ const blobId = parseInt(item.querySelectorAll("input")[1].value);
10304
+ if (returnedBlobIds.includes(blobId)) {
10305
+ return;
10306
+ }
10238
10307
  this.removeItem(item);
10239
10308
  });
10240
10309
  }
@@ -10267,6 +10336,9 @@ var media_modal_controller_default = class extends Controller {
10267
10336
  static get targets() {
10268
10337
  return ["idCheckbox", "item", "form", "selectButton", "placeholder", "count"];
10269
10338
  }
10339
+ static get values() {
10340
+ return { ids: Array };
10341
+ }
10270
10342
  connect() {
10271
10343
  this.validate();
10272
10344
  this.updateCount();
@@ -10278,34 +10350,64 @@ var media_modal_controller_default = class extends Controller {
10278
10350
  this.hidePlaceholder();
10279
10351
  this.triggerFormSubmission();
10280
10352
  }
10281
- inputChange() {
10282
- this.handleInputChange();
10353
+ inputChange(event) {
10354
+ this.handleIdsUpdate(event.target);
10283
10355
  this.updateCount();
10284
10356
  }
10285
10357
  hidePlaceholder() {
10286
10358
  this.placeholderTarget.classList.add("d-none");
10287
10359
  }
10288
- handleInputChange() {
10360
+ handleIdsUpdate(element) {
10361
+ if (element.checked) {
10362
+ const arr = this.idsValue;
10363
+ arr.push(element.value);
10364
+ this.idsValue = arr;
10365
+ } else {
10366
+ this.idsValue = this.idsValue.filter((value) => {
10367
+ return element.value !== value;
10368
+ });
10369
+ }
10370
+ }
10371
+ itemTargetConnected(element) {
10372
+ this.updateItem(element.querySelector("input"));
10373
+ }
10374
+ updateItem(element) {
10375
+ const arr = this.idsValue;
10376
+ if (arr.includes(element.value)) {
10377
+ element.checked = true;
10378
+ } else {
10379
+ element.checked = false;
10380
+ }
10381
+ }
10382
+ idsValueChanged() {
10383
+ for (const item of this.itemTargets) {
10384
+ this.updateItem(item.querySelector("input"));
10385
+ }
10289
10386
  this.validate();
10290
10387
  }
10291
10388
  dispatchSelectionEvent() {
10292
10389
  document.dispatchEvent(new CustomEvent("mediaSelectionSubmitted", {
10293
10390
  detail: {
10294
10391
  name: this.element.dataset.name,
10295
- items: this.renderItemsForEvent(this.selectedItems())
10392
+ items: this.renderItemsForEvent()
10296
10393
  }
10297
10394
  }));
10298
10395
  }
10299
10396
  triggerFormSubmission() {
10300
10397
  this.formTarget.requestSubmit();
10301
10398
  }
10302
- renderItemsForEvent(items) {
10303
- return items.map((item) => this.renderItemForEvent(item));
10399
+ renderItemsForEvent() {
10400
+ return this.idsValue.map((item) => this.renderItemForEvent(item)).filter((i) => {
10401
+ return i !== void 0;
10402
+ });
10304
10403
  }
10305
10404
  renderItemForEvent(item) {
10405
+ const id = parseInt(item);
10406
+ const blobId = `#blob_${id}`;
10407
+ const element = this.element.querySelector(blobId);
10306
10408
  return {
10307
- blobId: item.querySelector('input[type="checkbox"]').value,
10308
- thumbnail: item.querySelector(".h-thumbnail")
10409
+ blobId: id,
10410
+ thumbnail: element ? element.querySelector(".h-thumbnail") : ""
10309
10411
  };
10310
10412
  }
10311
10413
  selectedItems() {
@@ -10315,7 +10417,7 @@ var media_modal_controller_default = class extends Controller {
10315
10417
  });
10316
10418
  }
10317
10419
  selectedItemsCount() {
10318
- return this.selectedItems().length;
10420
+ return this.idsValue.length;
10319
10421
  }
10320
10422
  minSelectedItems() {
10321
10423
  return parseInt(this.element.dataset.min, 10) || 0;
@@ -10341,7 +10443,7 @@ var media_modal_controller_default = class extends Controller {
10341
10443
  return count >= this.minSelectedItems() && count <= this.maxSelectedItems();
10342
10444
  }
10343
10445
  updateCount() {
10344
- this.countTarget.innerHTML = this.idCheckboxTargets.filter((checkbox) => checkbox.checked).length;
10446
+ this.countTarget.innerHTML = this.selectedItemsCount();
10345
10447
  }
10346
10448
  };
10347
10449
 
@@ -15901,6 +16003,7 @@ var Headmin = class {
15901
16003
  Stimulus.register("filters", filters_controller_default);
15902
16004
  Stimulus.register("flatpickr", flatpickr_controller_default);
15903
16005
  Stimulus.register("hello", hello_controller_default);
16006
+ Stimulus.register("infinite-scroller", infinite_scroller_controller_default);
15904
16007
  Stimulus.register("media", media_controller_default);
15905
16008
  Stimulus.register("media-modal", media_modal_controller_default);
15906
16009
  Stimulus.register("notification", notification_controller_default);
@@ -1,4 +1,5 @@
1
1
  class Headmin::MediaController < HeadminController
2
+ include Headmin::Pagination
2
3
  layout false
3
4
 
4
5
  def index
@@ -9,7 +10,13 @@ class Headmin::MediaController < HeadminController
9
10
  .order(created_at: :desc)
10
11
  .group(:id)
11
12
  .all
13
+ @blobs = paginate(@blobs)
12
14
  @mimetypes = media_params[:mimetype]
15
+
16
+ respond_to do |format|
17
+ format.html
18
+ format.turbo_stream
19
+ end
13
20
  end
14
21
 
15
22
  def create
@@ -38,6 +45,10 @@ class Headmin::MediaController < HeadminController
38
45
  end
39
46
  end
40
47
 
48
+ def thumbnail
49
+ @blob = ActiveStorage::Blob.find(params[:id])
50
+ end
51
+
41
52
  private
42
53
 
43
54
  def media_params
@@ -1,16 +1,5 @@
1
1
  module Headmin
2
2
  module FormHelper
3
- # TODO: cleanup after input field refactoring
4
- def form_field_validation_id(form, name)
5
- [form.object_name, name.to_s, "validation"].join("_").parameterize.underscore
6
- end
7
-
8
- # TODO: cleanup after input field refactoring
9
- def form_field_validation_class(form, name)
10
- return nil if request.get?
11
- form.object.errors.has_key?(name) ? "is-invalid" : "is-valid"
12
- end
13
-
14
3
  # Outputs currently present query parameters as hidden fields for a given form
15
4
  #
16
5
  # https://example.com/products?amount=1&type[]=food&type[]=beverage
@@ -69,18 +69,6 @@ module Headmin
69
69
  def has_many?
70
70
  macro == :has_many
71
71
  end
72
-
73
- private
74
-
75
- def is_i?(value)
76
- # Regex: this selects signed digits (\d) only, it is then checked to the value, e.g.:
77
- # is_i?("3") = true
78
- # is_i?("-3") = true
79
- # is_i?("3a") = false
80
- # is_i?("3.2") = false
81
-
82
- /\A[-+]?\d+\z/.match(value)
83
- end
84
72
  end
85
73
  end
86
74
  end
@@ -0,0 +1,50 @@
1
+ module Headmin
2
+ module Filter
3
+ class AssociationCount < Headmin::Filter::Base
4
+ OPERATORS = %w[eq not_eq gt gteq lt lteq]
5
+
6
+ def cast_value(value)
7
+ is_i?(value) ? value.to_i : 0
8
+ end
9
+
10
+ def query(collection)
11
+ return collection unless @instructions.any?
12
+
13
+ # Store the collections' class for later use
14
+ @parent_class = collection.is_a?(Class) ? collection : collection.klass
15
+
16
+ # Join table and group on primary key if necessary
17
+ collection = collection.left_joins(reflection.name).group(primary_key)
18
+
19
+ # Build query and execute
20
+ query = nil
21
+ @instructions.each do |instruction|
22
+ query = build_query(query, collection, instruction)
23
+ end
24
+ collection.having(query)
25
+ end
26
+
27
+ private
28
+
29
+ def build_query(query, collection, instruction)
30
+ query_operator = convert_to_query_operator(instruction[:operator])
31
+ query_value = convert_to_query_value(instruction[:value], instruction[:operator])
32
+
33
+ query_operator, query_value = process_null_operators(query_operator, query_value)
34
+ new_query = reflection.klass.arel_table[reflection.foreign_key.to_sym].count.send(query_operator, query_value)
35
+ query ? query.send(instruction[:conditional], new_query) : new_query
36
+ end
37
+
38
+ def reflection
39
+ reflection = @parent_class.reflect_on_association(attribute.to_s.split("_")[0].to_sym)
40
+ raise UnknownAssociation if reflection.nil?
41
+
42
+ reflection
43
+ end
44
+
45
+ def primary_key
46
+ "#{@parent_class.table_name}.#{@parent_class.primary_key}"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,78 @@
1
+ module Headmin
2
+ module Filter
3
+ class AssociationCountView < FilterView
4
+ def base_options
5
+ keys = %i[name label form]
6
+ options = to_h.slice(*keys)
7
+ default_base_options.merge(options)
8
+ end
9
+
10
+ def input_options
11
+ keys = %i[form]
12
+ options = to_h.slice(*keys)
13
+ default_input_options.merge(options)
14
+ end
15
+
16
+ def collection
17
+ @collection || association_model.all.map { |record| [record.to_s, record.id] }
18
+ end
19
+
20
+ def association_model
21
+ reflection.klass
22
+ end
23
+
24
+ private
25
+
26
+ def id
27
+ "#{name}_value"
28
+ end
29
+
30
+ def name
31
+ @name || attribute
32
+ end
33
+
34
+ def attribute
35
+ "#{@association}_count"
36
+ end
37
+
38
+ def label
39
+ @label || I18n.t("attributes.#{attribute}", default: "#{I18n.t("attributes.count")} #{association_model.model_name.human(count: collection? ? 2 : 1)}")
40
+ end
41
+
42
+ def reflection
43
+ form.object.class.reflect_on_association(@association)
44
+ end
45
+
46
+ def collection?
47
+ reflection.collection?
48
+ end
49
+
50
+ def default_base_options
51
+ {
52
+ label: label,
53
+ name: attribute,
54
+ display_values: collection,
55
+ filter: Headmin::Filter::AssociationCount.new(name, @params),
56
+ allowed_operators: Headmin::Filter::AssociationCount::OPERATORS
57
+ }
58
+ end
59
+
60
+ def default_input_options
61
+ {
62
+ label: false,
63
+ wrapper: false,
64
+ name: nil,
65
+ id: id,
66
+ data: {
67
+ action: "change->filter#updateHiddenValue",
68
+ filter_target: "value",
69
+ filter_row_target: "original"
70
+ },
71
+ collection: collection,
72
+ selected: selected,
73
+ class: "form-control"
74
+ }
75
+ end
76
+ end
77
+ end
78
+ end
@@ -100,6 +100,16 @@ module Headmin
100
100
  query ? query.send(instruction[:conditional], new_query) : new_query
101
101
  end
102
102
 
103
+ def is_i?(value)
104
+ # Regex: this selects signed digits (\d) only, it is then checked to the value, e.g.:
105
+ # is_i?("3") = true
106
+ # is_i?("-3") = true
107
+ # is_i?("3a") = false
108
+ # is_i?("3.2") = false
109
+
110
+ /\A[-+]?\d+\z/.match(value)
111
+ end
112
+
103
113
  private
104
114
 
105
115
  def parse(string)
@@ -56,8 +56,15 @@ module Headmin
56
56
  def display_value(value)
57
57
  # This uses the default date format of headmin.
58
58
  # Can be overwritten by setting default date format of the application.
59
- if value.class.to_s == "Range"
59
+
60
+ current_operator = instructions.find { |instruction| instruction[:value] == value }[:operator]
61
+
62
+ # To make the operators eq and not_eq work, we pass a range.
63
+ # However, display value should return this as a date and not a range.
64
+ if value.class.to_s == "Range" && (current_operator == "eq" || current_operator == "not_eq")
60
65
  I18n.l(value.last.to_date)
66
+ elsif values.class.to_s
67
+ "#{I18n.l(value.first.to_date)} - #{I18n.l(value.last.to_date)}"
61
68
  else
62
69
  I18n.l(value.to_date)
63
70
  end
@@ -28,7 +28,7 @@ module Headmin
28
28
  label: label,
29
29
  name: attribute,
30
30
  filter: Headmin::Filter::Date.new(name, @params),
31
- allowed_operators: Headmin::Filter::Date::OPERATORS - %w[in not_in between not_between]
31
+ allowed_operators: Headmin::Filter::Date::OPERATORS - %w[in not_in]
32
32
  }
33
33
  end
34
34
 
@@ -10,18 +10,6 @@ module Headmin
10
10
  def to_s
11
11
  string
12
12
  end
13
-
14
- private
15
-
16
- def is_i?(value)
17
- # Regex: this selects signed digits (\d) only, it is then checked to the value, e.g.:
18
- # is_i?("3") = true
19
- # is_i?("-3") = true
20
- # is_i?("3a") = false
21
- # is_i?("3.2") = false
22
-
23
- /\A[-+]?\d+\z/.match(value)
24
- end
25
13
  end
26
14
  end
27
15
  end
@@ -24,7 +24,9 @@ module Headmin
24
24
  is_null: "&#9675; #{I18n.t("headmin.filters.operators.is_null")}",
25
25
  is_not_null: "&#9679; #{I18n.t("headmin.filters.operators.is_not_null")}",
26
26
  in: "&ni; #{I18n.t("headmin.filters.operators.in")}",
27
- not_in: "&notni; #{I18n.t("headmin.filters.operators.not_in")}"
27
+ not_in: "&notni; #{I18n.t("headmin.filters.operators.not_in")}",
28
+ between: "&harr; #{I18n.t("headmin.filters.operators.between")}",
29
+ not_between: "&harrcir; #{I18n.t("headmin.filters.operators.not_between")}"
28
30
  }
29
31
  end
30
32
  end
@@ -23,7 +23,7 @@
23
23
 
24
24
  <!-- Default parameters (e.g. sorting, pagination) -->
25
25
  <% default_params.except("page").each do |name, value| %>
26
- <%= form.hidden_field name.to_sym, value: value %>
26
+ <%= hidden_field_tag(name, value) %>
27
27
  <% end %>
28
28
 
29
29
  <div class="d-flex flex-column flex-md-row align-content-start align-items-md-start">
@@ -0,0 +1,28 @@
1
+ <%
2
+ # headmin/filters/association_count
3
+ #
4
+ # ==== Required parameters
5
+ # * +association+ - Name of the association that has to be counted
6
+ # * +form+ - Form object
7
+ #
8
+ # ==== Optional parameters
9
+ # * +label+ - Display label
10
+ # * +name+ - Name of the filter parameter
11
+ #
12
+ # ==== Examples
13
+ # Basic version (one-to-many)
14
+ # <%= render "headmin/filters", url: admin_orders_path do |form| %#>
15
+ # <%= render "headmin/filters/association_count", form: form, association: :beverages %#>
16
+ # <% end %#>
17
+ #
18
+ # Basic version (one-to-one)
19
+ # <%= render "headmin/filters", url: admin_orders_path do |form| %#>
20
+ # <%= render "headmin/filters/association_count", form: form, association: :beverage %#>
21
+ # <% end %#>
22
+
23
+ number = Headmin::Filter::AssociationCountView.new(local_assigns.merge(params: params))
24
+ %>
25
+
26
+ <%= render "headmin/filters/base", number.base_options do |value| %>
27
+ <%= render "headmin/forms/number", number.input_options.merge({value: value}) %>
28
+ <% end %>
@@ -20,4 +20,5 @@
20
20
 
21
21
  <%= render "headmin/filters/base", date.base_options do |value| %>
22
22
  <%= render "headmin/forms/date", date.input_options.merge({value: value}) %>
23
+ <%= render "headmin/forms/date_range", date.input_options.merge({start: {value: value.class.to_s == "Range" ? value.first : value}, end: {value: value.class.to_s == "Range" ? value.last : value}}) %>
23
24
  <% end %>
@@ -1,4 +1,4 @@
1
- <div class="media-modal modal fade" tabindex="-1" data-controller="remote-modal media-modal" data-name="<%= name %>" data-min="<%= min %>" data-max="<%= max %>">
1
+ <div class="media-modal modal fade" tabindex="-1" data-controller="remote-modal media-modal" data-media-modal-ids-value="<%= params[:ids] ? params[:ids] : [] %>" data-name="<%= name %>" data-min="<%= min %>" data-max="<%= max %>">
2
2
  <div class="modal-dialog modal-lg modal-dialog-scrollable">
3
3
  <div class="modal-content">
4
4
  <div class="modal-header">
@@ -10,15 +10,20 @@
10
10
  <div class="modal-body">
11
11
  <%= turbo_frame_tag "thumbnails", class: "d-flex flex-wrap gap-2" do %>
12
12
  <% @blobs.each do |blob| %>
13
- <%= render "headmin/media/item", blob: blob %>
13
+ <%= turbo_frame_tag blob, src: headmin_media_item_thumbnail_path(blob), loading: "lazy" do %>
14
+ <%= render "thumbnail" %>
15
+ <% end %>
14
16
  <% end %>
15
17
  <div data-media-modal-target="placeholder" class="<%= "d-none" if !@blobs.empty? %>">
16
18
  <p><%= t(".placeholder") %></p>
17
19
  </div>
18
20
  <% end %>
21
+ <div class="mt-3">
22
+ <%= render "headmin/pagination/infinite", items: @blobs %>
23
+ </div>
19
24
  </div>
20
25
  <div class="modal-footer">
21
- <%= form_with url: headmin_media_path, multipart: true, data: {"media-modal-target": "form"}, class: "me-auto" do |form| %>
26
+ <%= form_with url: headmin_new_media_path, multipart: true, data: {"media-modal-target": "form"}, class: "me-auto" do |form| %>
22
27
  <%= form.label :files, class: "btn h-btn-outline-light" do %>
23
28
  <%= bootstrap_icon("upload") %>
24
29
  <%= t(".upload") %>
@@ -0,0 +1,20 @@
1
+ <% blob = local_assigns.has_key?(:blob) ? local_assigns[:blob] : nil %>
2
+ <% if blob.present? %>
3
+ <div data-media-modal-target="item" title="<%= "#{blob.filename} (#{l(blob.created_at, format: :long)})" %>">
4
+ <!-- Input -->
5
+ <input
6
+ id="media-item-<%= blob.id %>"
7
+ type="checkbox"
8
+ value="<%= blob.id %>"
9
+ data-action="change->media-modal#inputChange"
10
+ data-media-modal-target="idCheckbox"
11
+ hidden>
12
+
13
+ <!-- Label -->
14
+ <label for="media-item-<%= blob.id %>">
15
+ <%= render "headmin/thumbnail", file: blob %>
16
+ </label>
17
+ </div>
18
+ <% else %>
19
+ <%= render "headmin/thumbnail", file: nil %>
20
+ <% end %>
@@ -1,5 +1,7 @@
1
1
  <%= turbo_stream.prepend "thumbnails" do %>
2
2
  <% @blobs.each do |blob| %>
3
- <%= render "headmin/media/item", blob: blob %>
3
+ <%= turbo_frame_tag blob, src: headmin_media_item_thumbnail_path(blob), loading: "lazy" do %>
4
+ <%= render "thumbnail" %>
5
+ <% end %>
4
6
  <% end %>
5
7
  <% end %>
@@ -0,0 +1,11 @@
1
+ <%= turbo_stream.append "thumbnails" do %>
2
+ <% @blobs.each do |blob| %>
3
+ <%= turbo_frame_tag blob, src: headmin_media_item_thumbnail_path(blob), loading: "lazy" do %>
4
+ <%= render 'thumbnail' %>
5
+ <% end %>
6
+ <% end %>
7
+ <% end %>
8
+
9
+ <%= turbo_stream.replace "infinite" do %>
10
+ <%= render "headmin/pagination/infinite", items: @blobs %>
11
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <%= turbo_frame_tag @blob do %>
2
+ <%= render "thumbnail", blob: @blob %>
3
+ <% end %>
@@ -0,0 +1,7 @@
1
+ <% auto_load = local_assigns.has_key?(:auto_load) ? local_assigns[:auto_load] : true %>
2
+
3
+ <% unless items.page(params[:page]).last_page? %>
4
+ <div id="infinite">
5
+ <%= link_to t(".load_more"), path_to_next_page(items), class: "btn btn-primary", data: {turbo_stream: true, controller: auto_load ? "infinite-scroller" : ""} %>
6
+ </div>
7
+ <% end %>
@@ -33,6 +33,8 @@ en:
33
33
  is_null: is null
34
34
  in: includes
35
35
  not_in: does not include
36
+ between: between
37
+ not_between: not between
36
38
  values:
37
39
  "false": "False"
38
40
  "no": "No"
@@ -33,6 +33,8 @@ nl:
33
33
  is_null: is niet ingevuld
34
34
  in: bevat
35
35
  not_in: bevat niet
36
+ between: tussen
37
+ not_between: niet tussen
36
38
  values:
37
39
  "false": Onwaar
38
40
  "no": Nee
@@ -1,6 +1,8 @@
1
1
  en:
2
2
  headmin:
3
3
  pagination:
4
+ infinite:
5
+ load_more: Load more
4
6
  per_page:
5
7
  title: items per page
6
8
  items:
@@ -1,6 +1,8 @@
1
1
  nl:
2
2
  headmin:
3
3
  pagination:
4
+ infinite:
5
+ load_more: Meer laden
4
6
  per_page:
5
7
  title: items per pagina
6
8
  items:
data/config/routes.rb CHANGED
@@ -3,8 +3,9 @@
3
3
  Rails.application.routes.draw do
4
4
  namespace(:headmin) do
5
5
  get "media", to: "media#index", as: :media
6
- post "media", to: "media#create"
6
+ post "media", to: "media#create", as: :new_media
7
7
  get "media/:id", to: "media#show", as: :media_item
8
8
  patch "media/:id", to: "media#update"
9
+ get "media/thumbnail/:id", to: "media#thumbnail", as: :media_item_thumbnail
9
10
  end
10
11
  end
@@ -1,3 +1,3 @@
1
1
  module Headmin
2
- VERSION = "0.6.1"
2
+ VERSION = "0.6.3"
3
3
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "headmin",
3
- "version": "0.6.0",
3
+ "version": "0.6.3",
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.6.1
4
+ version: 0.6.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jef Vlamings
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-01-23 00:00:00.000000000 Z
11
+ date: 2024-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: closure_tree
@@ -105,6 +105,7 @@ files:
105
105
  - app/assets/javascripts/headmin/controllers/filters_controller.js
106
106
  - app/assets/javascripts/headmin/controllers/flatpickr_controller.js
107
107
  - app/assets/javascripts/headmin/controllers/hello_controller.js
108
+ - app/assets/javascripts/headmin/controllers/infinite_scroller_controller.js
108
109
  - app/assets/javascripts/headmin/controllers/media_controller.js
109
110
  - app/assets/javascripts/headmin/controllers/media_modal_controller.js
110
111
  - app/assets/javascripts/headmin/controllers/notification_controller.js
@@ -261,6 +262,8 @@ files:
261
262
  - app/models/headmin/.DS_Store
262
263
  - app/models/headmin/blocks_view.rb
263
264
  - app/models/headmin/filter/association.rb
265
+ - app/models/headmin/filter/association_count.rb
266
+ - app/models/headmin/filter/association_count_view.rb
264
267
  - app/models/headmin/filter/association_view.rb
265
268
  - app/models/headmin/filter/base.rb
266
269
  - app/models/headmin/filter/base_view.rb
@@ -338,6 +341,7 @@ files:
338
341
  - app/views/headmin/dropdown/_list.html.erb
339
342
  - app/views/headmin/dropdown/_locale.html.erb
340
343
  - app/views/headmin/filters/_association.html.erb
344
+ - app/views/headmin/filters/_association_count.html.erb
341
345
  - app/views/headmin/filters/_base.html.erb
342
346
  - app/views/headmin/filters/_boolean.html.erb
343
347
  - app/views/headmin/filters/_date.html.erb
@@ -408,17 +412,20 @@ files:
408
412
  - app/views/headmin/layout/_sidebar.html.erb
409
413
  - app/views/headmin/layout/sidebar/_bottom.html.erb
410
414
  - app/views/headmin/layout/sidebar/_nav.html.erb
411
- - app/views/headmin/media/_item.html.erb
412
415
  - app/views/headmin/media/_media_item_modal.html.erb
413
416
  - app/views/headmin/media/_modal.html.erb
417
+ - app/views/headmin/media/_thumbnail.html.erb
414
418
  - app/views/headmin/media/create.turbo_stream.erb
415
419
  - app/views/headmin/media/index.html.erb
420
+ - app/views/headmin/media/index.turbo_stream.erb
416
421
  - app/views/headmin/media/show.html.erb
422
+ - app/views/headmin/media/thumbnail.html.erb
417
423
  - app/views/headmin/media/update.turbo_stream.erb
418
424
  - app/views/headmin/nav/_dropdown.html.erb
419
425
  - app/views/headmin/nav/_item.html.erb
420
426
  - app/views/headmin/nav/item/_devise.html.erb
421
427
  - app/views/headmin/nav/item/_locale.html.erb
428
+ - app/views/headmin/pagination/_infinite.html.erb
422
429
  - app/views/headmin/pagination/_per_page.html.erb
423
430
  - app/views/headmin/pagination/kaminari/_first_page.html.erb
424
431
  - app/views/headmin/pagination/kaminari/_gap.html.erb
@@ -566,7 +573,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
566
573
  - !ruby/object:Gem::Version
567
574
  version: '0'
568
575
  requirements: []
569
- rubygems_version: 3.3.3
576
+ rubygems_version: 3.5.6
570
577
  signing_key:
571
578
  specification_version: 4
572
579
  summary: Admin component library
@@ -1,16 +0,0 @@
1
- <div data-media-modal-target="item" title="<%= "#{blob.filename} (#{l(blob.created_at, format: :long)})" %>">
2
- <!-- Input -->
3
- <input
4
- id="media-item-<%= blob.id %>"
5
- type="checkbox"
6
- value="<%= blob.id %>"
7
- <%= "checked" if params[:ids]&.include?(blob.id.to_s) %>
8
- data-action="change->media-modal#inputChange"
9
- data-media-modal-target="idCheckbox"
10
- hidden>
11
-
12
- <!-- Label -->
13
- <label for="media-item-<%= blob.id %>">
14
- <%= render "headmin/thumbnail", file: blob %>
15
- </label>
16
- </div>