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.
- checksums.yaml +4 -4
- data/Gemfile.lock +5 -4
- data/app/assets/javascripts/headmin/controllers/filter_controller.js +15 -3
- data/app/assets/javascripts/headmin/controllers/filter_row_controller.js +28 -0
- data/app/assets/javascripts/headmin/controllers/infinite_scroller_controller.js +28 -0
- data/app/assets/javascripts/headmin/controllers/media_controller.js +24 -8
- data/app/assets/javascripts/headmin/controllers/media_modal_controller.js +47 -10
- data/app/assets/javascripts/headmin/index.js +2 -0
- data/app/assets/javascripts/headmin.js +122 -19
- data/app/controllers/headmin/media_controller.rb +11 -0
- data/app/helpers/headmin/form_helper.rb +0 -11
- 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/views/headmin/_filters.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/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 +3 -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/pagination/_infinite.html.erb +7 -0
- data/config/locales/headmin/filters/en.yml +2 -0
- data/config/locales/headmin/filters/nl.yml +2 -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 +11 -4
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 819c038f09a0d2dab83f4931d67256dd6130244afcff409af0580d8abf72d02c
|
4
|
+
data.tar.gz: 28fa2501518683ed07ce67315a42b62b1f2d8749208196a84ef20642125b815b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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.
|
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 (
|
292
|
-
activerecord (>=
|
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
|
-
|
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.
|
69
|
+
this.removeAllDeselectedItems(items)
|
70
70
|
|
71
|
-
// Add all selected items
|
72
|
-
this.
|
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
|
-
|
133
|
-
|
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
|
-
|
196
|
-
this.
|
203
|
+
removeAllDeselectedItems (items) {
|
204
|
+
this.removeDeselectedItems(items, this.itemTargets)
|
197
205
|
}
|
198
206
|
|
199
|
-
|
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.
|
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
|
-
|
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(
|
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 (
|
57
|
-
return
|
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:
|
63
|
-
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.
|
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.
|
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
|
-
|
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
|
+
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(
|
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 blobId = `#blob_${id}`;
|
10407
|
+
const element = this.element.querySelector(blobId);
|
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,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
|
-
|
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
|
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: "○ #{I18n.t("headmin.filters.operators.is_null")}",
|
25
25
|
is_not_null: "● #{I18n.t("headmin.filters.operators.is_not_null")}",
|
26
26
|
in: "∋ #{I18n.t("headmin.filters.operators.in")}",
|
27
|
-
not_in: "∌ #{I18n.t("headmin.filters.operators.not_in")}"
|
27
|
+
not_in: "∌ #{I18n.t("headmin.filters.operators.not_in")}",
|
28
|
+
between: "↔ #{I18n.t("headmin.filters.operators.between")}",
|
29
|
+
not_between: "⥈ #{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
|
-
<%=
|
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
|
-
<%=
|
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:
|
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
|
-
<%=
|
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,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 %>
|
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
|
data/lib/headmin/version.rb
CHANGED
data/package.json
CHANGED
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.
|
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:
|
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.
|
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>
|