headmin 0.6.0 → 0.6.2

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -0
  3. data/Gemfile.lock +92 -90
  4. data/README.md +2 -2
  5. data/app/assets/javascripts/headmin/controllers/filter_controller.js +15 -3
  6. data/app/assets/javascripts/headmin/controllers/filter_row_controller.js +75 -47
  7. data/app/assets/javascripts/headmin/controllers/infinite_scroller_controller.js +30 -0
  8. data/app/assets/javascripts/headmin/controllers/media_controller.js +24 -8
  9. data/app/assets/javascripts/headmin/controllers/media_modal_controller.js +142 -105
  10. data/app/assets/javascripts/headmin/index.js +2 -0
  11. data/app/assets/javascripts/headmin.js +122 -19
  12. data/app/assets/stylesheets/headmin.css +1 -1
  13. data/app/controllers/headmin/media_controller.rb +11 -0
  14. data/app/helpers/headmin/form_helper.rb +0 -11
  15. data/app/models/concerns/headmin/attachment.rb +34 -0
  16. data/app/models/headmin/filter/association.rb +0 -12
  17. data/app/models/headmin/filter/association_count.rb +50 -0
  18. data/app/models/headmin/filter/association_count_view.rb +78 -0
  19. data/app/models/headmin/filter/base.rb +10 -0
  20. data/app/models/headmin/filter/date.rb +8 -1
  21. data/app/models/headmin/filter/date_view.rb +1 -1
  22. data/app/models/headmin/filter/number.rb +0 -12
  23. data/app/models/headmin/filter/operator_view.rb +3 -1
  24. data/app/models/headmin/form/media_view.rb +6 -2
  25. data/app/views/headmin/_filters.html.erb +3 -8
  26. data/app/views/headmin/_form.html.erb +1 -1
  27. data/app/views/headmin/_index.html.erb +1 -1
  28. data/app/views/headmin/filters/_association_count.html.erb +28 -0
  29. data/app/views/headmin/filters/_date.html.erb +1 -0
  30. data/app/views/headmin/forms/fields/_base.html.erb +1 -1
  31. data/app/views/headmin/layout/_content.html.erb +1 -1
  32. data/app/views/headmin/media/_media_item_modal.html.erb +26 -0
  33. data/app/views/headmin/media/_modal.html.erb +8 -3
  34. data/app/views/headmin/media/_thumbnail.html.erb +20 -0
  35. data/app/views/headmin/media/create.turbo_stream.erb +1 -1
  36. data/app/views/headmin/media/index.turbo_stream.erb +11 -0
  37. data/app/views/headmin/media/thumbnail.html.erb +3 -0
  38. data/app/views/headmin/nav/_item.html.erb +6 -1
  39. data/app/views/headmin/pagination/_infinite.html.erb +7 -0
  40. data/config/initializers/extend_active_storage_attachment.rb +3 -0
  41. data/config/locales/headmin/filters/en.yml +2 -0
  42. data/config/locales/headmin/filters/nl.yml +2 -0
  43. data/config/locales/headmin/media/en.yml +1 -0
  44. data/config/locales/headmin/media/nl.yml +1 -0
  45. data/config/locales/headmin/pagination/en.yml +2 -0
  46. data/config/locales/headmin/pagination/nl.yml +2 -0
  47. data/config/routes.rb +2 -1
  48. data/lib/headmin/version.rb +1 -1
  49. data/package.json +1 -1
  50. metadata +12 -3
  51. data/app/views/headmin/media/_item.html.erb +0 -16
@@ -1,110 +1,147 @@
1
1
  /* global CustomEvent */
2
- import { Controller } from '@hotwired/stimulus'
2
+ import {Controller} from '@hotwired/stimulus'
3
3
 
4
4
  export default class extends Controller {
5
- static get targets () {
6
- return ['idCheckbox', 'item', 'form', 'selectButton', 'placeholder', 'count']
7
- }
8
-
9
- connect () {
10
- this.validate()
11
- this.updateCount()
12
- }
13
-
14
- // Actions
15
- select () {
16
- this.dispatchSelectionEvent()
17
- }
18
-
19
- submitForm () {
20
- this.hidePlaceholder()
21
- this.triggerFormSubmission()
22
- }
23
-
24
- inputChange () {
25
- this.handleInputChange()
26
- this.updateCount()
27
- }
28
-
29
- // Methods
30
- hidePlaceholder () {
31
- this.placeholderTarget.classList.add('d-none')
32
- }
33
-
34
- handleInputChange () {
35
- this.validate()
36
- }
37
-
38
- dispatchSelectionEvent () {
39
- document.dispatchEvent(
40
- new CustomEvent(
41
- 'mediaSelectionSubmitted',
42
- {
43
- detail: {
44
- name: this.element.dataset.name,
45
- items: this.renderItemsForEvent(this.selectedItems())
46
- }
5
+ static get targets() {
6
+ return ['idCheckbox', 'item', 'form', 'selectButton', 'placeholder', 'count']
7
+ }
8
+
9
+ static get values () {
10
+ return { ids: Array }
11
+ }
12
+
13
+ connect() {
14
+ this.validate()
15
+ this.updateCount()
16
+ }
17
+
18
+ // Actions
19
+ select() {
20
+ this.dispatchSelectionEvent()
21
+ }
22
+
23
+ submitForm() {
24
+ this.hidePlaceholder()
25
+ this.triggerFormSubmission()
26
+ }
27
+
28
+ inputChange(event) {
29
+ this.handleIdsUpdate(event.target)
30
+ this.updateCount()
31
+ }
32
+
33
+ // Methods
34
+ hidePlaceholder() {
35
+ this.placeholderTarget.classList.add('d-none')
36
+ }
37
+
38
+ handleIdsUpdate(element) {
39
+ if (element.checked) {
40
+ let 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
47
  }
48
- )
49
- )
50
- }
51
-
52
- triggerFormSubmission () {
53
- this.formTarget.requestSubmit()
54
- }
55
-
56
- renderItemsForEvent (items) {
57
- return items.map((item) => this.renderItemForEvent(item))
58
- }
59
-
60
- renderItemForEvent (item) {
61
- return {
62
- blobId: item.querySelector('input[type="checkbox"]').value,
63
- thumbnail: item.querySelector('.h-thumbnail')
64
- }
65
- }
66
-
67
- selectedItems () {
68
- return this.itemTargets.filter((item) => {
69
- const checkbox = item.querySelector('input[type="checkbox"]')
70
- return checkbox.checked
71
- })
72
- }
73
-
74
- selectedItemsCount () {
75
- return this.selectedItems().length
76
- }
77
-
78
- minSelectedItems () {
79
- return parseInt(this.element.dataset.min, 10) || 0
80
- }
81
-
82
- maxSelectedItems () {
83
- return parseInt(this.element.dataset.max, 10) || Infinity
84
- }
85
-
86
- validate () {
87
- if (this.isValid()) {
88
- this.enableSelectButton()
89
- } else {
90
- this.disableSelectButton()
91
- }
92
- }
93
-
94
- enableSelectButton () {
95
- this.selectButtonTarget.removeAttribute('disabled')
96
- }
97
-
98
- disableSelectButton () {
99
- this.selectButtonTarget.setAttribute('disabled', '')
100
- }
101
-
102
- isValid () {
103
- const count = this.selectedItemsCount()
104
- return count >= this.minSelectedItems() && count <= this.maxSelectedItems()
105
- }
106
-
107
- updateCount () {
108
- this.countTarget.innerHTML = this.idCheckboxTargets.filter(checkbox => checkbox.checked).length
109
- }
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
+ }
68
+ this.validate()
69
+ }
70
+
71
+ dispatchSelectionEvent() {
72
+ document.dispatchEvent(
73
+ new CustomEvent(
74
+ 'mediaSelectionSubmitted',
75
+ {
76
+ detail: {
77
+ name: this.element.dataset.name,
78
+ items: this.renderItemsForEvent()
79
+ }
80
+ }
81
+ )
82
+ )
83
+ }
84
+
85
+ triggerFormSubmission() {
86
+ this.formTarget.requestSubmit()
87
+ }
88
+
89
+ renderItemsForEvent() {
90
+ return this.idsValue.map((item) => this.renderItemForEvent(item)).filter((i) => { return i !== undefined })
91
+ }
92
+
93
+ renderItemForEvent(item) {
94
+ const id = parseInt(item)
95
+ const blob_id = `#blob_${id}`
96
+ const element = this.element.querySelector(blob_id)
97
+
98
+ return {
99
+ blobId: id,
100
+ thumbnail: element ? element.querySelector('.h-thumbnail') : ""
101
+ }
102
+ }
103
+
104
+ selectedItems() {
105
+ return this.itemTargets.filter((item) => {
106
+ const checkbox = item.querySelector('input[type="checkbox"]')
107
+ return checkbox.checked
108
+ })
109
+ }
110
+
111
+ selectedItemsCount() {
112
+ return this.idsValue.length
113
+ }
114
+
115
+ minSelectedItems() {
116
+ return parseInt(this.element.dataset.min, 10) || 0
117
+ }
118
+
119
+ maxSelectedItems() {
120
+ return parseInt(this.element.dataset.max, 10) || Infinity
121
+ }
122
+
123
+ validate() {
124
+ if (this.isValid()) {
125
+ this.enableSelectButton()
126
+ } else {
127
+ this.disableSelectButton()
128
+ }
129
+ }
130
+
131
+ enableSelectButton() {
132
+ this.selectButtonTarget.removeAttribute('disabled')
133
+ }
134
+
135
+ disableSelectButton() {
136
+ this.selectButtonTarget.setAttribute('disabled', '')
137
+ }
138
+
139
+ isValid() {
140
+ const count = this.selectedItemsCount()
141
+ return count >= this.minSelectedItems() && count <= this.maxSelectedItems()
142
+ }
143
+
144
+ updateCount() {
145
+ this.countTarget.innerHTML = this.selectedItemsCount()
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
+ let 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 blob_id = `#blob_${id}`;
10407
+ const element = this.element.querySelector(blob_id);
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,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:/opt/homebrew/var/www/headmin/src/scss/headmin.scss */
4
+ /* sass-plugin-0:/usr/local/var/www/headmin/src/scss/headmin.scss */
5
5
  :root {
6
6
  --bs-blue: #0d6efd;
7
7
  --bs-indigo: #6610f2;
@@ -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
@@ -0,0 +1,34 @@
1
+ module Headmin
2
+ module Attachment
3
+ extend ActiveSupport::Concern
4
+ included do
5
+ scope :not_a_variant, -> { where.not(record_type: "ActiveStorage::VariantRecord") }
6
+
7
+ def record_hierarchy
8
+ hierarchy = []
9
+
10
+ current = record
11
+
12
+ current, partial_hierarchy = process_fieldable(current)
13
+ hierarchy << partial_hierarchy if partial_hierarchy.present?
14
+
15
+ current, partial_hierarchy = process_blockable(current)
16
+ hierarchy << partial_hierarchy if partial_hierarchy.present?
17
+
18
+ hierarchy << current if current.present?
19
+
20
+ hierarchy.flatten
21
+ end
22
+
23
+ private
24
+
25
+ def process_blockable(field)
26
+ field.respond_to?(:blockable) ? [field.blockable, field] : [field, nil]
27
+ end
28
+
29
+ def process_fieldable(field)
30
+ field.respond_to?(:fieldable) ? [field.root.fieldable, field.self_and_ancestors] : [field, nil]
31
+ end
32
+ end
33
+ end
34
+ end
@@ -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