headmin 0.5.8 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/Gemfile.lock +112 -110
  4. data/app/assets/javascripts/headmin/controllers/media_controller.js +38 -1
  5. data/app/assets/javascripts/headmin/controllers/textarea_controller.js +15 -1
  6. data/app/assets/javascripts/headmin.js +42 -2
  7. data/app/assets/stylesheets/headmin/overrides/redactorx.scss +2 -1
  8. data/app/assets/stylesheets/headmin.css +2 -1
  9. data/app/controllers/concerns/headmin/pagination.rb +1 -1
  10. data/app/models/concerns/headmin/blob.rb +2 -2
  11. data/app/models/concerns/headmin/block.rb +6 -0
  12. data/app/models/concerns/headmin/fieldable.rb +10 -2
  13. data/app/models/headmin/filter/base.rb +2 -2
  14. data/app/models/headmin/form/blocks_view.rb +13 -0
  15. data/app/models/headmin/form/color_view.rb +1 -2
  16. data/app/models/headmin/form/media_view.rb +2 -2
  17. data/app/views/headmin/_blocks.html.erb +3 -6
  18. data/app/views/headmin/forms/_blocks.html.erb +10 -3
  19. data/app/views/headmin/forms/_media.html.erb +7 -5
  20. data/app/views/headmin/forms/_repeater.html.erb +3 -1
  21. data/app/views/headmin/forms/blocks/_modal.html.erb +20 -0
  22. data/app/views/headmin/forms/fields/_group.html.erb +18 -11
  23. data/app/views/headmin/forms/fields/_list.html.erb +4 -1
  24. data/app/views/headmin/forms/media/_validation.html.erb +10 -0
  25. data/app/views/headmin/forms/repeater/_row.html.erb +2 -1
  26. data/app/views/headmin/layout/_footer.html.erb +1 -1
  27. data/config/locales/activerecord/en.yml +2 -0
  28. data/config/locales/activerecord/nl.yml +3 -0
  29. data/config/locales/headmin/forms/en.yml +12 -0
  30. data/config/locales/headmin/forms/nl.yml +13 -0
  31. data/lib/generators/templates/migrations/create_blocks.rb +2 -0
  32. data/lib/headmin/version.rb +1 -1
  33. data/package.json +1 -1
  34. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a3e1511a9e927ad280258b5cf3ac43df51b9c19e9cec274d314c84d4da94fc6
4
- data.tar.gz: cf34c6243886ce6d8955c93b7ac7ea9541603d18f1c279b1817cedb2ac64a6a4
3
+ metadata.gz: a0d9ff0c21ed70d23b6f58a61c4853028cc2580cdd23e7df89cdfce51d6c547e
4
+ data.tar.gz: 2e0c0aeb8efc4294c2a01bd04c46949e97a9a9c7056e5d0caf99f75339ff02d7
5
5
  SHA512:
6
- metadata.gz: 79a39b84ebd8b14e49aa9ac721fd8503407a3c4e660fb3837beb9611c33476a0ef41bd427e2dcc46f7df502a744f63548055031b5515a9990f7cab4bcfd538d6
7
- data.tar.gz: 326d06a1402976c2cf6a06081b1f20af02808f1071775df37bc8aaf019da9f0ff38c119ec7c837b45f95b139d5138d6ecd41889f21713e4f5e6768e816cc735a
6
+ metadata.gz: f0712678aa575c3e982ec2359b9f1bb949b968ca8785ea482db63319b3fa843413a0e5744b31c8c7e917aafbd7db676fe2270d4e2e0b6ce419e90e2c82d06d5b
7
+ data.tar.gz: a6965e77198490afc58e7825da2db8711a540159e6a0366c660f9c15c2f303fa19cfbef006540677efa6f8eb8bf9aee61bfde0d5a04c774d1393c5ac5cfda859
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Headmin Changelog
2
2
 
3
+ ## 0.6
4
+ - FEATURE: Blocks can now be configured to have custom anchor names and have its visibility toggled
5
+ - BREAK: Add this migration to current projects that make use of blocks
6
+ ```ruby
7
+ class AddVisibleHandleToBlock < ActiveRecord::Migration[7.0]
8
+ def change
9
+ add_column :blocks, :visible, :boolean, default: true
10
+ add_column :blocks, :handle, :string
11
+ end
12
+ end
13
+ ```
14
+
3
15
  ## 0.5
4
16
  - FEATURE: Filters now have support for operators and conditionals
5
17
  - BREAK: `headmin/filters` now return a form object that is required for each individual form
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- headmin (0.5.7)
4
+ headmin (0.5.9)
5
5
  closure_tree (~> 7.4)
6
6
  inline_svg (~> 1.7)
7
7
  redcarpet (~> 3.5)
@@ -10,81 +10,81 @@ PATH
10
10
  GEM
11
11
  remote: https://rubygems.org/
12
12
  specs:
13
- actioncable (7.0.2.4)
14
- actionpack (= 7.0.2.4)
15
- activesupport (= 7.0.2.4)
13
+ actioncable (7.0.4)
14
+ actionpack (= 7.0.4)
15
+ activesupport (= 7.0.4)
16
16
  nio4r (~> 2.0)
17
17
  websocket-driver (>= 0.6.1)
18
- actionmailbox (7.0.2.4)
19
- actionpack (= 7.0.2.4)
20
- activejob (= 7.0.2.4)
21
- activerecord (= 7.0.2.4)
22
- activestorage (= 7.0.2.4)
23
- activesupport (= 7.0.2.4)
18
+ actionmailbox (7.0.4)
19
+ actionpack (= 7.0.4)
20
+ activejob (= 7.0.4)
21
+ activerecord (= 7.0.4)
22
+ activestorage (= 7.0.4)
23
+ activesupport (= 7.0.4)
24
24
  mail (>= 2.7.1)
25
25
  net-imap
26
26
  net-pop
27
27
  net-smtp
28
- actionmailer (7.0.2.4)
29
- actionpack (= 7.0.2.4)
30
- actionview (= 7.0.2.4)
31
- activejob (= 7.0.2.4)
32
- activesupport (= 7.0.2.4)
28
+ actionmailer (7.0.4)
29
+ actionpack (= 7.0.4)
30
+ actionview (= 7.0.4)
31
+ activejob (= 7.0.4)
32
+ activesupport (= 7.0.4)
33
33
  mail (~> 2.5, >= 2.5.4)
34
34
  net-imap
35
35
  net-pop
36
36
  net-smtp
37
37
  rails-dom-testing (~> 2.0)
38
- actionpack (7.0.2.4)
39
- actionview (= 7.0.2.4)
40
- activesupport (= 7.0.2.4)
38
+ actionpack (7.0.4)
39
+ actionview (= 7.0.4)
40
+ activesupport (= 7.0.4)
41
41
  rack (~> 2.0, >= 2.2.0)
42
42
  rack-test (>= 0.6.3)
43
43
  rails-dom-testing (~> 2.0)
44
44
  rails-html-sanitizer (~> 1.0, >= 1.2.0)
45
- actiontext (7.0.2.4)
46
- actionpack (= 7.0.2.4)
47
- activerecord (= 7.0.2.4)
48
- activestorage (= 7.0.2.4)
49
- activesupport (= 7.0.2.4)
45
+ actiontext (7.0.4)
46
+ actionpack (= 7.0.4)
47
+ activerecord (= 7.0.4)
48
+ activestorage (= 7.0.4)
49
+ activesupport (= 7.0.4)
50
50
  globalid (>= 0.6.0)
51
51
  nokogiri (>= 1.8.5)
52
- actionview (7.0.2.4)
53
- activesupport (= 7.0.2.4)
52
+ actionview (7.0.4)
53
+ activesupport (= 7.0.4)
54
54
  builder (~> 3.1)
55
55
  erubi (~> 1.4)
56
56
  rails-dom-testing (~> 2.0)
57
57
  rails-html-sanitizer (~> 1.1, >= 1.2.0)
58
- activejob (7.0.2.4)
59
- activesupport (= 7.0.2.4)
58
+ activejob (7.0.4)
59
+ activesupport (= 7.0.4)
60
60
  globalid (>= 0.3.6)
61
- activemodel (7.0.2.4)
62
- activesupport (= 7.0.2.4)
63
- activerecord (7.0.2.4)
64
- activemodel (= 7.0.2.4)
65
- activesupport (= 7.0.2.4)
66
- activestorage (7.0.2.4)
67
- actionpack (= 7.0.2.4)
68
- activejob (= 7.0.2.4)
69
- activerecord (= 7.0.2.4)
70
- activesupport (= 7.0.2.4)
61
+ activemodel (7.0.4)
62
+ activesupport (= 7.0.4)
63
+ activerecord (7.0.4)
64
+ activemodel (= 7.0.4)
65
+ activesupport (= 7.0.4)
66
+ activestorage (7.0.4)
67
+ actionpack (= 7.0.4)
68
+ activejob (= 7.0.4)
69
+ activerecord (= 7.0.4)
70
+ activesupport (= 7.0.4)
71
71
  marcel (~> 1.0)
72
72
  mini_mime (>= 1.1.0)
73
- activesupport (7.0.2.4)
73
+ activesupport (7.0.4)
74
74
  concurrent-ruby (~> 1.0, >= 1.0.2)
75
75
  i18n (>= 1.6, < 2)
76
76
  minitest (>= 5.1)
77
77
  tzinfo (~> 2.0)
78
78
  acts_as_list (1.0.4)
79
79
  activerecord (>= 4.2)
80
- addressable (2.8.0)
81
- public_suffix (>= 2.0.2, < 5.0)
80
+ addressable (2.8.1)
81
+ public_suffix (>= 2.0.2, < 6.0)
82
82
  ast (2.4.2)
83
83
  bcrypt (3.1.18)
84
84
  breadcrumbs_on_rails (4.1.0)
85
85
  railties (>= 5.0)
86
86
  builder (3.2.4)
87
- capybara (3.36.0)
87
+ capybara (3.38.0)
88
88
  addressable
89
89
  matrix
90
90
  mini_mime (>= 0.1.3)
@@ -98,19 +98,18 @@ GEM
98
98
  with_advisory_lock (>= 4.0.0)
99
99
  concurrent-ruby (1.1.10)
100
100
  crass (1.0.6)
101
- debug (1.5.0)
102
- irb (>= 1.3.6)
103
- reline (>= 0.2.7)
101
+ debug (1.7.0)
102
+ irb (>= 1.5.0)
103
+ reline (>= 0.3.1)
104
104
  devise (4.8.1)
105
105
  bcrypt (~> 3.0)
106
106
  orm_adapter (~> 0.1)
107
107
  railties (>= 4.1.0)
108
108
  responders
109
109
  warden (~> 1.2.3)
110
- digest (3.1.0)
111
110
  enumerize (2.5.0)
112
111
  activesupport (>= 3.2)
113
- erubi (1.10.0)
112
+ erubi (1.11.0)
114
113
  ffi (1.15.5)
115
114
  globalid (1.0.0)
116
115
  activesupport (>= 5.0)
@@ -118,20 +117,21 @@ GEM
118
117
  rails (>= 6.0.0)
119
118
  stimulus-rails
120
119
  turbo-rails
121
- i18n (1.10.0)
120
+ i18n (1.12.0)
122
121
  concurrent-ruby (~> 1.0)
123
122
  image_processing (1.12.2)
124
123
  mini_magick (>= 4.9.5, < 5)
125
124
  ruby-vips (>= 2.0.17, < 3)
126
- importmap-rails (1.0.3)
125
+ importmap-rails (1.1.5)
127
126
  actionpack (>= 6.0.0)
128
127
  railties (>= 6.0.0)
129
128
  inline_svg (1.8.0)
130
129
  activesupport (>= 3.0)
131
130
  nokogiri (>= 1.6)
132
131
  io-console (0.5.11)
133
- irb (1.4.1)
132
+ irb (1.5.1)
134
133
  reline (>= 0.3.0)
134
+ json (2.6.3)
135
135
  kaminari (1.2.2)
136
136
  activesupport (>= 4.1.0)
137
137
  kaminari-actionview (= 1.2.2)
@@ -144,74 +144,72 @@ GEM
144
144
  activerecord
145
145
  kaminari-core (= 1.2.2)
146
146
  kaminari-core (1.2.2)
147
- loofah (2.16.0)
147
+ language_server-protocol (3.17.0.2)
148
+ loofah (2.19.0)
148
149
  crass (~> 1.0.2)
149
150
  nokogiri (>= 1.5.9)
150
- mail (2.7.1)
151
+ mail (2.8.0)
151
152
  mini_mime (>= 0.1.1)
153
+ net-imap
154
+ net-pop
155
+ net-smtp
152
156
  marcel (1.0.2)
153
157
  matrix (0.4.2)
154
158
  method_source (1.0.0)
155
159
  mini_magick (4.11.0)
156
160
  mini_mime (1.1.2)
157
- minitest (5.15.0)
158
- minitest-spec-rails (6.1.0)
161
+ minitest (5.16.3)
162
+ minitest-spec-rails (6.2.0)
159
163
  minitest (>= 5.0)
160
164
  railties (>= 4.1)
161
- net-imap (0.2.3)
162
- digest
165
+ net-imap (0.3.1)
163
166
  net-protocol
164
- strscan
165
- net-pop (0.1.1)
166
- digest
167
+ net-pop (0.1.2)
167
168
  net-protocol
169
+ net-protocol (0.2.0)
168
170
  timeout
169
- net-protocol (0.1.3)
170
- timeout
171
- net-smtp (0.3.1)
172
- digest
171
+ net-smtp (0.3.3)
173
172
  net-protocol
174
- timeout
175
173
  nio4r (2.5.8)
176
- nokogiri (1.13.4-arm64-darwin)
174
+ nokogiri (1.13.9-arm64-darwin)
177
175
  racc (~> 1.4)
178
- nokogiri (1.13.4-x86_64-darwin)
176
+ nokogiri (1.13.9-x86_64-darwin)
179
177
  racc (~> 1.4)
180
- nokogiri (1.13.4-x86_64-linux)
178
+ nokogiri (1.13.9-x86_64-linux)
181
179
  racc (~> 1.4)
182
180
  orm_adapter (0.5.0)
183
181
  parallel (1.22.1)
184
- parser (3.1.2.0)
182
+ parser (3.1.3.0)
185
183
  ast (~> 2.4.1)
186
- public_suffix (4.0.7)
187
- puma (5.6.4)
184
+ public_suffix (5.0.0)
185
+ puma (5.6.5)
188
186
  nio4r (~> 2.0)
189
- racc (1.6.0)
190
- rack (2.2.3)
191
- rack-test (1.1.0)
192
- rack (>= 1.0, < 3)
193
- rails (7.0.2.4)
194
- actioncable (= 7.0.2.4)
195
- actionmailbox (= 7.0.2.4)
196
- actionmailer (= 7.0.2.4)
197
- actionpack (= 7.0.2.4)
198
- actiontext (= 7.0.2.4)
199
- actionview (= 7.0.2.4)
200
- activejob (= 7.0.2.4)
201
- activemodel (= 7.0.2.4)
202
- activerecord (= 7.0.2.4)
203
- activestorage (= 7.0.2.4)
204
- activesupport (= 7.0.2.4)
187
+ racc (1.6.1)
188
+ rack (2.2.4)
189
+ rack-test (2.0.2)
190
+ rack (>= 1.3)
191
+ rails (7.0.4)
192
+ actioncable (= 7.0.4)
193
+ actionmailbox (= 7.0.4)
194
+ actionmailer (= 7.0.4)
195
+ actionpack (= 7.0.4)
196
+ actiontext (= 7.0.4)
197
+ actionview (= 7.0.4)
198
+ activejob (= 7.0.4)
199
+ activemodel (= 7.0.4)
200
+ activerecord (= 7.0.4)
201
+ activestorage (= 7.0.4)
202
+ activesupport (= 7.0.4)
205
203
  bundler (>= 1.15.0)
206
- railties (= 7.0.2.4)
204
+ railties (= 7.0.4)
207
205
  rails-dom-testing (2.0.3)
208
206
  activesupport (>= 4.2.0)
209
207
  nokogiri (>= 1.6)
210
- rails-html-sanitizer (1.4.2)
208
+ rails-html-sanitizer (1.4.3)
211
209
  loofah (~> 2.3)
212
- railties (7.0.2.4)
213
- actionpack (= 7.0.2.4)
214
- activesupport (= 7.0.2.4)
210
+ railties (7.0.4)
211
+ actionpack (= 7.0.4)
212
+ activesupport (= 7.0.4)
215
213
  method_source
216
214
  rake (>= 12.2)
217
215
  thor (~> 1.0)
@@ -219,7 +217,7 @@ GEM
219
217
  rainbow (3.1.1)
220
218
  rake (13.0.6)
221
219
  redcarpet (3.5.1)
222
- regexp_parser (2.3.1)
220
+ regexp_parser (2.6.1)
223
221
  reline (0.3.1)
224
222
  io-console (~> 0.5)
225
223
  responders (3.0.1)
@@ -231,18 +229,19 @@ GEM
231
229
  actionpack (>= 5.2, < 7.1)
232
230
  activesupport (>= 5.2, < 7.1)
233
231
  addressable (~> 2.7)
234
- rubocop (1.28.2)
232
+ rubocop (1.39.0)
233
+ json (~> 2.3)
235
234
  parallel (~> 1.10)
236
- parser (>= 3.1.0.0)
235
+ parser (>= 3.1.2.1)
237
236
  rainbow (>= 2.2.2, < 4.0)
238
237
  regexp_parser (>= 1.8, < 3.0)
239
- rexml
240
- rubocop-ast (>= 1.17.0, < 2.0)
238
+ rexml (>= 3.2.5, < 4.0)
239
+ rubocop-ast (>= 1.23.0, < 2.0)
241
240
  ruby-progressbar (~> 1.7)
242
241
  unicode-display_width (>= 1.4.0, < 3.0)
243
- rubocop-ast (1.17.0)
242
+ rubocop-ast (1.24.0)
244
243
  parser (>= 3.1.1.0)
245
- rubocop-performance (1.13.3)
244
+ rubocop-performance (1.15.1)
246
245
  rubocop (>= 1.7.0, < 2.0)
247
246
  rubocop-ast (>= 0.4.0)
248
247
  ruby-progressbar (1.11.0)
@@ -256,29 +255,32 @@ GEM
256
255
  sprockets (> 3.0)
257
256
  sprockets-rails
258
257
  tilt
259
- sprockets (4.0.3)
258
+ sprockets (4.1.1)
260
259
  concurrent-ruby (~> 1.0)
261
260
  rack (> 1, < 3)
262
261
  sprockets-rails (3.4.2)
263
262
  actionpack (>= 5.2)
264
263
  activesupport (>= 5.2)
265
264
  sprockets (>= 3.0.0)
266
- sqlite3 (1.4.2)
267
- standard (1.11.0)
268
- rubocop (= 1.28.2)
269
- rubocop-performance (= 1.13.3)
270
- stimulus-rails (1.0.4)
265
+ sqlite3 (1.5.4-arm64-darwin)
266
+ sqlite3 (1.5.4-x86_64-darwin)
267
+ sqlite3 (1.5.4-x86_64-linux)
268
+ standard (1.19.1)
269
+ language_server-protocol (~> 3.17.0.2)
270
+ rubocop (= 1.39.0)
271
+ rubocop-performance (= 1.15.1)
272
+ stimulus-rails (1.2.1)
271
273
  railties (>= 6.0.0)
272
- strscan (3.0.1)
273
274
  thor (1.2.1)
274
- tilt (2.0.10)
275
- timeout (0.2.0)
276
- turbo-rails (1.0.1)
275
+ tilt (2.0.11)
276
+ timeout (0.3.1)
277
+ turbo-rails (1.3.2)
277
278
  actionpack (>= 6.0.0)
279
+ activejob (>= 6.0.0)
278
280
  railties (>= 6.0.0)
279
- tzinfo (2.0.4)
281
+ tzinfo (2.0.5)
280
282
  concurrent-ruby (~> 1.0)
281
- unicode-display_width (2.1.0)
283
+ unicode-display_width (2.3.0)
282
284
  warden (1.2.9)
283
285
  rack (>= 2.0.9)
284
286
  websocket-driver (0.7.5)
@@ -288,7 +290,7 @@ GEM
288
290
  activerecord (>= 4.2)
289
291
  xpath (3.2.0)
290
292
  nokogiri (~> 1.8)
291
- zeitwerk (2.5.4)
293
+ zeitwerk (2.6.6)
292
294
 
293
295
  PLATFORMS
294
296
  arm64-darwin-21
@@ -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', 'count', 'editButton']
6
+ return ['item', 'template', 'thumbnails', 'modalButton', 'placeholder', 'count', 'editButton', 'validationInput']
7
7
  }
8
8
 
9
9
  connect () {
@@ -17,6 +17,8 @@ export default class extends Controller {
17
17
  if (this.hasSorting()) {
18
18
  this.initSortable()
19
19
  }
20
+
21
+ this.validate()
20
22
  }
21
23
 
22
24
  // Actions
@@ -81,6 +83,41 @@ export default class extends Controller {
81
83
 
82
84
  // Toggle placeholder
83
85
  this.togglePlaceholder()
86
+
87
+ // Validate
88
+ this.validate()
89
+ }
90
+
91
+ validate () {
92
+ this.clearValidation()
93
+ this.validateMinimum()
94
+ this.validateMaximum()
95
+ }
96
+
97
+ clearValidation () {
98
+ this.validationInputTarget.setCustomValidity('')
99
+ }
100
+
101
+ validateMinimum () {
102
+ const count = this.activeItems().length
103
+ if (count < this.minActiveItems()) {
104
+ this.validationInputTarget.setCustomValidity(this.validationInputTarget.dataset.minMessage)
105
+ }
106
+ }
107
+
108
+ validateMaximum () {
109
+ const count = this.activeItems().length
110
+ if (count > this.maxActiveItems()) {
111
+ this.validationInputTarget.setCustomValidity(this.validationInputTarget.dataset.maxMessage)
112
+ }
113
+ }
114
+
115
+ minActiveItems () {
116
+ return parseInt(this.element.dataset.min, 10) || 0
117
+ }
118
+
119
+ maxActiveItems () {
120
+ return parseInt(this.element.dataset.max, 10) || Infinity
84
121
  }
85
122
 
86
123
  resetPositions () {
@@ -1,3 +1,4 @@
1
+ /* global IntersectionObserver */
1
2
  import { Controller } from '@hotwired/stimulus'
2
3
 
3
4
  export default class extends Controller {
@@ -6,7 +7,7 @@ export default class extends Controller {
6
7
  }
7
8
 
8
9
  connect () {
9
- this.update()
10
+ onVisible(this.textareaTarget, () => { this.update() })
10
11
  }
11
12
 
12
13
  update () {
@@ -32,3 +33,16 @@ export default class extends Controller {
32
33
  this.countTarget.textContent = `${currentLength}/${maximumLength}`
33
34
  }
34
35
  }
36
+
37
+ // Custom callback event that triggers when an element becomes visible.
38
+ // Solves the bug where textarea fields are not properly sized when they (or their parent) or hidden.
39
+ function onVisible (element, callback) {
40
+ new IntersectionObserver((entries, observer) => {
41
+ entries.forEach(entry => {
42
+ if (entry.intersectionRatio > 0) {
43
+ callback(element)
44
+ observer.disconnect()
45
+ }
46
+ })
47
+ }).observe(element)
48
+ }
@@ -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", "count", "editButton"];
10099
+ return ["item", "template", "thumbnails", "modalButton", "placeholder", "count", "editButton", "validationInput"];
10100
10100
  }
10101
10101
  connect() {
10102
10102
  document.addEventListener("mediaSelectionSubmitted", (event) => {
@@ -10107,6 +10107,7 @@ var media_controller_default = class extends Controller {
10107
10107
  if (this.hasSorting()) {
10108
10108
  this.initSortable();
10109
10109
  }
10110
+ this.validate();
10110
10111
  }
10111
10112
  destroy(event) {
10112
10113
  const item = event.currentTarget.closest("[data-media-target='item']");
@@ -10147,6 +10148,33 @@ var media_controller_default = class extends Controller {
10147
10148
  this.resetPositions();
10148
10149
  this.syncIds();
10149
10150
  this.togglePlaceholder();
10151
+ this.validate();
10152
+ }
10153
+ validate() {
10154
+ this.clearValidation();
10155
+ this.validateMinimum();
10156
+ this.validateMaximum();
10157
+ }
10158
+ clearValidation() {
10159
+ this.validationInputTarget.setCustomValidity("");
10160
+ }
10161
+ validateMinimum() {
10162
+ const count = this.activeItems().length;
10163
+ if (count < this.minActiveItems()) {
10164
+ this.validationInputTarget.setCustomValidity(this.validationInputTarget.dataset.minMessage);
10165
+ }
10166
+ }
10167
+ validateMaximum() {
10168
+ const count = this.activeItems().length;
10169
+ if (count > this.maxActiveItems()) {
10170
+ this.validationInputTarget.setCustomValidity(this.validationInputTarget.dataset.maxMessage);
10171
+ }
10172
+ }
10173
+ minActiveItems() {
10174
+ return parseInt(this.element.dataset.min, 10) || 0;
10175
+ }
10176
+ maxActiveItems() {
10177
+ return parseInt(this.element.dataset.max, 10) || Infinity;
10150
10178
  }
10151
10179
  resetPositions() {
10152
10180
  this.activeItems().forEach((item, index2) => {
@@ -15825,7 +15853,9 @@ var textarea_controller_default = class extends Controller {
15825
15853
  return ["textarea", "count"];
15826
15854
  }
15827
15855
  connect() {
15828
- this.update();
15856
+ onVisible(this.textareaTarget, () => {
15857
+ this.update();
15858
+ });
15829
15859
  }
15830
15860
  update() {
15831
15861
  this.resize();
@@ -15846,6 +15876,16 @@ var textarea_controller_default = class extends Controller {
15846
15876
  this.countTarget.textContent = `${currentLength}/${maximumLength}`;
15847
15877
  }
15848
15878
  };
15879
+ function onVisible(element, callback) {
15880
+ new IntersectionObserver((entries, observer) => {
15881
+ entries.forEach((entry) => {
15882
+ if (entry.intersectionRatio > 0) {
15883
+ callback(element);
15884
+ observer.disconnect();
15885
+ }
15886
+ });
15887
+ }).observe(element);
15888
+ }
15849
15889
 
15850
15890
  // app/assets/javascripts/headmin/index.js
15851
15891
  var Headmin = class {
@@ -32,7 +32,8 @@
32
32
  }
33
33
 
34
34
  .rx-toolbar-container.is-rx-toolbar {
35
- background: transparent;
35
+ border-top-left-radius: 0.25rem;
36
+ border-top-right-radius: 0.25rem;
36
37
  border-bottom: 1px solid $input-border-color;
37
38
  }
38
39
 
@@ -12936,7 +12936,8 @@ span.flatpickr-weekday {
12936
12936
  background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem);
12937
12937
  }
12938
12938
  .rx-toolbar-container.is-rx-toolbar {
12939
- background: transparent;
12939
+ border-top-left-radius: 0.25rem;
12940
+ border-top-right-radius: 0.25rem;
12940
12941
  border-bottom: 1px solid #ced4da;
12941
12942
  }
12942
12943
  .rx-content {
@@ -21,7 +21,7 @@ module Headmin
21
21
 
22
22
  def per_page
23
23
  length = params[:per_page].to_i
24
- length > 0 ? length : 24
24
+ (length > 0) ? length : 24
25
25
  end
26
26
  end
27
27
  end
@@ -16,7 +16,7 @@ module Headmin
16
16
  end
17
17
 
18
18
  def by_mimetypes_string(mimetype_string)
19
- return self unless mimetype_string.present?
19
+ return where({}) if mimetype_string.blank?
20
20
  by_mimetypes(mimetype_string.split(","))
21
21
  end
22
22
 
@@ -26,7 +26,7 @@ module Headmin
26
26
  mimetypes.map.with_index do |mimetype, index|
27
27
  content_type = mimetype.tr("*", "%")
28
28
  query = where(arel_table[:content_type].matches(content_type))
29
- results = index == 0 ? query : results.or(query)
29
+ results = (index == 0) ? query : results.or(query)
30
30
  end
31
31
 
32
32
  results
@@ -5,6 +5,12 @@ module Headmin
5
5
  included do
6
6
  # Associations
7
7
  belongs_to :blockable, polymorphic: true, optional: true, touch: true
8
+
9
+ # Validations
10
+ validates :handle, uniqueness: {scope: :blockable_id}, allow_blank: true
11
+
12
+ # Scopes
13
+ scope :visible, -> { where("visible = true") }
8
14
  end
9
15
  end
10
16
  end
@@ -88,7 +88,7 @@ module Headmin
88
88
  fieldable: self,
89
89
  name: name,
90
90
  field_type: "list",
91
- fields: array.map { |item| ::Field.new(fieldable: self, name: "item", field_type: "group", fields: fields_for(item)) }
91
+ fields: array.map { |item| ::Field.new(fieldable: self, field_type: :list_item, fields: fields_for(item)) }
92
92
  )
93
93
  end
94
94
 
@@ -143,6 +143,8 @@ module Headmin
143
143
  case field.field_type.to_sym
144
144
  when :group
145
145
  parse_group_field(field, children)
146
+ when :list_item
147
+ parse_list_item_field(field, children)
146
148
  when :list
147
149
  parse_list_field(field, children)
148
150
  when :files
@@ -158,6 +160,10 @@ module Headmin
158
160
  parse_hash_tree(children)
159
161
  end
160
162
 
163
+ def parse_list_item_field(field, children)
164
+ parse_hash_tree(children)
165
+ end
166
+
161
167
  def parse_list_field(field, children)
162
168
  children.map do |child, grand_children|
163
169
  parse_field(child, grand_children)
@@ -165,7 +171,9 @@ module Headmin
165
171
  end
166
172
 
167
173
  def parse_files_field(field)
168
- field.files.all
174
+ files = field.files
175
+ files = files.order(:position) if ActiveStorage::Attachment.column_names.include?("position")
176
+ files
169
177
  end
170
178
 
171
179
  def parse_file_field(field)
@@ -135,7 +135,7 @@ module Headmin
135
135
  string = string.remove("+AND+").remove("+OR+")
136
136
 
137
137
  # Regex: takes out the value before the : character
138
- operator_raw = string.match(/^[^:]*/)[0] == string ? nil : string.match(/^[^:]*/)[0]
138
+ operator_raw = (string.match(/^[^:]*/)[0] == string) ? nil : string.match(/^[^:]*/)[0]
139
139
  value_raw = string.remove("#{operator_raw}:")
140
140
 
141
141
  [conditional_raw, operator_raw, value_raw]
@@ -237,7 +237,7 @@ module Headmin
237
237
 
238
238
  value = if instruction[:operator] == "is_null" || instruction[:operator] == "is_not_null"
239
239
  # In case of special operators (is_null & is_not_null), we intercept the value
240
- value == 1 ? I18n.t("headmin.filters.values.yes") : I18n.t("headmin.filters.values.no")
240
+ (value == 1) ? I18n.t("headmin.filters.values.yes") : I18n.t("headmin.filters.values.no")
241
241
  else
242
242
  display_value(value)
243
243
  end
@@ -15,6 +15,19 @@ module Headmin
15
15
  paths + ["admin/blocks", "blocks", ""]
16
16
  end
17
17
 
18
+ def badge_style(object)
19
+ visible = object.visible?
20
+ errors = object.errors.present?
21
+
22
+ if !visible && !errors
23
+ "bg-light text-secondary"
24
+ elsif errors
25
+ "bg-danger text-white"
26
+ else
27
+ "bg-light text-dark"
28
+ end
29
+ end
30
+
18
31
  private
19
32
 
20
33
  def default_repeater_options
@@ -28,8 +28,7 @@ module Headmin
28
28
 
29
29
  def wrapper_options
30
30
  default_wrapper_options.deep_merge(
31
- {
32
- }
31
+ {}
33
32
  ).deep_merge(@wrapper || {})
34
33
  end
35
34
 
@@ -105,9 +105,9 @@ module Headmin
105
105
 
106
106
  def min
107
107
  if @required
108
- @min.to_i < 1 ? 1 : @min.to_i
108
+ (@min.to_i < 1) ? 1 : @min.to_i
109
109
  else
110
- @min.to_i < 1 ? 0 : @min.to_i
110
+ (@min.to_i < 1) ? 0 : @min.to_i
111
111
  end
112
112
  end
113
113
 
@@ -18,11 +18,8 @@
18
18
  @lookup_context.prefixes = blocks.prefixes + @lookup_context.prefixes
19
19
  %>
20
20
 
21
- <% if blockable && blockable.respond_to?(:blocks) %>
22
- <% blockable.blocks.order(:position).each do |block| %>
23
-
24
- <!-- <%= block.name %> -->
21
+ <% blockable.blocks.visible.order(:position).each do |block| %>
22
+ <div class="block block-<%= block.name.dasherize %>" id="<%= block.handle.present? ? block.handle : "block-#{block.id}" %>">
25
23
  <%= render block.name, block: block %>
26
-
27
- <% end %>
24
+ </div>
28
25
  <% end %>
@@ -24,15 +24,22 @@
24
24
 
25
25
  <%= render "headmin/forms/repeater", blocks.repeater_options do |block_form, template| %>
26
26
  <% name = template || block_form.object.name %>
27
-
28
27
  <!-- Name input of the block -->
29
28
  <%= block_form.hidden_field :name, value: name %>
30
29
 
31
30
  <!-- Render block form fields -->
32
- <%= render name, form: block_form %>
31
+ <div class="<%= block_form.object.visible ? "" : "opacity-50" %>">
32
+ <%= render name, form: block_form %>
33
+ </div>
34
+ <% badge_style = blocks.badge_style(block_form.object) %>
33
35
 
34
36
  <!-- Label -->
35
- <span class="position-absolute top-0 end-0 badge bg-light text-dark">
37
+ <span class="position-absolute top-0 end-0 badge rounded-0 d-flex align-items-center gap-2 bg-danger <%= badge_style %>">
36
38
  <%= t("blocks.#{name}", default: name).humanize %>
39
+ <button type="button" class="btn p-0 <%= badge_style %>" data-bs-toggle="modal" data-bs-target="#modal-block-<%= block_form.object.id %>">
40
+ <%= bootstrap_icon("gear") %>
41
+ </button>
37
42
  </span>
43
+
44
+ <%= render "headmin/forms/blocks/modal", form: block_form, name: name %>
38
45
  <% end %>
@@ -7,11 +7,11 @@
7
7
  #
8
8
  # ==== Optional parameters
9
9
  # * +destroy+ - Adds delete buttons to the preview thumbnails
10
- # * +hint+ - Informative text to assist with data input. HTML markup is allowed.
11
- # * +label+ - Text to display inside label tag. Defaults to the attribute name. Set to false if you don"t want to show a label.
12
- # * +min+ - Limit the selection to a minimum amount of items.
13
- # * +max+ - Limit the selection to a maximum amount of items.
14
- # * +sort+ - Allow sorting by dragging items. `active_storage_attachments` must have a position column.
10
+ # * +hint+ - Informative text to assist with data input. HTML markup is allowed
11
+ # * +label+ - Text to display inside label tag. Defaults to the attribute name. Set to false if you don"t want to show a label
12
+ # * +min+ - Limit the selection to a minimum amount of items
13
+ # * +max+ - Limit the selection to a maximum amount of items
14
+ # * +sort+ - Allow sorting by dragging items. `active_storage_attachments` must have a position column
15
15
  # * +wrapper+ - Hash with all options for the surrounding html tag
16
16
  # * +width+ - Width of the thumbnail
17
17
  # * +height+ - Height of the thumbnail
@@ -31,6 +31,8 @@
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
+
34
36
  <!-- Render previews for attachments -->
35
37
  <%= form.fields_for(media.nested_attribute, media.association_object) do |ff| %>
36
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, mimetype: media.accept)) %>
@@ -45,8 +45,10 @@
45
45
  association_model = object_model.reflect_on_association(attribute).class_name.constantize
46
46
  association_object = association_model.new
47
47
  with_positions = association_object.attributes.keys.include?("position")
48
+
49
+ # We sort the collection with ruby to prevent a new query to be made that would dispose of nested object in memory
48
50
  associations = form.object.send(attribute)
49
- associations = with_positions ? associations.order(:position) : associations
51
+ associations = with_positions ? associations.sort_by{|resource| resource.position} : associations
50
52
  repeater_id = form.object_id
51
53
  pass_thru = template_names.count == 1 ? "[data-template-name=\"#{template_names.first}\"]" : nil
52
54
  show_label = label != false
@@ -0,0 +1,20 @@
1
+ <div class="modal fade" tabindex="-1" id="modal-block-<%= form.object.id %>">
2
+ <div class="modal-dialog modal-lg modal-dialog-scrollable">
3
+ <div class="modal-content">
4
+ <div class="modal-header">
5
+ <h5 class="modal-title">
6
+ <%= t(".title", name: name) %>
7
+ </h5>
8
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="<%= t(".close") %>"></button>
9
+ </div>
10
+ <div class="modal-body">
11
+ <%= render "headmin/forms/text", form: form, attribute: :handle %>
12
+ <%= render "headmin/forms/checkbox", form: form, attribute: :visible %>
13
+ </div>
14
+ <div class="modal-footer">
15
+
16
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><%= t(".close") %></button>
17
+ </div>
18
+ </div>
19
+ </div>
20
+ </div>
@@ -5,6 +5,7 @@
5
5
  # * +form+ - Form object
6
6
  # * +name+ - Name for the field
7
7
  # * +label+ - Text to show as label. Label will be hidden if value is false
8
+ # * +named+ - Whether a group has to be wrapped around the passed block. If false, no group will be wrapped around the passed block.
8
9
  #
9
10
  # ==== Examples
10
11
  # Basic version
@@ -25,21 +26,27 @@
25
26
 
26
27
  label = local_assigns.has_key?(:label) ? label : nil
27
28
  wrapper = local_assigns.has_key?(:wrapper) ? local_assigns[:wrapper] : true
29
+ named = local_assigns.has_key?(:named) ? local_assigns[:named] : true
28
30
  show_label = label != false
29
31
  %>
30
32
 
31
- <%= render "headmin/forms/fields/base", form: form, name: name, field_type: :group do |group, field_label| %>
32
- <% if show_label %>
33
- <%= render "headmin/forms/label", form: form, attribute: :value, text: label || field_label %>
34
- <% end %>
33
+ <% if named %>
34
+ <%= render "headmin/forms/fields/base", form: form, name: name, field_type: :group do |group, field_label| %>
35
+ <% if show_label %>
36
+ <%= render "headmin/forms/label", form: form, attribute: :value, text: label || field_label %>
37
+ <% end %>
35
38
 
36
- <% if wrapper %>
37
- <ul class="list-group mb-3">
38
- <li class="list-group-item">
39
+ <% if wrapper %>
40
+ <ul class="list-group mb-3">
41
+ <li class="list-group-item">
42
+ <%= yield group %>
43
+ </li>
44
+ </ul>
45
+ <% else %>
39
46
  <%= yield group %>
40
- </li>
41
- </ul>
42
- <% else %>
43
- <%= yield group %>
47
+ <% end %>
44
48
  <% end %>
49
+
50
+ <% else %>
51
+ <%= yield form %>
45
52
  <% end %>
@@ -21,7 +21,10 @@
21
21
  <%= render "headmin/forms/fields/base", form: form, name: name, field_type: :list do |list, field_label| %>
22
22
  <div class="mb-3">
23
23
  <%= render "headmin/forms/repeater", form: list, attribute: :fields, label: show_label ? label || field_label : false, flush: false do |field| %>
24
- <%= field.hidden_field :field_type, value: :group %>
24
+
25
+ <%= field.hidden_field :field_type, value: :list_item %>
26
+ <%= field.hidden_field :name, value: :list_item %>
27
+
25
28
  <%= yield field %>
26
29
  <% end %>
27
30
  </div>
@@ -0,0 +1,10 @@
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
+ } %>
@@ -7,10 +7,11 @@
7
7
 
8
8
  draggable = form.object.respond_to?(:position)
9
9
  destroyable = form.object.respond_to?(:destroy)
10
+ error_class = form.object.errors.present? ? "border border-danger" : ""
10
11
  class_names = local_assigns.has_key?(:class) ? local_assigns[:class] : "repeater-row list-group-item"
11
12
  %>
12
13
 
13
- <div class="<%= class_names %>"
14
+ <div class="<%= class_names %> <%= error_class %>"
14
15
  data-repeater-target="row"
15
16
  data-row-index="<%= form.options[:child_index] %>"
16
17
  data-new-record="<%= form.object.new_record? %>"
@@ -10,7 +10,7 @@
10
10
  <div class="text-secondary">
11
11
  Headmin v.<%= Headmin::VERSION %>
12
12
  <span class="float-end">
13
- <%= yield %>
13
+ <%= yield unless content_for(:layout).eql?(yield) %>
14
14
  </span>
15
15
  </div>
16
16
  </div>
@@ -3,8 +3,10 @@ en:
3
3
  created_at: Created at
4
4
  email: Email
5
5
  filename: Filename
6
+ handle: Handle
6
7
  password: Password
7
8
  password_confirmation: Password confirmation
8
9
  remember_me: Stay logged in
9
10
  updated_at: Updated at
10
11
  value: Value
12
+ visible: Visible?
@@ -3,8 +3,11 @@ nl:
3
3
  created_at: Aangemaakt
4
4
  email: E-mailadres
5
5
  filename: Bestandsnaam
6
+ handle: Naam
6
7
  password: Wachtwoord
7
8
  password_confirmation: Wachtwoord bevestiging
8
9
  remember_me: Aangemeld blijven
9
10
  updated_at: Aangepast
10
11
  value: Waarde
12
+ visible: Zichtbaar?
13
+
@@ -18,6 +18,18 @@ en:
18
18
  remove:
19
19
  title: Delete
20
20
  confirm: Are you sure you want to delete this?
21
+ modal:
22
+ close: Close
23
+ title: "Edit block '%{name}'"
24
+ media:
25
+ validation:
26
+ min:
27
+ one: Please select at least 1 item
28
+ other: "Please select at least %{count} items"
29
+ max:
30
+ one: Please limit your selection to maximum 1 item
31
+ other: "Please limit your selection to maximum %{count} items"
32
+
21
33
  select:
22
34
  blank: Make a choice
23
35
  repeater:
@@ -9,6 +9,7 @@ nl:
9
9
  group:
10
10
  save: Opslaan
11
11
  blocks:
12
+ add: "%{name} toevoegen"
12
13
  empty:
13
14
  title: Er zijn nog geen blokken toegevoegd.
14
15
  add: Blok toevoegen
@@ -17,6 +18,18 @@ nl:
17
18
  remove:
18
19
  title: Verwijderen
19
20
  confirm: Ben je zeker dat je dit wil verwijderen?
21
+ modal:
22
+ close: Sluiten
23
+ title: "Wijzig blok '%{name}'"
24
+ media:
25
+ validation:
26
+ min:
27
+ one: Gelieve minstens 1 item te selecteren
28
+ other: "Gelieve minstens %{count} items te selecteren"
29
+ max:
30
+ one: Gelieve maximum 1 item te selecteren
31
+ other: "Gelieve maximum %{count} items te selecteren"
32
+
20
33
  select:
21
34
  blank: Maak een keuze
22
35
  repeater:
@@ -3,7 +3,9 @@ class CreateBlocks < ActiveRecord::Migration[6.1]
3
3
  create_table :blocks do |t|
4
4
  t.references :blockable, polymorphic: true
5
5
  t.string :name
6
+ t.string :handle
6
7
  t.integer :position
8
+ t.boolean :visible, default: true
7
9
  t.timestamps
8
10
  end
9
11
  end
@@ -1,3 +1,3 @@
1
1
  module Headmin
2
- VERSION = "0.5.8"
2
+ VERSION = "0.6.0"
3
3
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "headmin",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "description": "Admin component library",
5
5
  "module": "app/assets/javascripts/headmin.js",
6
6
  "main": "app/assets/javascripts/headmin.js",
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: headmin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.8
4
+ version: 0.6.0
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-29 00:00:00.000000000 Z
11
+ date: 2023-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: closure_tree
@@ -387,6 +387,7 @@ files:
387
387
  - app/views/headmin/forms/_wysiwyg.html.erb
388
388
  - app/views/headmin/forms/autocomplete/_item.html.erb
389
389
  - app/views/headmin/forms/autocomplete/_list.html.erb
390
+ - app/views/headmin/forms/blocks/_modal.html.erb
390
391
  - app/views/headmin/forms/fields/_base.html.erb
391
392
  - app/views/headmin/forms/fields/_file.html.erb
392
393
  - app/views/headmin/forms/fields/_files.html.erb
@@ -394,6 +395,7 @@ files:
394
395
  - app/views/headmin/forms/fields/_list.html.erb
395
396
  - app/views/headmin/forms/fields/_text.html.erb
396
397
  - app/views/headmin/forms/media/_item.html.erb
398
+ - app/views/headmin/forms/media/_validation.html.erb
397
399
  - app/views/headmin/forms/repeater/_row.html.erb
398
400
  - app/views/headmin/heading/_title.html.erb
399
401
  - app/views/headmin/layout/_body.html.erb