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.
- checksums.yaml +4 -4
- data/.gitignore +6 -0
- data/Gemfile.lock +92 -90
- data/README.md +2 -2
- data/app/assets/javascripts/headmin/controllers/filter_controller.js +15 -3
- data/app/assets/javascripts/headmin/controllers/filter_row_controller.js +75 -47
- data/app/assets/javascripts/headmin/controllers/infinite_scroller_controller.js +30 -0
- data/app/assets/javascripts/headmin/controllers/media_controller.js +24 -8
- data/app/assets/javascripts/headmin/controllers/media_modal_controller.js +142 -105
- data/app/assets/javascripts/headmin/index.js +2 -0
- data/app/assets/javascripts/headmin.js +122 -19
- data/app/assets/stylesheets/headmin.css +1 -1
- data/app/controllers/headmin/media_controller.rb +11 -0
- data/app/helpers/headmin/form_helper.rb +0 -11
- data/app/models/concerns/headmin/attachment.rb +34 -0
- data/app/models/headmin/filter/association.rb +0 -12
- data/app/models/headmin/filter/association_count.rb +50 -0
- data/app/models/headmin/filter/association_count_view.rb +78 -0
- data/app/models/headmin/filter/base.rb +10 -0
- data/app/models/headmin/filter/date.rb +8 -1
- data/app/models/headmin/filter/date_view.rb +1 -1
- data/app/models/headmin/filter/number.rb +0 -12
- data/app/models/headmin/filter/operator_view.rb +3 -1
- data/app/models/headmin/form/media_view.rb +6 -2
- data/app/views/headmin/_filters.html.erb +3 -8
- data/app/views/headmin/_form.html.erb +1 -1
- data/app/views/headmin/_index.html.erb +1 -1
- data/app/views/headmin/filters/_association_count.html.erb +28 -0
- data/app/views/headmin/filters/_date.html.erb +1 -0
- data/app/views/headmin/forms/fields/_base.html.erb +1 -1
- data/app/views/headmin/layout/_content.html.erb +1 -1
- data/app/views/headmin/media/_media_item_modal.html.erb +26 -0
- data/app/views/headmin/media/_modal.html.erb +8 -3
- data/app/views/headmin/media/_thumbnail.html.erb +20 -0
- data/app/views/headmin/media/create.turbo_stream.erb +1 -1
- data/app/views/headmin/media/index.turbo_stream.erb +11 -0
- data/app/views/headmin/media/thumbnail.html.erb +3 -0
- data/app/views/headmin/nav/_item.html.erb +6 -1
- data/app/views/headmin/pagination/_infinite.html.erb +7 -0
- data/config/initializers/extend_active_storage_attachment.rb +3 -0
- data/config/locales/headmin/filters/en.yml +2 -0
- data/config/locales/headmin/filters/nl.yml +2 -0
- data/config/locales/headmin/media/en.yml +1 -0
- data/config/locales/headmin/media/nl.yml +1 -0
- data/config/locales/headmin/pagination/en.yml +2 -0
- data/config/locales/headmin/pagination/nl.yml +2 -0
- data/config/routes.rb +2 -1
- data/lib/headmin/version.rb +1 -1
- data/package.json +1 -1
- metadata +12 -3
- data/app/views/headmin/media/_item.html.erb +0 -16
@@ -1,110 +1,147 @@
|
|
1
1
|
/* global CustomEvent */
|
2
|
-
import {
|
2
|
+
import {Controller} from '@hotwired/stimulus'
|
3
3
|
|
4
4
|
export default class extends Controller {
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
}
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
-
|
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.
|
10144
|
-
this.
|
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
|
-
|
10188
|
-
|
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
|
-
|
10234
|
-
this.
|
10295
|
+
removeAllDeselectedItems(items) {
|
10296
|
+
this.removeDeselectedItems(items, this.itemTargets);
|
10235
10297
|
}
|
10236
|
-
|
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.
|
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
|
-
|
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(
|
10392
|
+
items: this.renderItemsForEvent()
|
10296
10393
|
}
|
10297
10394
|
}));
|
10298
10395
|
}
|
10299
10396
|
triggerFormSubmission() {
|
10300
10397
|
this.formTarget.requestSubmit();
|
10301
10398
|
}
|
10302
|
-
renderItemsForEvent(
|
10303
|
-
return
|
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:
|
10308
|
-
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.
|
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.
|
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:/
|
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
|