headmin 0.5.4 → 0.5.6
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 +2 -2
- data/app/assets/javascripts/headmin/controllers/media_controller.js +1 -39
- data/app/assets/javascripts/headmin/controllers/repeater_controller.js +16 -4
- data/app/assets/javascripts/headmin.js +10 -33
- data/app/controllers/concerns/headmin/pagination.rb +5 -1
- data/app/controllers/headmin/media_controller.rb +8 -3
- data/app/models/concerns/headmin/blob.rb +36 -0
- data/app/models/concerns/headmin/field.rb +1 -1
- data/app/models/concerns/headmin/fieldable.rb +4 -1
- data/app/models/headmin/filter/association_view.rb +1 -1
- data/app/models/headmin/filter/base.rb +34 -17
- data/app/models/headmin/filter/boolean_view.rb +1 -5
- data/app/models/headmin/filter/date_view.rb +1 -5
- data/app/models/headmin/filter/field.rb +55 -0
- data/app/models/headmin/filter/field_view.rb +50 -0
- data/app/models/headmin/filter/filter_view.rb +25 -0
- data/app/models/headmin/filter/number_view.rb +1 -5
- data/app/models/headmin/filter/options_view.rb +1 -5
- data/app/models/headmin/filter/text_view.rb +1 -5
- data/app/models/headmin/filters.rb +19 -4
- data/app/models/headmin/form/media_item_view.rb +4 -0
- data/app/models/headmin/form/media_view.rb +5 -1
- data/app/views/headmin/_filters.html.erb +1 -1
- data/app/views/headmin/filters/_field.html.erb +23 -0
- data/app/views/headmin/forms/_media.html.erb +3 -5
- data/app/views/headmin/media/_modal.html.erb +1 -1
- data/app/views/headmin/media/index.html.erb +1 -1
- data/config/initializers/extend_active_storage_blob.rb +3 -0
- data/config/locales/headmin/forms/en.yml +0 -8
- data/config/locales/headmin/forms/nl.yml +0 -8
- data/lib/headmin/version.rb +1 -1
- data/package.json +1 -1
- metadata +8 -3
- data/app/views/headmin/forms/media/_validation.html.erb +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f5b560711076cca763871af2499728101e4f8d7a27a04f5c702671165e134d7
|
4
|
+
data.tar.gz: ed2d18ec0405786fd9b4f18984e750571062124828fc1102a662f78e0333c03e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8afa230d5e31dad3cbaa288fe46849672af9c4df118b4e9a9ed8024eea60da7d1089865b3678ee67860cfe88d34f67ba3a3db0c2b8ee1e37ebfbe515e2ae07fc
|
7
|
+
data.tar.gz: 106023130df278d025a608345e763aac87b6586736e511fd05866703876b296de9ae08d43ea5fd86d3f7cb75d378935e3aa86ff2d9f519d50434ee02e6453dc8
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
headmin (0.5.
|
4
|
+
headmin (0.5.5)
|
5
5
|
closure_tree (~> 7.4)
|
6
6
|
inline_svg (~> 1.7)
|
7
7
|
redcarpet (~> 3.5)
|
@@ -226,7 +226,7 @@ GEM
|
|
226
226
|
actionpack (>= 5.0)
|
227
227
|
railties (>= 5.0)
|
228
228
|
rexml (3.2.5)
|
229
|
-
rouge (3.
|
229
|
+
rouge (3.30.0)
|
230
230
|
route_translator (12.1.0)
|
231
231
|
actionpack (>= 5.2, < 7.1)
|
232
232
|
activesupport (>= 5.2, < 7.1)
|
@@ -3,7 +3,7 @@ import Sortable from 'sortablejs'
|
|
3
3
|
|
4
4
|
export default class extends Controller {
|
5
5
|
static get targets () {
|
6
|
-
return ['item', 'template', 'thumbnails', 'modalButton', 'placeholder', '
|
6
|
+
return ['item', 'template', 'thumbnails', 'modalButton', 'placeholder', 'count', 'editButton']
|
7
7
|
}
|
8
8
|
|
9
9
|
connect () {
|
@@ -17,8 +17,6 @@ export default class extends Controller {
|
|
17
17
|
if (this.hasSorting()) {
|
18
18
|
this.initSortable()
|
19
19
|
}
|
20
|
-
|
21
|
-
this.validate()
|
22
20
|
}
|
23
21
|
|
24
22
|
// Actions
|
@@ -83,42 +81,6 @@ export default class extends Controller {
|
|
83
81
|
|
84
82
|
// Toggle placeholder
|
85
83
|
this.togglePlaceholder()
|
86
|
-
|
87
|
-
// Validate
|
88
|
-
this.validate()
|
89
|
-
}
|
90
|
-
|
91
|
-
validate () {
|
92
|
-
this.clearValidation()
|
93
|
-
if (this.element.dataset.required === '0') return
|
94
|
-
this.validateMinimum()
|
95
|
-
this.validateMaximum()
|
96
|
-
}
|
97
|
-
|
98
|
-
clearValidation () {
|
99
|
-
this.validationInputTarget.setCustomValidity('')
|
100
|
-
}
|
101
|
-
|
102
|
-
validateMinimum () {
|
103
|
-
const count = this.activeItems().length
|
104
|
-
if (count < this.minActiveItems()) {
|
105
|
-
this.validationInputTarget.setCustomValidity(this.validationInputTarget.dataset.minMessage)
|
106
|
-
}
|
107
|
-
}
|
108
|
-
|
109
|
-
validateMaximum () {
|
110
|
-
const count = this.activeItems().length
|
111
|
-
if (count > this.maxActiveItems()) {
|
112
|
-
this.validationInputTarget.setCustomValidity(this.validationInputTarget.dataset.maxMessage)
|
113
|
-
}
|
114
|
-
}
|
115
|
-
|
116
|
-
minActiveItems () {
|
117
|
-
return parseInt(this.element.dataset.min, 10) || 0
|
118
|
-
}
|
119
|
-
|
120
|
-
maxActiveItems () {
|
121
|
-
return parseInt(this.element.dataset.max, 10) || Infinity
|
122
84
|
}
|
123
85
|
|
124
86
|
resetPositions () {
|
@@ -80,8 +80,8 @@ export default class extends Controller {
|
|
80
80
|
row.remove()
|
81
81
|
} else {
|
82
82
|
// Existing records are hidden and flagged for deletion
|
83
|
-
|
84
|
-
row.
|
83
|
+
this.flagRowForDeletion(row)
|
84
|
+
row.remove()
|
85
85
|
}
|
86
86
|
|
87
87
|
this.resetIndices()
|
@@ -89,6 +89,18 @@ export default class extends Controller {
|
|
89
89
|
this.toggleEmpty()
|
90
90
|
}
|
91
91
|
|
92
|
+
flagRowForDeletion (row) {
|
93
|
+
const destroyInput = row.querySelector('input[name*=\'_destroy\']')
|
94
|
+
const idInput = row.querySelector('input[name*=\'[id]\']')
|
95
|
+
|
96
|
+
// Update _destroy value
|
97
|
+
destroyInput.value = 1
|
98
|
+
|
99
|
+
// Move away from row
|
100
|
+
this.listTarget.parentNode.appendChild(destroyInput)
|
101
|
+
this.listTarget.parentNode.appendChild(idInput)
|
102
|
+
}
|
103
|
+
|
92
104
|
getTemplate (name) {
|
93
105
|
return this.templateTargets.filter((template) => {
|
94
106
|
return template.dataset.templateName === name
|
@@ -107,7 +119,7 @@ export default class extends Controller {
|
|
107
119
|
visibleRows () {
|
108
120
|
const rows = this.rowTargets
|
109
121
|
return rows.filter((row) => {
|
110
|
-
return row.querySelector(
|
122
|
+
return row.querySelector('input[name*=\'_destroy\']').value !== '1'
|
111
123
|
})
|
112
124
|
}
|
113
125
|
|
@@ -121,7 +133,7 @@ export default class extends Controller {
|
|
121
133
|
|
122
134
|
resetPositions () {
|
123
135
|
this.visibleRows().forEach((row, index) => {
|
124
|
-
const positionInput = row.querySelector(
|
136
|
+
const positionInput = row.querySelector('input[name*=\'position\']')
|
125
137
|
if (positionInput) {
|
126
138
|
positionInput.value = index
|
127
139
|
}
|
@@ -10096,7 +10096,7 @@ var hello_controller_default = class extends Controller {
|
|
10096
10096
|
// app/assets/javascripts/headmin/controllers/media_controller.js
|
10097
10097
|
var media_controller_default = class extends Controller {
|
10098
10098
|
static get targets() {
|
10099
|
-
return ["item", "template", "thumbnails", "modalButton", "placeholder", "
|
10099
|
+
return ["item", "template", "thumbnails", "modalButton", "placeholder", "count", "editButton"];
|
10100
10100
|
}
|
10101
10101
|
connect() {
|
10102
10102
|
document.addEventListener("mediaSelectionSubmitted", (event) => {
|
@@ -10107,7 +10107,6 @@ var media_controller_default = class extends Controller {
|
|
10107
10107
|
if (this.hasSorting()) {
|
10108
10108
|
this.initSortable();
|
10109
10109
|
}
|
10110
|
-
this.validate();
|
10111
10110
|
}
|
10112
10111
|
destroy(event) {
|
10113
10112
|
const item = event.currentTarget.closest("[data-media-target='item']");
|
@@ -10148,35 +10147,6 @@ var media_controller_default = class extends Controller {
|
|
10148
10147
|
this.resetPositions();
|
10149
10148
|
this.syncIds();
|
10150
10149
|
this.togglePlaceholder();
|
10151
|
-
this.validate();
|
10152
|
-
}
|
10153
|
-
validate() {
|
10154
|
-
this.clearValidation();
|
10155
|
-
if (this.element.dataset.required === "0")
|
10156
|
-
return;
|
10157
|
-
this.validateMinimum();
|
10158
|
-
this.validateMaximum();
|
10159
|
-
}
|
10160
|
-
clearValidation() {
|
10161
|
-
this.validationInputTarget.setCustomValidity("");
|
10162
|
-
}
|
10163
|
-
validateMinimum() {
|
10164
|
-
const count = this.activeItems().length;
|
10165
|
-
if (count < this.minActiveItems()) {
|
10166
|
-
this.validationInputTarget.setCustomValidity(this.validationInputTarget.dataset.minMessage);
|
10167
|
-
}
|
10168
|
-
}
|
10169
|
-
validateMaximum() {
|
10170
|
-
const count = this.activeItems().length;
|
10171
|
-
if (count > this.maxActiveItems()) {
|
10172
|
-
this.validationInputTarget.setCustomValidity(this.validationInputTarget.dataset.maxMessage);
|
10173
|
-
}
|
10174
|
-
}
|
10175
|
-
minActiveItems() {
|
10176
|
-
return parseInt(this.element.dataset.min, 10) || 0;
|
10177
|
-
}
|
10178
|
-
maxActiveItems() {
|
10179
|
-
return parseInt(this.element.dataset.max, 10) || Infinity;
|
10180
10150
|
}
|
10181
10151
|
resetPositions() {
|
10182
10152
|
this.activeItems().forEach((item, index2) => {
|
@@ -15559,13 +15529,20 @@ var repeater_controller_default = class extends Controller {
|
|
15559
15529
|
if (row.dataset.newRecord === "true") {
|
15560
15530
|
row.remove();
|
15561
15531
|
} else {
|
15562
|
-
|
15563
|
-
row.
|
15532
|
+
this.flagRowForDeletion(row);
|
15533
|
+
row.remove();
|
15564
15534
|
}
|
15565
15535
|
this.resetIndices();
|
15566
15536
|
this.resetPositions();
|
15567
15537
|
this.toggleEmpty();
|
15568
15538
|
}
|
15539
|
+
flagRowForDeletion(row) {
|
15540
|
+
const destroyInput = row.querySelector("input[name*='_destroy']");
|
15541
|
+
const idInput = row.querySelector("input[name*='[id]']");
|
15542
|
+
destroyInput.value = 1;
|
15543
|
+
this.listTarget.parentNode.appendChild(destroyInput);
|
15544
|
+
this.listTarget.parentNode.appendChild(idInput);
|
15545
|
+
}
|
15569
15546
|
getTemplate(name) {
|
15570
15547
|
return this.templateTargets.filter((template) => {
|
15571
15548
|
return template.dataset.templateName === name;
|
@@ -2,7 +2,11 @@ module Headmin
|
|
2
2
|
module Pagination
|
3
3
|
def paginate(collection)
|
4
4
|
@records_filtered = collection.count
|
5
|
-
collection.
|
5
|
+
if collection.is_a?(Array)
|
6
|
+
Kaminari.paginate_array(collection).page(page).per(per_page)
|
7
|
+
else
|
8
|
+
collection.page(page).per(per_page)
|
9
|
+
end
|
6
10
|
end
|
7
11
|
|
8
12
|
def page
|
@@ -4,12 +4,12 @@ class Headmin::MediaController < HeadminController
|
|
4
4
|
def index
|
5
5
|
@blobs =
|
6
6
|
ActiveStorage::Blob
|
7
|
-
.
|
8
|
-
.
|
9
|
-
.or(ActiveStorage::Blob.where(active_storage_attachments: {id: nil})) # Or an orphan
|
7
|
+
.not_attached_to_variant
|
8
|
+
.by_mimetypes_string(media_params[:mimetype])
|
10
9
|
.order(created_at: :desc)
|
11
10
|
.group(:id)
|
12
11
|
.all
|
12
|
+
@mimetypes = media_params[:mimetype]
|
13
13
|
end
|
14
14
|
|
15
15
|
def create
|
@@ -42,6 +42,11 @@ class Headmin::MediaController < HeadminController
|
|
42
42
|
|
43
43
|
def media_params
|
44
44
|
params.permit(
|
45
|
+
:min,
|
46
|
+
:max,
|
47
|
+
:name,
|
48
|
+
:mimetype,
|
49
|
+
ids: [],
|
45
50
|
files: []
|
46
51
|
)
|
47
52
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Headmin
|
2
|
+
module Blob
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class << self
|
7
|
+
def not_attached_to_variant
|
8
|
+
left_outer_joins(:attachments)
|
9
|
+
.where.not(active_storage_attachments: {record_type: "ActiveStorage::VariantRecord"})
|
10
|
+
.or(is_orphan)
|
11
|
+
end
|
12
|
+
|
13
|
+
def is_orphan
|
14
|
+
left_outer_joins(:attachments)
|
15
|
+
.where(active_storage_attachments: {id: nil})
|
16
|
+
end
|
17
|
+
|
18
|
+
def by_mimetypes_string(mimetype_string)
|
19
|
+
by_mimetypes(mimetype_string.split(","))
|
20
|
+
end
|
21
|
+
|
22
|
+
def by_mimetypes(mimetypes = [])
|
23
|
+
results = self
|
24
|
+
|
25
|
+
mimetypes.map.with_index do |mimetype, index|
|
26
|
+
content_type = mimetype.tr("*", "%")
|
27
|
+
query = where(arel_table[:content_type].matches(content_type))
|
28
|
+
results = index == 0 ? query : results.or(query)
|
29
|
+
end
|
30
|
+
|
31
|
+
results
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -4,7 +4,7 @@ module Headmin
|
|
4
4
|
|
5
5
|
included do
|
6
6
|
# Configuration
|
7
|
-
has_closure_tree order: "position", numeric_order: true
|
7
|
+
has_closure_tree order: "position", numeric_order: true, dependent: :destroy
|
8
8
|
|
9
9
|
# Associations
|
10
10
|
belongs_to :fieldable, polymorphic: true, optional: true, touch: true
|
@@ -28,7 +28,10 @@ module Headmin
|
|
28
28
|
private
|
29
29
|
|
30
30
|
def parse_fields
|
31
|
-
hash_tree =
|
31
|
+
hash_tree = {}
|
32
|
+
fields.roots.includes(default_include_tables | include_tables).each do |field|
|
33
|
+
field.hash_tree.map { |key, value| hash_tree[key] = value }
|
34
|
+
end
|
32
35
|
parse_hash_tree(hash_tree)
|
33
36
|
end
|
34
37
|
|
@@ -21,12 +21,13 @@ module Headmin
|
|
21
21
|
}
|
22
22
|
|
23
23
|
# Methods
|
24
|
-
def initialize(attribute, params)
|
25
|
-
@
|
26
|
-
@
|
24
|
+
def initialize(attribute, params, association: nil)
|
25
|
+
@attribute = association ? "#{association}_#{attribute}".to_sym : attribute
|
26
|
+
@raw_value = params[@attribute]
|
27
|
+
@association = association
|
27
28
|
@instructions = []
|
28
29
|
|
29
|
-
if params.key?(attribute)
|
30
|
+
if params.key?(@attribute)
|
30
31
|
parse(@raw_value)
|
31
32
|
end
|
32
33
|
end
|
@@ -61,7 +62,8 @@ module Headmin
|
|
61
62
|
query = build_query(query, collection, instruction)
|
62
63
|
end
|
63
64
|
|
64
|
-
collection.
|
65
|
+
collection = collection.joins(@association) if @association
|
66
|
+
collection.distinct.where(query)
|
65
67
|
end
|
66
68
|
|
67
69
|
def cast_value(value)
|
@@ -74,6 +76,30 @@ module Headmin
|
|
74
76
|
value
|
75
77
|
end
|
76
78
|
|
79
|
+
def build_query(query, collection, instruction)
|
80
|
+
query_operator = convert_to_query_operator(instruction[:operator])
|
81
|
+
query_value = convert_to_query_value(instruction[:value], instruction[:operator])
|
82
|
+
|
83
|
+
query_operator, query_value = process_null_operators(query_operator, query_value)
|
84
|
+
|
85
|
+
model = collection.is_a?(Class) ? collection : collection.model
|
86
|
+
|
87
|
+
if @association
|
88
|
+
# Association attributes are passed through as {association}_{attribute}, so we need to transform this into {attribute}
|
89
|
+
new_attribute = attribute.to_s.gsub("#{@association}_", "").to_sym
|
90
|
+
new_model = model.reflect_on_association(@association)
|
91
|
+
|
92
|
+
# In case the association cannot be found, raise a well defined error
|
93
|
+
raise UnknownAssociation if new_model.nil?
|
94
|
+
|
95
|
+
new_query = new_model.klass.arel_table[new_attribute].send(query_operator, query_value)
|
96
|
+
else
|
97
|
+
new_query = model.arel_table[attribute].send(query_operator, query_value)
|
98
|
+
end
|
99
|
+
|
100
|
+
query ? query.send(instruction[:conditional], new_query) : new_query
|
101
|
+
end
|
102
|
+
|
77
103
|
private
|
78
104
|
|
79
105
|
def parse(string)
|
@@ -139,18 +165,6 @@ module Headmin
|
|
139
165
|
process_value(string, operator)
|
140
166
|
end
|
141
167
|
|
142
|
-
def build_query(query, collection, instruction)
|
143
|
-
query_operator = convert_to_query_operator(instruction[:operator])
|
144
|
-
query_value = convert_to_query_value(instruction[:value], instruction[:operator])
|
145
|
-
|
146
|
-
query_operator, query_value = process_null_operators(query_operator, query_value)
|
147
|
-
|
148
|
-
model = collection.is_a?(Class) ? collection : collection.model
|
149
|
-
new_query = model.arel_table[attribute].send(query_operator, query_value)
|
150
|
-
|
151
|
-
query ? query.send(instruction[:conditional], new_query) : new_query
|
152
|
-
end
|
153
|
-
|
154
168
|
def process_null_operators(operator, value)
|
155
169
|
# In case of null operators (is_null and is_not_null), we have to intercept the operator and value values
|
156
170
|
# and transform them to the correct operator (eq or not_eq) and value (nil)
|
@@ -237,5 +251,8 @@ module Headmin
|
|
237
251
|
|
238
252
|
class NotImplementedMethodError < StandardError
|
239
253
|
end
|
254
|
+
|
255
|
+
class UnknownAssociation < StandardError
|
256
|
+
end
|
240
257
|
end
|
241
258
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Headmin
|
2
2
|
module Filter
|
3
|
-
class BooleanView <
|
3
|
+
class BooleanView < FilterView
|
4
4
|
def base_options
|
5
5
|
keys = %i[name label form]
|
6
6
|
options = to_h.slice(*keys)
|
@@ -28,10 +28,6 @@ module Headmin
|
|
28
28
|
@name || attribute
|
29
29
|
end
|
30
30
|
|
31
|
-
def label
|
32
|
-
@label || I18n.t("attributes.#{attribute}", default: name.to_s)
|
33
|
-
end
|
34
|
-
|
35
31
|
def default_base_options
|
36
32
|
{
|
37
33
|
label: label,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Headmin
|
2
2
|
module Filter
|
3
|
-
class DateView <
|
3
|
+
class DateView < FilterView
|
4
4
|
def base_options
|
5
5
|
keys = %i[name label form]
|
6
6
|
options = to_h.slice(*keys)
|
@@ -23,10 +23,6 @@ module Headmin
|
|
23
23
|
@name || attribute
|
24
24
|
end
|
25
25
|
|
26
|
-
def label
|
27
|
-
@label || I18n.t("attributes.#{attribute}", default: name.to_s)
|
28
|
-
end
|
29
|
-
|
30
26
|
def default_base_options
|
31
27
|
{
|
32
28
|
label: label,
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Headmin
|
2
|
+
module Filter
|
3
|
+
class Field < Headmin::Filter::Base
|
4
|
+
OPERATORS = %w[eq not_eq matches does_not_match]
|
5
|
+
|
6
|
+
def initialize(attribute, params, association: nil)
|
7
|
+
@attribute = association ? "#{association}_#{attribute}".to_sym : attribute
|
8
|
+
@attribute = "field_#{attribute}".to_sym
|
9
|
+
@raw_value = params[@attribute]
|
10
|
+
@association = association
|
11
|
+
@instructions = []
|
12
|
+
|
13
|
+
if params.key?(@attribute)
|
14
|
+
parse(@raw_value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def cast_value(value)
|
19
|
+
value
|
20
|
+
end
|
21
|
+
|
22
|
+
def display_value(value)
|
23
|
+
value.downcase
|
24
|
+
end
|
25
|
+
|
26
|
+
def query(collection)
|
27
|
+
return collection unless @instructions.any?
|
28
|
+
|
29
|
+
query = nil
|
30
|
+
|
31
|
+
@instructions.each do |instruction|
|
32
|
+
query = build_query(query, collection, instruction)
|
33
|
+
end
|
34
|
+
|
35
|
+
collection = collection.joins(:fields)
|
36
|
+
collection.where(query)
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_query(query, collection, instruction)
|
40
|
+
query_operator = convert_to_query_operator(instruction[:operator])
|
41
|
+
query_value = convert_to_query_value(instruction[:value], instruction[:operator])
|
42
|
+
|
43
|
+
query_operator, query_value = process_null_operators(query_operator, query_value)
|
44
|
+
|
45
|
+
model = collection.is_a?(Class) ? collection : collection.model
|
46
|
+
|
47
|
+
new_attribute = attribute.to_s.gsub("field_", "").to_sym
|
48
|
+
fields_model = model.reflect_on_association(:fields).klass
|
49
|
+
new_query = fields_model.arel_table[:name].matches(new_attribute).and(fields_model.arel_table[:value].send(query_operator, query_value))
|
50
|
+
|
51
|
+
query ? query.send(instruction[:conditional], new_query) : new_query
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Headmin
|
2
|
+
module Filter
|
3
|
+
class FieldView < 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
|
+
private
|
17
|
+
|
18
|
+
def id
|
19
|
+
"#{name}_value"
|
20
|
+
end
|
21
|
+
|
22
|
+
def name
|
23
|
+
"field_#{@name}".to_sym || attribute
|
24
|
+
end
|
25
|
+
|
26
|
+
def default_base_options
|
27
|
+
{
|
28
|
+
label: label,
|
29
|
+
name: "field_#{attribute}".to_sym,
|
30
|
+
filter: Headmin::Filter::Field.new(attribute.to_s.to_sym, @params),
|
31
|
+
allowed_operators: Headmin::Filter::Field::OPERATORS - %w[in not_in]
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def default_input_options
|
36
|
+
{
|
37
|
+
label: false,
|
38
|
+
wrapper: false,
|
39
|
+
id: id,
|
40
|
+
name: nil,
|
41
|
+
data: {
|
42
|
+
action: "change->filter#updateHiddenValue",
|
43
|
+
filter_target: "value",
|
44
|
+
filter_row_target: "original"
|
45
|
+
}
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Headmin
|
2
|
+
module Filter
|
3
|
+
class FilterView < ViewModel
|
4
|
+
def attribute
|
5
|
+
@association ? "#{@association}_#{@attribute}".to_sym : @attribute
|
6
|
+
end
|
7
|
+
|
8
|
+
def label
|
9
|
+
@label || I18n.t("attributes.#{attribute}", default: @association ? "#{association_model.model_name.human(count: 1)} - #{association_model.human_attribute_name(@attribute)}" : name.to_s)
|
10
|
+
end
|
11
|
+
|
12
|
+
def reflection
|
13
|
+
if @association
|
14
|
+
form.object.class.reflect_on_association(@association)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def association_model
|
19
|
+
if @association
|
20
|
+
reflection.klass
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Headmin
|
2
2
|
module Filter
|
3
|
-
class NumberView <
|
3
|
+
class NumberView < FilterView
|
4
4
|
def base_options
|
5
5
|
keys = %i[name label form]
|
6
6
|
options = to_h.slice(*keys)
|
@@ -23,10 +23,6 @@ module Headmin
|
|
23
23
|
@name || attribute
|
24
24
|
end
|
25
25
|
|
26
|
-
def label
|
27
|
-
@label || I18n.t("attributes.#{attribute}", default: name.to_s)
|
28
|
-
end
|
29
|
-
|
30
26
|
def default_base_options
|
31
27
|
{
|
32
28
|
label: label,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Headmin
|
2
2
|
module Filter
|
3
|
-
class OptionsView <
|
3
|
+
class OptionsView < FilterView
|
4
4
|
def base_options
|
5
5
|
keys = %i[name label form]
|
6
6
|
options = to_h.slice(*keys)
|
@@ -27,10 +27,6 @@ module Headmin
|
|
27
27
|
@name || attribute
|
28
28
|
end
|
29
29
|
|
30
|
-
def label
|
31
|
-
@label || I18n.t("attributes.#{attribute}", default: name.to_s)
|
32
|
-
end
|
33
|
-
|
34
30
|
def default_base_options
|
35
31
|
{
|
36
32
|
label: label,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Headmin
|
2
2
|
module Filter
|
3
|
-
class TextView <
|
3
|
+
class TextView < FilterView
|
4
4
|
def base_options
|
5
5
|
keys = %i[name label form]
|
6
6
|
options = to_h.slice(*keys)
|
@@ -23,10 +23,6 @@ module Headmin
|
|
23
23
|
@name || attribute
|
24
24
|
end
|
25
25
|
|
26
|
-
def label
|
27
|
-
@label || I18n.t("attributes.#{attribute}", default: name.to_s)
|
28
|
-
end
|
29
|
-
|
30
26
|
def default_base_options
|
31
27
|
{
|
32
28
|
label: label,
|
@@ -13,15 +13,30 @@ module Headmin
|
|
13
13
|
@param_types = param_types
|
14
14
|
end
|
15
15
|
|
16
|
-
def parse(attribute, type)
|
16
|
+
def parse(attribute, type, association: nil)
|
17
17
|
class_name = "Headmin::Filter::#{type.to_s.classify}".constantize
|
18
|
-
class_name.new(attribute, @params)
|
18
|
+
class_name.new(attribute, @params, association: association)
|
19
19
|
end
|
20
20
|
|
21
21
|
def query(collection)
|
22
22
|
@param_types.each do |attribute, type|
|
23
|
-
|
24
|
-
|
23
|
+
if type.is_a? Hash
|
24
|
+
# We are given attribute filters of an association
|
25
|
+
association = attribute
|
26
|
+
|
27
|
+
# By default, we offer a filter of type association
|
28
|
+
association_filter = Headmin::Filter::Association.new(attribute, @params, association: nil)
|
29
|
+
collection = association_filter.query(collection)
|
30
|
+
|
31
|
+
# Query all the passed attribute filters for this association
|
32
|
+
type.each do |new_attribute, new_type|
|
33
|
+
filter = parse(new_attribute, new_type, association: association)
|
34
|
+
collection = filter.query(collection)
|
35
|
+
end
|
36
|
+
else
|
37
|
+
filter = parse(attribute, type)
|
38
|
+
collection = filter.query(collection)
|
39
|
+
end
|
25
40
|
end
|
26
41
|
collection
|
27
42
|
end
|
@@ -23,6 +23,7 @@ module Headmin
|
|
23
23
|
min: min,
|
24
24
|
max: max,
|
25
25
|
sort: sort,
|
26
|
+
accept: accept,
|
26
27
|
required: required.nil? ? 0 : required
|
27
28
|
}
|
28
29
|
}).deep_merge(@wrapper || {})
|
@@ -45,7 +46,8 @@ module Headmin
|
|
45
46
|
form: form,
|
46
47
|
attribute: attribute,
|
47
48
|
min: min,
|
48
|
-
max: max
|
49
|
+
max: max,
|
50
|
+
accept: accept
|
49
51
|
}
|
50
52
|
end
|
51
53
|
|
@@ -117,6 +119,8 @@ module Headmin
|
|
117
119
|
end
|
118
120
|
end
|
119
121
|
|
122
|
+
attr_reader :accept
|
123
|
+
|
120
124
|
def required
|
121
125
|
@required ? 1 : nil
|
122
126
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<%
|
2
|
+
# headmin/filters/field
|
3
|
+
#
|
4
|
+
# ==== Required parameters
|
5
|
+
# * +form+ - Form object
|
6
|
+
# * +attribute+ - Name of the attribute to be filtered
|
7
|
+
#
|
8
|
+
# ==== Optional parameters
|
9
|
+
# * +label+ - Display label
|
10
|
+
# * +name+ - Name of the filter parameter
|
11
|
+
#
|
12
|
+
# ==== Examples
|
13
|
+
# Basic version
|
14
|
+
# <%= render "headmin/filters", url: admin_orders_path do |form| %#>
|
15
|
+
# <%= render "headmin/filters/field", form: form, attribute: :title %#>
|
16
|
+
# <% end %#>
|
17
|
+
|
18
|
+
text = Headmin::Filter::FieldView.new(local_assigns.merge(params: params))
|
19
|
+
%>
|
20
|
+
|
21
|
+
<%= render "headmin/filters/base", text.base_options do |value| %>
|
22
|
+
<%= render "headmin/forms/text", text.input_options.merge({value: value}) %>
|
23
|
+
<% end %>
|
@@ -31,16 +31,14 @@
|
|
31
31
|
<%= render "headmin/forms/wrapper", media.wrapper_options do %>
|
32
32
|
<%= render "headmin/forms/label", media.label_options if media.prepend_label? %>
|
33
33
|
<div class="h-form-file-thumbnails" data-media-target="thumbnails">
|
34
|
-
<%= render "headmin/forms/media/validation", media.custom_validation_options %>
|
35
|
-
|
36
34
|
<!-- Render previews for attachments -->
|
37
35
|
<%= form.fields_for(media.nested_attribute, media.association_object) do |ff| %>
|
38
|
-
<%= render "headmin/forms/media/item", media.item_options.merge(form: ff, url: headmin_media_url(name: media.name, ids: media.blob_ids, min: media.min, max: media.max)) %>
|
36
|
+
<%= render "headmin/forms/media/item", media.item_options.merge(form: ff, url: headmin_media_url(name: media.name, ids: media.blob_ids, min: media.min, max: media.max, mimetype: media.accept)) %>
|
39
37
|
<% end %>
|
40
38
|
|
41
39
|
<!-- Placeholder -->
|
42
40
|
<div class="<%= "d-none" if media.attachments.any? %>" data-media-target="placeholder">
|
43
|
-
<a href="<%= headmin_media_url(name: media.name, ids: media.blob_ids, min: media.min, max: media.max) %>" data-turbo-frame="remote_modal" data-media-target="modalButton">
|
41
|
+
<a href="<%= headmin_media_url(name: media.name, ids: media.blob_ids, min: media.min, max: media.max, mimetype: media.accept) %>" data-turbo-frame="remote_modal" data-media-target="modalButton">
|
44
42
|
<%= render "headmin/thumbnail", media.thumbnail_options %>
|
45
43
|
</a>
|
46
44
|
</div>
|
@@ -50,7 +48,7 @@
|
|
50
48
|
<% association_object = ActiveStorage::Attachment.new %>
|
51
49
|
<template data-media-target="template" data-template-id-regex="<%= association_object.object_id %>">
|
52
50
|
<%= form.fields_for(media.nested_attribute, ActiveStorage::Attachment.new, child_index: association_object.object_id) do |ff| %>
|
53
|
-
<%= render "headmin/forms/media/item", media.item_options.merge(form: ff, url: headmin_media_url(name: media.name, ids: media.blob_ids, min: media.min, max: media.max)) %>
|
51
|
+
<%= render "headmin/forms/media/item", media.item_options.merge(form: ff, url: headmin_media_url(name: media.name, ids: media.blob_ids, min: media.min, max: media.max, mimetype: media.accept)) %>
|
54
52
|
<% end %>
|
55
53
|
</template>
|
56
54
|
|
@@ -22,7 +22,7 @@
|
|
22
22
|
<%= form.label :files, class: "btn h-btn-outline-light" do %>
|
23
23
|
<%= bootstrap_icon("upload") %>
|
24
24
|
<%= t(".upload") %>
|
25
|
-
<%= form.file_field :files, class: "d-none", multiple: true, data: {action: "change->media-modal#submitForm"} %>
|
25
|
+
<%= form.file_field :files, accept: mimetypes, class: "d-none", multiple: true, data: {action: "change->media-modal#submitForm"} %>
|
26
26
|
<% end %>
|
27
27
|
<% end %>
|
28
28
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><%= t(".close") %></button>
|
@@ -1,3 +1,3 @@
|
|
1
1
|
<%= turbo_frame_tag "remote_modal" do %>
|
2
|
-
<%= render "headmin/media/modal", blobs: @blobs, name: params[:name], min: params[:min], max: params[:max] %>
|
2
|
+
<%= render "headmin/media/modal", blobs: @blobs, mimetypes: @mimetypes, name: params[:name], min: params[:min], max: params[:max] %>
|
3
3
|
<% end %>
|
@@ -18,14 +18,6 @@ en:
|
|
18
18
|
remove:
|
19
19
|
title: Delete
|
20
20
|
confirm: Are you sure you want to delete this?
|
21
|
-
media:
|
22
|
-
validation:
|
23
|
-
min:
|
24
|
-
one: "Please select at least 1 item"
|
25
|
-
other: "Please select at least %{count} items"
|
26
|
-
max:
|
27
|
-
one: "Please limit your selection to maximum 1 item"
|
28
|
-
other: "Please limit your selection to maximum %{count} items"
|
29
21
|
select:
|
30
22
|
blank: Make a choice
|
31
23
|
repeater:
|
@@ -17,14 +17,6 @@ nl:
|
|
17
17
|
remove:
|
18
18
|
title: Verwijderen
|
19
19
|
confirm: Ben je zeker dat je dit wil verwijderen?
|
20
|
-
media:
|
21
|
-
validation:
|
22
|
-
min:
|
23
|
-
one: "Gelieve minstens 1 item te selecteren"
|
24
|
-
other: "Gelieve minstens %{count} items te selecteren"
|
25
|
-
max:
|
26
|
-
one: "Gelieve maximum 1 item te selecteren"
|
27
|
-
other: "Gelieve maximum %{count} items te selecteren"
|
28
20
|
select:
|
29
21
|
blank: Maak een keuze
|
30
22
|
repeater:
|
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.5.
|
4
|
+
version: 0.5.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jef Vlamings
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-11-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: closure_tree
|
@@ -244,6 +244,7 @@ files:
|
|
244
244
|
- app/helpers/headmin/form_helper.rb
|
245
245
|
- app/helpers/headmin/notification_helper.rb
|
246
246
|
- app/helpers/headmin/request_helper.rb
|
247
|
+
- app/models/concerns/headmin/blob.rb
|
247
248
|
- app/models/concerns/headmin/block.rb
|
248
249
|
- app/models/concerns/headmin/blockable.rb
|
249
250
|
- app/models/concerns/headmin/field.rb
|
@@ -268,6 +269,9 @@ files:
|
|
268
269
|
- app/models/headmin/filter/conditional_view.rb
|
269
270
|
- app/models/headmin/filter/date.rb
|
270
271
|
- app/models/headmin/filter/date_view.rb
|
272
|
+
- app/models/headmin/filter/field.rb
|
273
|
+
- app/models/headmin/filter/field_view.rb
|
274
|
+
- app/models/headmin/filter/filter_view.rb
|
271
275
|
- app/models/headmin/filter/flatpickr_view.rb
|
272
276
|
- app/models/headmin/filter/menu_item_view.rb
|
273
277
|
- app/models/headmin/filter/money.rb
|
@@ -336,6 +340,7 @@ files:
|
|
336
340
|
- app/views/headmin/filters/_base.html.erb
|
337
341
|
- app/views/headmin/filters/_boolean.html.erb
|
338
342
|
- app/views/headmin/filters/_date.html.erb
|
343
|
+
- app/views/headmin/filters/_field.html.erb
|
339
344
|
- app/views/headmin/filters/_flatpickr.html.erb
|
340
345
|
- app/views/headmin/filters/_number.html.erb
|
341
346
|
- app/views/headmin/filters/_options.html.erb
|
@@ -389,7 +394,6 @@ files:
|
|
389
394
|
- app/views/headmin/forms/fields/_list.html.erb
|
390
395
|
- app/views/headmin/forms/fields/_text.html.erb
|
391
396
|
- app/views/headmin/forms/media/_item.html.erb
|
392
|
-
- app/views/headmin/forms/media/_validation.html.erb
|
393
397
|
- app/views/headmin/forms/repeater/_row.html.erb
|
394
398
|
- app/views/headmin/heading/_title.html.erb
|
395
399
|
- app/views/headmin/layout/_body.html.erb
|
@@ -462,6 +466,7 @@ files:
|
|
462
466
|
- bin/setup
|
463
467
|
- config/importmap.rb
|
464
468
|
- config/initializers/customize_input_error.rb
|
469
|
+
- config/initializers/extend_active_storage_blob.rb
|
465
470
|
- config/locales/activerecord/en.yml
|
466
471
|
- config/locales/activerecord/nl.yml
|
467
472
|
- config/locales/defaults/en.yml
|
@@ -1,10 +0,0 @@
|
|
1
|
-
<!-- Custom validation field -->
|
2
|
-
<%= form.text_field :"validation_#{attribute}",
|
3
|
-
name: nil,
|
4
|
-
value: nil,
|
5
|
-
class: "h-form-media-validation",
|
6
|
-
data: {
|
7
|
-
"media-target": "validationInput",
|
8
|
-
"min-message": t(".min", count: min),
|
9
|
-
"max-message": t(".max", count: max),
|
10
|
-
} %>
|