headmin 0.5.1 → 0.5.4

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 (136) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2 -2
  3. data/Gemfile +14 -0
  4. data/Gemfile.lock +79 -2
  5. data/app/assets/javascripts/headmin/controllers/media_controller.js +237 -0
  6. data/app/assets/javascripts/headmin/controllers/media_modal_controller.js +110 -0
  7. data/app/assets/javascripts/headmin/controllers/remote_modal_controller.js +9 -0
  8. data/app/assets/javascripts/headmin/controllers/textarea_controller.js +34 -0
  9. data/app/assets/javascripts/headmin/index.js +8 -0
  10. data/app/assets/javascripts/headmin.js +294 -0
  11. data/app/assets/stylesheets/headmin/forms/file.scss +40 -5
  12. data/app/assets/stylesheets/headmin/forms/media.scss +10 -0
  13. data/app/assets/stylesheets/headmin/forms.scss +1 -0
  14. data/app/assets/stylesheets/headmin/layout/sidebar.scss +0 -1
  15. data/app/assets/stylesheets/headmin/media/index.scss +9 -0
  16. data/app/assets/stylesheets/headmin/media.scss +1 -0
  17. data/app/assets/stylesheets/headmin/overrides/redactorx.scss +1 -1
  18. data/app/assets/stylesheets/headmin/table.scss +8 -0
  19. data/app/assets/stylesheets/headmin/vendor/{tom-select-bootstrap.css → tom-select-bootstrap.scss} +0 -1
  20. data/app/assets/stylesheets/headmin.css +61 -8
  21. data/app/assets/stylesheets/headmin.scss +1 -0
  22. data/app/controllers/headmin/media_controller.rb +52 -0
  23. data/app/controllers/headmin_controller.rb +2 -0
  24. data/app/helpers/headmin/form_helper.rb +2 -2
  25. data/app/models/concerns/headmin/field.rb +2 -2
  26. data/app/models/concerns/headmin/fieldable.rb +19 -10
  27. data/app/models/concerns/headmin/form/hintable.rb +6 -1
  28. data/app/models/headmin/filter/association.rb +86 -0
  29. data/app/models/headmin/filter/association_view.rb +74 -0
  30. data/app/models/headmin/filter/base.rb +5 -2
  31. data/app/models/headmin/filter/boolean_view.rb +1 -0
  32. data/app/models/headmin/filter/date.rb +49 -1
  33. data/app/models/headmin/filter/date_view.rb +1 -0
  34. data/app/models/headmin/filter/flatpickr_view.rb +1 -0
  35. data/app/models/headmin/filter/number_view.rb +1 -0
  36. data/app/models/headmin/filter/operator_view.rb +3 -1
  37. data/app/models/headmin/filter/options_view.rb +1 -0
  38. data/app/models/headmin/filter/text_view.rb +1 -0
  39. data/app/models/headmin/form/association_view.rb +102 -0
  40. data/app/models/headmin/form/blocks_view.rb +4 -1
  41. data/app/models/headmin/form/file_view.rb +0 -8
  42. data/app/models/headmin/form/flatpickr_view.rb +2 -1
  43. data/app/models/headmin/form/media_item_view.rb +39 -0
  44. data/app/models/headmin/form/media_view.rb +137 -0
  45. data/app/models/headmin/form/select_view.rb +2 -1
  46. data/app/models/headmin/form/textarea_view.rb +6 -1
  47. data/app/models/headmin/thumbnail_view.rb +40 -19
  48. data/app/models/view_model.rb +4 -0
  49. data/app/views/examples/admin.html.erb +8 -8
  50. data/app/views/examples/auth.html.erb +2 -2
  51. data/app/views/headmin/_breadcrumbs.html.erb +2 -2
  52. data/app/views/headmin/_dropdown.html.erb +1 -1
  53. data/app/views/headmin/_filters.html.erb +12 -7
  54. data/app/views/headmin/_pagination.html.erb +2 -2
  55. data/app/views/headmin/_popup.html.erb +4 -4
  56. data/app/views/headmin/_table.html.erb +1 -1
  57. data/app/views/headmin/_thumbnail.html.erb +33 -9
  58. data/app/views/headmin/dropdown/_button.html.erb +2 -2
  59. data/app/views/headmin/dropdown/_item.html.erb +2 -2
  60. data/app/views/headmin/dropdown/_list.html.erb +3 -3
  61. data/app/views/headmin/dropdown/_locale.html.erb +5 -5
  62. data/app/views/headmin/filters/_association.html.erb +24 -0
  63. data/app/views/headmin/filters/_options.html.erb +1 -1
  64. data/app/views/headmin/filters/filter/_button.html.erb +2 -2
  65. data/app/views/headmin/filters/filter/_null_select.html.erb +2 -2
  66. data/app/views/headmin/forms/_association.html.erb +30 -0
  67. data/app/views/headmin/forms/_errors.html.erb +1 -1
  68. data/app/views/headmin/forms/_file.html.erb +10 -11
  69. data/app/views/headmin/forms/_hint.html.erb +6 -1
  70. data/app/views/headmin/forms/_media.html.erb +60 -0
  71. data/app/views/headmin/forms/_repeater.html.erb +18 -16
  72. data/app/views/headmin/forms/_textarea.html.erb +1 -1
  73. data/app/views/headmin/forms/_wrapper.html.erb +0 -1
  74. data/app/views/headmin/forms/fields/_list.html.erb +6 -4
  75. data/app/views/headmin/forms/media/_item.html.erb +38 -0
  76. data/app/views/headmin/forms/media/_validation.html.erb +10 -0
  77. data/app/views/headmin/forms/repeater/_row.html.erb +17 -15
  78. data/app/views/headmin/heading/_title.html.erb +2 -2
  79. data/app/views/headmin/layout/_main.html.erb +2 -0
  80. data/app/views/headmin/layout/_remote_modal.html.erb +1 -0
  81. data/app/views/headmin/layout/_sidebar.html.erb +1 -1
  82. data/app/views/headmin/media/_item.html.erb +16 -0
  83. data/app/views/headmin/media/_media_item_modal.html.erb +51 -0
  84. data/app/views/headmin/media/_modal.html.erb +35 -0
  85. data/app/views/headmin/media/create.turbo_stream.erb +5 -0
  86. data/app/views/headmin/media/index.html.erb +3 -0
  87. data/app/views/headmin/media/show.html.erb +9 -0
  88. data/app/views/headmin/media/update.turbo_stream.erb +3 -0
  89. data/app/views/headmin/nav/_dropdown.html.erb +7 -7
  90. data/app/views/headmin/nav/_item.html.erb +5 -5
  91. data/app/views/headmin/nav/item/_locale.html.erb +6 -6
  92. data/app/views/headmin/pagination/_per_page.html.erb +7 -7
  93. data/app/views/headmin/pagination/kaminari/_first_page.html.erb +2 -2
  94. data/app/views/headmin/pagination/kaminari/_gap.html.erb +1 -1
  95. data/app/views/headmin/pagination/kaminari/_last_page.html.erb +2 -2
  96. data/app/views/headmin/pagination/kaminari/_next_page.html.erb +3 -3
  97. data/app/views/headmin/pagination/kaminari/_page.html.erb +2 -2
  98. data/app/views/headmin/pagination/kaminari/_paginator.html.erb +1 -1
  99. data/app/views/headmin/pagination/kaminari/_prev_page.html.erb +2 -2
  100. data/app/views/headmin/table/_actions.html.erb +9 -9
  101. data/app/views/headmin/table/_body.html.erb +1 -1
  102. data/app/views/headmin/table/actions/_action.html.erb +4 -4
  103. data/app/views/headmin/table/actions/_export.html.erb +1 -1
  104. data/app/views/headmin/table/body/_association.html.erb +17 -3
  105. data/app/views/headmin/table/body/_boolean.erb +4 -4
  106. data/app/views/headmin/table/body/_date.html.erb +2 -2
  107. data/app/views/headmin/table/body/_image.html.erb +18 -0
  108. data/app/views/headmin/table/body/_string.html.erb +1 -1
  109. data/app/views/headmin/table/head/_cell.html.erb +1 -1
  110. data/app/views/headmin/table/head/cell/_asc.html.erb +2 -2
  111. data/app/views/headmin/table/head/cell/_default.html.erb +1 -1
  112. data/app/views/headmin/table/head/cell/_desc.html.erb +1 -1
  113. data/app/views/headmin/views/devise/confirmations/_new.html.erb +1 -1
  114. data/app/views/headmin/views/devise/passwords/_edit.html.erb +1 -1
  115. data/app/views/headmin/views/devise/passwords/_new.html.erb +1 -1
  116. data/app/views/headmin/views/devise/registrations/_edit.html.erb +5 -5
  117. data/app/views/headmin/views/devise/registrations/_new.html.erb +1 -1
  118. data/app/views/headmin/views/devise/sessions/_new.html.erb +1 -1
  119. data/app/views/headmin/views/devise/shared/_links.html.erb +14 -20
  120. data/app/views/headmin/views/devise/unlocks/_new.html.erb +1 -1
  121. data/config/locales/activerecord/en.yml +1 -0
  122. data/config/locales/activerecord/nl.yml +1 -0
  123. data/config/locales/devise/nl.yml +1 -1
  124. data/config/locales/headmin/filters/en.yml +3 -1
  125. data/config/locales/headmin/filters/nl.yml +2 -0
  126. data/config/locales/headmin/forms/en.yml +8 -0
  127. data/config/locales/headmin/forms/nl.yml +8 -0
  128. data/config/locales/headmin/media/en.yml +23 -0
  129. data/config/locales/headmin/media/nl.yml +23 -0
  130. data/config/locales/headmin/table/en.yml +2 -0
  131. data/config/locales/headmin/table/nl.yml +2 -0
  132. data/config/routes.rb +10 -0
  133. data/lib/generators/templates/views/layouts/auth.html.erb +2 -2
  134. data/lib/headmin/version.rb +1 -1
  135. data/package.json +1 -1
  136. metadata +34 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a80387ee0e1dfc309d65f5e5eb450260dc84fda671904c3886eb50d2087c3eb
4
- data.tar.gz: 98282252c395347dbb1c2490247c323649b4d282d1bc0d7a6bad32c51ec7ebfc
3
+ metadata.gz: c163e16eb068c93256e3a73a0ac3b58f569cc72c36da590f084ecb13873b8866
4
+ data.tar.gz: 9297b7bfd0194980aa843c45e59f9180349bc17e571cfe8f9c75678738de7def
5
5
  SHA512:
6
- metadata.gz: 3c7271cbff3e46599c77506c07bdd0d0fc0d132bf7230202913a394b4b285f599c37866f52d28ff5d79b19ea59c754db7f25fab68bba71795e87d7d42139285a
7
- data.tar.gz: 9900e4731b52d46486ea772433696774856b974d7bd2dc758628ce964c5d990296910c1a59a04990f5e509a5a1e341f3d9848d2043ad2ee2c14029c8096feae9
6
+ metadata.gz: b72bc6a71c864837e78619f8bfc7b0d455d4f57d108790f6d6619fbb3fe2acb882d2be87236b8a2d87204b55995fd4234c8617ee93be5a587b6eafa2616aaac4
7
+ data.tar.gz: 819ec0caa24dbfa82ac02dec7909d6c888550f87c71bf5eadae0b86b03a179c57b599c5a3af3b418e4435dfc569373a1b744ed5621380382bdf7cff4cb6d229d
data/CHANGELOG.md CHANGED
@@ -32,10 +32,10 @@
32
32
  - BREAK: filter concern (controllers) has been renamed to filterable
33
33
  ```erb
34
34
  # Old
35
- <%= render "headmin/forms/date_range", form: form, start_attribute: :start_date, end_attribute: :end_date %>
35
+ <%= render 'headmin/forms/date_range', form: form, start_attribute: :start_date, end_attribute: :end_date %>
36
36
 
37
37
  # New
38
- <%= render "headmin/forms/date_range", form: form, start: {attribute: :start_date}, end: {attribute: :end_date} %>
38
+ <%= render 'headmin/forms/date_range', form: form, start: {attribute: :start_date}, end: {attribute: :end_date} %>
39
39
  ```
40
40
 
41
41
  ## 0.3
data/Gemfile CHANGED
@@ -6,9 +6,23 @@ gemspec
6
6
  gem "capybara", ">= 3.26"
7
7
  gem "minitest", "~> 5.0"
8
8
  gem "minitest-spec-rails", "~> 6.1"
9
+ gem "puma", "~> 5.2"
9
10
  gem "rails", "~> 7.0"
10
11
  gem "rake", "~> 13.0"
11
12
  gem "sprockets-rails", "~> 3.4"
12
13
  gem "sqlite3", "~> 1.4"
13
14
  gem "standard", "~> 1.7"
14
15
  gem "debug"
16
+
17
+ # Dummy app
18
+ gem "devise", "~> 4.8"
19
+ gem "enumerize", "~> 2.3"
20
+ gem "sassc-rails", "~> 2.1"
21
+ gem "image_processing", "~> 1.2"
22
+ gem "inline_svg", "~> 1.7"
23
+ gem "kaminari", "~> 1.2"
24
+ gem "route_translator", "~> 12.0"
25
+ gem "acts_as_list", "~> 1.0"
26
+ gem "breadcrumbs_on_rails", "~> 4.1"
27
+ gem "importmap-rails", "~> 1.0"
28
+ gem "hotwire-rails", "~> 0.1"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- headmin (0.5.1)
4
+ headmin (0.5.3)
5
5
  closure_tree (~> 7.4)
6
6
  inline_svg (~> 1.7)
7
7
  redcarpet (~> 3.5)
@@ -75,9 +75,14 @@ GEM
75
75
  i18n (>= 1.6, < 2)
76
76
  minitest (>= 5.1)
77
77
  tzinfo (~> 2.0)
78
+ acts_as_list (1.0.4)
79
+ activerecord (>= 4.2)
78
80
  addressable (2.8.0)
79
81
  public_suffix (>= 2.0.2, < 5.0)
80
82
  ast (2.4.2)
83
+ bcrypt (3.1.18)
84
+ breadcrumbs_on_rails (4.1.0)
85
+ railties (>= 5.0)
81
86
  builder (3.2.4)
82
87
  capybara (3.36.0)
83
88
  addressable
@@ -96,18 +101,49 @@ GEM
96
101
  debug (1.5.0)
97
102
  irb (>= 1.3.6)
98
103
  reline (>= 0.2.7)
104
+ devise (4.8.1)
105
+ bcrypt (~> 3.0)
106
+ orm_adapter (~> 0.1)
107
+ railties (>= 4.1.0)
108
+ responders
109
+ warden (~> 1.2.3)
99
110
  digest (3.1.0)
111
+ enumerize (2.5.0)
112
+ activesupport (>= 3.2)
100
113
  erubi (1.10.0)
114
+ ffi (1.15.5)
101
115
  globalid (1.0.0)
102
116
  activesupport (>= 5.0)
117
+ hotwire-rails (0.1.3)
118
+ rails (>= 6.0.0)
119
+ stimulus-rails
120
+ turbo-rails
103
121
  i18n (1.10.0)
104
122
  concurrent-ruby (~> 1.0)
123
+ image_processing (1.12.2)
124
+ mini_magick (>= 4.9.5, < 5)
125
+ ruby-vips (>= 2.0.17, < 3)
126
+ importmap-rails (1.0.3)
127
+ actionpack (>= 6.0.0)
128
+ railties (>= 6.0.0)
105
129
  inline_svg (1.8.0)
106
130
  activesupport (>= 3.0)
107
131
  nokogiri (>= 1.6)
108
132
  io-console (0.5.11)
109
133
  irb (1.4.1)
110
134
  reline (>= 0.3.0)
135
+ kaminari (1.2.2)
136
+ activesupport (>= 4.1.0)
137
+ kaminari-actionview (= 1.2.2)
138
+ kaminari-activerecord (= 1.2.2)
139
+ kaminari-core (= 1.2.2)
140
+ kaminari-actionview (1.2.2)
141
+ actionview
142
+ kaminari-core (= 1.2.2)
143
+ kaminari-activerecord (1.2.2)
144
+ activerecord
145
+ kaminari-core (= 1.2.2)
146
+ kaminari-core (1.2.2)
111
147
  loofah (2.16.0)
112
148
  crass (~> 1.0.2)
113
149
  nokogiri (>= 1.5.9)
@@ -116,6 +152,7 @@ GEM
116
152
  marcel (1.0.2)
117
153
  matrix (0.4.2)
118
154
  method_source (1.0.0)
155
+ mini_magick (4.11.0)
119
156
  mini_mime (1.1.2)
120
157
  minitest (5.15.0)
121
158
  minitest-spec-rails (6.1.0)
@@ -142,10 +179,13 @@ GEM
142
179
  racc (~> 1.4)
143
180
  nokogiri (1.13.4-x86_64-linux)
144
181
  racc (~> 1.4)
182
+ orm_adapter (0.5.0)
145
183
  parallel (1.22.1)
146
184
  parser (3.1.2.0)
147
185
  ast (~> 2.4.1)
148
186
  public_suffix (4.0.7)
187
+ puma (5.6.4)
188
+ nio4r (~> 2.0)
149
189
  racc (1.6.0)
150
190
  rack (2.2.3)
151
191
  rack-test (1.1.0)
@@ -182,8 +222,15 @@ GEM
182
222
  regexp_parser (2.3.1)
183
223
  reline (0.3.1)
184
224
  io-console (~> 0.5)
225
+ responders (3.0.1)
226
+ actionpack (>= 5.0)
227
+ railties (>= 5.0)
185
228
  rexml (3.2.5)
186
- rouge (3.28.0)
229
+ rouge (3.29.0)
230
+ route_translator (12.1.0)
231
+ actionpack (>= 5.2, < 7.1)
232
+ activesupport (>= 5.2, < 7.1)
233
+ addressable (~> 2.7)
187
234
  rubocop (1.28.2)
188
235
  parallel (~> 1.10)
189
236
  parser (>= 3.1.0.0)
@@ -199,6 +246,16 @@ GEM
199
246
  rubocop (>= 1.7.0, < 2.0)
200
247
  rubocop-ast (>= 0.4.0)
201
248
  ruby-progressbar (1.11.0)
249
+ ruby-vips (2.1.4)
250
+ ffi (~> 1.12)
251
+ sassc (2.4.0)
252
+ ffi (~> 1.9)
253
+ sassc-rails (2.1.2)
254
+ railties (>= 4.0.0)
255
+ sassc (>= 2.0)
256
+ sprockets (> 3.0)
257
+ sprockets-rails
258
+ tilt
202
259
  sprockets (4.0.3)
203
260
  concurrent-ruby (~> 1.0)
204
261
  rack (> 1, < 3)
@@ -210,12 +267,20 @@ GEM
210
267
  standard (1.11.0)
211
268
  rubocop (= 1.28.2)
212
269
  rubocop-performance (= 1.13.3)
270
+ stimulus-rails (1.0.4)
271
+ railties (>= 6.0.0)
213
272
  strscan (3.0.1)
214
273
  thor (1.2.1)
274
+ tilt (2.0.10)
215
275
  timeout (0.2.0)
276
+ turbo-rails (1.0.1)
277
+ actionpack (>= 6.0.0)
278
+ railties (>= 6.0.0)
216
279
  tzinfo (2.0.4)
217
280
  concurrent-ruby (~> 1.0)
218
281
  unicode-display_width (2.1.0)
282
+ warden (1.2.9)
283
+ rack (>= 2.0.9)
219
284
  websocket-driver (0.7.5)
220
285
  websocket-extensions (>= 0.1.0)
221
286
  websocket-extensions (0.1.5)
@@ -231,13 +296,25 @@ PLATFORMS
231
296
  x86_64-linux
232
297
 
233
298
  DEPENDENCIES
299
+ acts_as_list (~> 1.0)
300
+ breadcrumbs_on_rails (~> 4.1)
234
301
  capybara (>= 3.26)
235
302
  debug
303
+ devise (~> 4.8)
304
+ enumerize (~> 2.3)
236
305
  headmin!
306
+ hotwire-rails (~> 0.1)
307
+ image_processing (~> 1.2)
308
+ importmap-rails (~> 1.0)
309
+ inline_svg (~> 1.7)
310
+ kaminari (~> 1.2)
237
311
  minitest (~> 5.0)
238
312
  minitest-spec-rails (~> 6.1)
313
+ puma (~> 5.2)
239
314
  rails (~> 7.0)
240
315
  rake (~> 13.0)
316
+ route_translator (~> 12.0)
317
+ sassc-rails (~> 2.1)
241
318
  sprockets-rails (~> 3.4)
242
319
  sqlite3 (~> 1.4)
243
320
  standard (~> 1.7)
@@ -0,0 +1,237 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+ import Sortable from 'sortablejs'
3
+
4
+ export default class extends Controller {
5
+ static get targets () {
6
+ return ['item', 'template', 'thumbnails', 'modalButton', 'placeholder', 'validationInput', 'count', 'editButton']
7
+ }
8
+
9
+ connect () {
10
+ document.addEventListener('mediaSelectionSubmitted', (event) => {
11
+ if (event.detail.name === this.element.dataset.name) {
12
+ this.selectItems(event.detail.items)
13
+ }
14
+ })
15
+
16
+ // Init sorting
17
+ if (this.hasSorting()) {
18
+ this.initSortable()
19
+ }
20
+
21
+ this.validate()
22
+ }
23
+
24
+ // Actions
25
+ destroy (event) {
26
+ const item = event.currentTarget.closest('[data-media-target=\'item\']')
27
+ this.destroyItem(item)
28
+ }
29
+
30
+ syncIds () {
31
+ const ids = this.activeIds()
32
+
33
+ this.modalButtonTargets.forEach((button) => {
34
+ const url = new URL(button.getAttribute('href'))
35
+
36
+ // Remove all ids[]
37
+ url.searchParams.delete('ids[]')
38
+
39
+ // Add new ids
40
+ ids.forEach((id) => {
41
+ url.searchParams.append('ids[]', id)
42
+ })
43
+
44
+ button.setAttribute('href', url.toString())
45
+ })
46
+ }
47
+
48
+ // Methods
49
+ initSortable () {
50
+ Sortable.create(this.thumbnailsTarget, {
51
+ handle: '.media-drag-sort-handle',
52
+ onEnd: (event) => {
53
+ this.resetPositions()
54
+ }
55
+ })
56
+ }
57
+
58
+ hasSorting () {
59
+ return this.element.dataset.sort === 'true'
60
+ }
61
+
62
+ destroyItem (item) {
63
+ this.removeItem(item)
64
+ this.postProcess()
65
+ }
66
+
67
+ selectItems (items) {
68
+ // Destroy all deselected items
69
+ this.removeAllItems()
70
+
71
+ // Add all selected items
72
+ this.addItems(items)
73
+
74
+ this.postProcess()
75
+ }
76
+
77
+ postProcess () {
78
+ // Reset positions
79
+ this.resetPositions()
80
+
81
+ // Sync Ids
82
+ this.syncIds()
83
+
84
+ // Toggle placeholder
85
+ 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
+ }
123
+
124
+ resetPositions () {
125
+ this.activeItems().forEach((item, index) => {
126
+ const positionInput = item.querySelector('input[name*=\'position\']')
127
+ if (positionInput) {
128
+ positionInput.value = index
129
+ }
130
+ })
131
+ }
132
+
133
+ addItems (items) {
134
+ items.forEach((item) => this.addItem(item))
135
+ }
136
+
137
+ addItem (item) {
138
+ const currentItem = this.itemByBlobId(item.blobId)
139
+ if (currentItem) {
140
+ // (re-)enable if already exists
141
+ this.enableItem(currentItem)
142
+ } else {
143
+ // Create if item doesn't exist yet
144
+ this.createItem(item)
145
+ }
146
+ }
147
+
148
+ togglePlaceholder () {
149
+ if (this.activeItems().length > 0) {
150
+ this.hidePlaceholder()
151
+ } else {
152
+ this.showPlaceholder()
153
+ }
154
+ }
155
+
156
+ showPlaceholder () {
157
+ this.placeholderTarget.classList.remove('d-none')
158
+ }
159
+
160
+ hidePlaceholder () {
161
+ this.placeholderTarget.classList.add('d-none')
162
+ }
163
+
164
+ enableItem (item) {
165
+ item.querySelector('input[name*=\'_destroy\']').value = false
166
+ item.classList.remove('d-none')
167
+ }
168
+
169
+ createItem (item) {
170
+ // Copy template
171
+ const template = this.templateTarget
172
+ const html = this.randomizeIds(template)
173
+ this.thumbnailsTarget.insertAdjacentHTML('beforeend', html)
174
+
175
+ // Set new values
176
+ const newItem = this.itemTargets.pop()
177
+ newItem.querySelector('input[name*="[blob_id]"]').value = item.blobId
178
+ newItem.querySelector('input[name*="[_destroy]"]').value = false
179
+
180
+ // Update edit button url
181
+ const editButton = newItem.querySelector('[data-media-target="editButton"]')
182
+ editButton.setAttribute('href', editButton.getAttribute('href').replace('$1', item.blobId))
183
+
184
+ // Copy thumbnail
185
+ const oldThumbnail = newItem.querySelector('.h-thumbnail')
186
+ const newThumbnail = item.thumbnail.cloneNode(true)
187
+ oldThumbnail.parentNode.replaceChild(newThumbnail, oldThumbnail)
188
+ }
189
+
190
+ randomizeIds (template) {
191
+ const regex = new RegExp(template.dataset.templateIdRegex, 'g')
192
+ const randomNumber = Math.floor(100000000 + Math.random() * 900000000)
193
+ return template.innerHTML.replace(regex, randomNumber)
194
+ }
195
+
196
+ removeAllItems () {
197
+ this.removeItems(this.itemTargets)
198
+ }
199
+
200
+ removeItems (items) {
201
+ items.forEach((item) => {
202
+ this.removeItem(item)
203
+ })
204
+ }
205
+
206
+ removeItem (item) {
207
+ item.querySelector('input[name*=\'_destroy\']').value = 1
208
+ item.classList.add('d-none')
209
+
210
+ // Reset positions
211
+ this.resetPositions()
212
+
213
+ // Sync ids
214
+ this.syncIds()
215
+
216
+ // Toggle placeholder
217
+ this.togglePlaceholder()
218
+ }
219
+
220
+ itemByBlobId (blobId) {
221
+ return this.itemTargets.find((item) => {
222
+ return item.querySelector('input[name*=\'blob_id\']').value === blobId
223
+ })
224
+ }
225
+
226
+ activeItems () {
227
+ return this.itemTargets.filter((item) => {
228
+ return item.querySelector('input[name$=\'[_destroy]\']').value === 'false'
229
+ })
230
+ }
231
+
232
+ activeIds () {
233
+ return this.activeItems().map((item) => {
234
+ return item.querySelector('input[name$=\'[blob_id]\']').value
235
+ })
236
+ }
237
+ }
@@ -0,0 +1,110 @@
1
+ /* global CustomEvent */
2
+ import { Controller } from '@hotwired/stimulus'
3
+
4
+ export default class extends Controller {
5
+ static get targets () {
6
+ return ['idCheckbox', 'item', 'form', 'selectButton', 'placeholder', 'count']
7
+ }
8
+
9
+ connect () {
10
+ this.validate()
11
+ this.updateCount()
12
+ }
13
+
14
+ // Actions
15
+ select () {
16
+ this.dispatchSelectionEvent()
17
+ }
18
+
19
+ submitForm () {
20
+ this.hidePlaceholder()
21
+ this.triggerFormSubmission()
22
+ }
23
+
24
+ inputChange () {
25
+ this.handleInputChange()
26
+ this.updateCount()
27
+ }
28
+
29
+ // Methods
30
+ hidePlaceholder () {
31
+ this.placeholderTarget.classList.add('d-none')
32
+ }
33
+
34
+ handleInputChange () {
35
+ this.validate()
36
+ }
37
+
38
+ dispatchSelectionEvent () {
39
+ document.dispatchEvent(
40
+ new CustomEvent(
41
+ 'mediaSelectionSubmitted',
42
+ {
43
+ detail: {
44
+ name: this.element.dataset.name,
45
+ items: this.renderItemsForEvent(this.selectedItems())
46
+ }
47
+ }
48
+ )
49
+ )
50
+ }
51
+
52
+ triggerFormSubmission () {
53
+ this.formTarget.requestSubmit()
54
+ }
55
+
56
+ renderItemsForEvent (items) {
57
+ return items.map((item) => this.renderItemForEvent(item))
58
+ }
59
+
60
+ renderItemForEvent (item) {
61
+ return {
62
+ blobId: item.querySelector('input[type="checkbox"]').value,
63
+ thumbnail: item.querySelector('.h-thumbnail')
64
+ }
65
+ }
66
+
67
+ selectedItems () {
68
+ return this.itemTargets.filter((item) => {
69
+ const checkbox = item.querySelector('input[type="checkbox"]')
70
+ return checkbox.checked
71
+ })
72
+ }
73
+
74
+ selectedItemsCount () {
75
+ return this.selectedItems().length
76
+ }
77
+
78
+ minSelectedItems () {
79
+ return parseInt(this.element.dataset.min, 10) || 0
80
+ }
81
+
82
+ maxSelectedItems () {
83
+ return parseInt(this.element.dataset.max, 10) || Infinity
84
+ }
85
+
86
+ validate () {
87
+ if (this.isValid()) {
88
+ this.enableSelectButton()
89
+ } else {
90
+ this.disableSelectButton()
91
+ }
92
+ }
93
+
94
+ enableSelectButton () {
95
+ this.selectButtonTarget.removeAttribute('disabled')
96
+ }
97
+
98
+ disableSelectButton () {
99
+ this.selectButtonTarget.setAttribute('disabled', '')
100
+ }
101
+
102
+ isValid () {
103
+ const count = this.selectedItemsCount()
104
+ return count >= this.minSelectedItems() && count <= this.maxSelectedItems()
105
+ }
106
+
107
+ updateCount () {
108
+ this.countTarget.innerHTML = this.idCheckboxTargets.filter(checkbox => checkbox.checked).length
109
+ }
110
+ }
@@ -0,0 +1,9 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+ import { Modal } from 'bootstrap'
3
+
4
+ export default class extends Controller {
5
+ connect () {
6
+ this.modal = new Modal(this.element)
7
+ this.modal.show()
8
+ }
9
+ }
@@ -0,0 +1,34 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ export default class extends Controller {
4
+ static get targets () {
5
+ return ['textarea', 'count']
6
+ }
7
+
8
+ connect () {
9
+ this.update()
10
+ }
11
+
12
+ update () {
13
+ this.resize()
14
+ this.updateCount()
15
+ }
16
+
17
+ resize () {
18
+ this.textareaTarget.style.height = 'auto'
19
+ this.textareaTarget.setAttribute('style', 'height:' + (this.textareaTarget.scrollHeight) + 'px;overflow-y:hidden;')
20
+ }
21
+
22
+ updateCount () {
23
+ if (this.textareaTarget.getAttribute('maxlength')) {
24
+ this.updateCountLength()
25
+ }
26
+ }
27
+
28
+ updateCountLength () {
29
+ const currentLength = this.textareaTarget.value.length
30
+ const maximumLength = this.textareaTarget.getAttribute('maxlength')
31
+
32
+ this.countTarget.textContent = `${currentLength}/${maximumLength}`
33
+ }
34
+ }
@@ -10,13 +10,17 @@ 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 MediaController from './controllers/media_controller'
14
+ import MediaModalController from './controllers/media_modal_controller'
13
15
  import NotificationController from './controllers/notification_controller'
14
16
  import PopupController from './controllers/popup_controller'
15
17
  import RedactorxController from './controllers/redactorx_controller'
18
+ import RemoteModalController from './controllers/remote_modal_controller'
16
19
  import RepeaterController from './controllers/repeater_controller'
17
20
  import SelectController from './controllers/select_controller'
18
21
  import TableActionsController from './controllers/table_actions_controller'
19
22
  import TableController from './controllers/table_controller'
23
+ import TextareaController from './controllers/textarea_controller'
20
24
 
21
25
  export class Headmin {
22
26
  static start () {
@@ -31,12 +35,16 @@ export class Headmin {
31
35
  Stimulus.register('filters', FiltersController)
32
36
  Stimulus.register('flatpickr', FlatpickrController)
33
37
  Stimulus.register('hello', HelloController)
38
+ Stimulus.register('media', MediaController)
39
+ Stimulus.register('media-modal', MediaModalController)
34
40
  Stimulus.register('notification', NotificationController)
35
41
  Stimulus.register('popup', PopupController)
36
42
  Stimulus.register('redactorx', RedactorxController)
43
+ Stimulus.register('remote-modal', RemoteModalController)
37
44
  Stimulus.register('repeater', RepeaterController)
38
45
  Stimulus.register('select', SelectController)
39
46
  Stimulus.register('table', TableController)
40
47
  Stimulus.register('table-actions', TableActionsController)
48
+ Stimulus.register('textarea', TextareaController)
41
49
  }
42
50
  }