headmin 0.4.0 → 0.5.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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/.lock-487e157d270f3062a98b7b2a012753708-1272821827 +0 -0
  3. data/CHANGELOG.md +16 -2
  4. data/Gemfile.lock +77 -79
  5. data/app/assets/javascripts/headmin/controllers/autocomplete_controller.js +84 -21
  6. data/app/assets/javascripts/headmin/controllers/date_range_controller.js +12 -6
  7. data/app/assets/javascripts/headmin/controllers/filter_controller.js +61 -11
  8. data/app/assets/javascripts/headmin/controllers/filter_row_controller.js +50 -0
  9. data/app/assets/javascripts/headmin/controllers/flatpickr_controller.js +2 -6
  10. data/app/assets/javascripts/headmin/controllers/popup_controller.js +14 -5
  11. data/app/assets/javascripts/headmin/controllers/table_actions_controller.js +16 -21
  12. data/app/assets/javascripts/headmin/index.js +2 -0
  13. data/app/assets/javascripts/headmin.js +186 -56
  14. data/app/assets/stylesheets/headmin/filter.scss +74 -0
  15. data/app/assets/stylesheets/headmin/general.scss +8 -0
  16. data/app/assets/stylesheets/headmin/layout/body.scss +5 -0
  17. data/app/assets/stylesheets/headmin/popup.scss +0 -1
  18. data/app/assets/stylesheets/headmin.css +70 -1
  19. data/app/controllers/concerns/headmin/filterable.rb +27 -0
  20. data/app/models/concerns/headmin/field.rb +4 -2
  21. data/app/models/concerns/headmin/fieldable.rb +138 -44
  22. data/app/models/headmin/filter/base.rb +238 -0
  23. data/app/models/headmin/filter/base_view.rb +64 -0
  24. data/app/models/headmin/filter/boolean.rb +15 -0
  25. data/app/models/headmin/filter/boolean_view.rb +61 -0
  26. data/app/models/headmin/filter/button_view.rb +25 -0
  27. data/app/models/headmin/filter/conditional_view.rb +16 -0
  28. data/app/models/headmin/filter/date.rb +19 -0
  29. data/app/models/headmin/filter/date_view.rb +52 -0
  30. data/app/models/headmin/filter/flatpickr_view.rb +54 -0
  31. data/app/models/headmin/filter/menu_item_view.rb +6 -0
  32. data/app/models/headmin/filter/money.rb +13 -0
  33. data/app/models/headmin/filter/number.rb +27 -0
  34. data/app/models/headmin/filter/number_view.rb +54 -0
  35. data/app/models/headmin/filter/operator_view.rb +30 -0
  36. data/app/models/headmin/filter/options_view.rb +61 -0
  37. data/app/models/headmin/filter/row_view.rb +13 -0
  38. data/app/models/headmin/filter/search.rb +18 -0
  39. data/app/models/headmin/filter/search_view.rb +31 -0
  40. data/app/models/headmin/filter/text.rb +25 -0
  41. data/app/models/headmin/filter/text_view.rb +53 -0
  42. data/app/models/headmin/filters.rb +29 -0
  43. data/app/models/headmin/form/blocks_view.rb +1 -1
  44. data/app/models/headmin/form/checkbox_view.rb +3 -3
  45. data/app/models/headmin/form/date_range_view.rb +2 -2
  46. data/app/models/headmin/form/date_view.rb +5 -5
  47. data/app/models/headmin/form/datetime_range_view.rb +25 -0
  48. data/app/models/headmin/form/datetime_view.rb +45 -0
  49. data/app/models/headmin/form/email_view.rb +7 -7
  50. data/app/models/headmin/form/file_view.rb +6 -6
  51. data/app/models/headmin/form/flatpickr_range_view.rb +11 -22
  52. data/app/models/headmin/form/flatpickr_view.rb +4 -13
  53. data/app/models/headmin/form/input_group_view.rb +1 -1
  54. data/app/models/headmin/form/label_view.rb +1 -1
  55. data/app/models/headmin/form/number_view.rb +5 -5
  56. data/app/models/headmin/form/password_view.rb +5 -5
  57. data/app/models/headmin/form/redactorx_view.rb +2 -2
  58. data/app/models/headmin/form/search_view.rb +7 -7
  59. data/app/models/headmin/form/select_view.rb +6 -6
  60. data/app/models/headmin/form/switch_view.rb +1 -1
  61. data/app/models/headmin/form/text_view.rb +7 -7
  62. data/app/models/headmin/form/textarea_view.rb +5 -5
  63. data/app/models/headmin/form/url_view.rb +7 -7
  64. data/app/models/headmin/form/wrapper_view.rb +1 -1
  65. data/app/models/headmin/form/wysiwyg_view.rb +1 -1
  66. data/app/models/view_model.rb +1 -1
  67. data/app/views/examples/admin.html.erb +13 -13
  68. data/app/views/examples/auth.html.erb +1 -1
  69. data/app/views/headmin/_filters.html.erb +6 -6
  70. data/app/views/headmin/_form.html.erb +2 -2
  71. data/app/views/headmin/_index.html.erb +1 -1
  72. data/app/views/headmin/_pagination.html.erb +1 -1
  73. data/app/views/headmin/_popup.html.erb +2 -2
  74. data/app/views/headmin/_table.html.erb +1 -1
  75. data/app/views/headmin/dropdown/_devise.html.erb +8 -8
  76. data/app/views/headmin/dropdown/_locale.html.erb +4 -4
  77. data/app/views/headmin/filters/_base.html.erb +95 -0
  78. data/app/views/headmin/filters/_boolean.html.erb +23 -0
  79. data/app/views/headmin/filters/_date.html.erb +14 -38
  80. data/app/views/headmin/filters/_flatpickr.html.erb +15 -48
  81. data/app/views/headmin/filters/_number.html.erb +23 -0
  82. data/app/views/headmin/filters/_options.html.erb +24 -0
  83. data/app/views/headmin/filters/_search.html.erb +14 -12
  84. data/app/views/headmin/filters/_text.html.erb +23 -0
  85. data/app/views/headmin/filters/filter/_button.html.erb +9 -10
  86. data/app/views/headmin/filters/filter/_conditional.html.erb +18 -0
  87. data/app/views/headmin/filters/filter/_menu_item.html.erb +5 -2
  88. data/app/views/headmin/filters/filter/_null_select.html.erb +8 -0
  89. data/app/views/headmin/filters/filter/_operator.html.erb +16 -0
  90. data/app/views/headmin/filters/filter/_row.html.erb +11 -0
  91. data/app/views/headmin/forms/_blocks.html.erb +1 -1
  92. data/app/views/headmin/forms/_date_range.html.erb +3 -3
  93. data/app/views/headmin/forms/_datetime.html.erb +41 -0
  94. data/app/views/headmin/forms/_datetime_range.html.erb +40 -0
  95. data/app/views/headmin/forms/_file.html.erb +3 -3
  96. data/app/views/headmin/forms/_flatpickr.html.erb +1 -1
  97. data/app/views/headmin/forms/_flatpickr_range.html.erb +3 -4
  98. data/app/views/headmin/forms/_label.html.erb +1 -1
  99. data/app/views/headmin/forms/_repeater.html.erb +12 -12
  100. data/app/views/headmin/forms/fields/_base.html.erb +1 -1
  101. data/app/views/headmin/forms/fields/_file.html.erb +3 -3
  102. data/app/views/headmin/forms/fields/_files.html.erb +17 -0
  103. data/app/views/headmin/forms/fields/_group.html.erb +10 -5
  104. data/app/views/headmin/forms/fields/_list.html.erb +4 -4
  105. data/app/views/headmin/forms/fields/_text.html.erb +2 -2
  106. data/app/views/headmin/layout/_footer.html.erb +1 -1
  107. data/app/views/headmin/layout/_main.html.erb +1 -1
  108. data/app/views/headmin/nav/_dropdown.html.erb +3 -3
  109. data/app/views/headmin/nav/_item.html.erb +2 -2
  110. data/app/views/headmin/nav/item/_devise.html.erb +8 -8
  111. data/app/views/headmin/nav/item/_locale.html.erb +4 -4
  112. data/app/views/headmin/table/_actions.html.erb +3 -6
  113. data/app/views/headmin/table/actions/_export.html.erb +1 -1
  114. data/app/views/headmin/table/body/_row.html.erb +3 -3
  115. data/app/views/headmin/table/foot/_id.html.erb +1 -1
  116. data/app/views/headmin/views/devise/confirmations/_new.html.erb +1 -1
  117. data/app/views/headmin/views/devise/passwords/_edit.html.erb +2 -2
  118. data/app/views/headmin/views/devise/passwords/_new.html.erb +1 -1
  119. data/app/views/headmin/views/devise/registrations/_edit.html.erb +4 -4
  120. data/app/views/headmin/views/devise/registrations/_new.html.erb +3 -3
  121. data/app/views/headmin/views/devise/sessions/_new.html.erb +3 -3
  122. data/app/views/headmin/views/devise/unlocks/_new.html.erb +1 -1
  123. data/config/locales/en.yml +4 -0
  124. data/config/locales/headmin/dropdown/en.yml +6 -0
  125. data/config/locales/headmin/dropdown/nl.yml +6 -0
  126. data/config/locales/headmin/filters/en.yml +26 -1
  127. data/config/locales/headmin/filters/nl.yml +26 -1
  128. data/config/locales/headmin/forms/en.yml +1 -1
  129. data/config/locales/headmin/forms/nl.yml +1 -1
  130. data/config/locales/headmin/layout/en.yml +0 -9
  131. data/config/locales/headmin/layout/nl.yml +0 -9
  132. data/config/locales/headmin/nav/en.yml +7 -0
  133. data/config/locales/headmin/nav/nl.yml +7 -0
  134. data/config/locales/nl.yml +4 -0
  135. data/lib/generators/templates/views/layouts/auth.html.erb +1 -1
  136. data/lib/headmin/version.rb +1 -1
  137. data/package.json +1 -1
  138. metadata +44 -7
  139. data/app/controllers/concerns/headmin/filter.rb +0 -5
  140. data/app/controllers/concerns/headmin/searchable.rb +0 -15
  141. data/app/views/headmin/filters/_select.html.erb +0 -45
  142. data/app/views/headmin/filters/filter/_template.html.erb +0 -13
  143. data/app/views/headmin/forms/fields/_image.html.erb +0 -17
@@ -4,17 +4,13 @@ import { Dutch } from 'flatpickr/dist/esm/l10n/nl.js'
4
4
  import I18n from '../config/i18n'
5
5
 
6
6
  export default class extends Controller {
7
- static get targets () {
8
- return ['input']
9
- }
10
-
11
7
  connect () {
12
8
  const options = { ...this.defaultOptions(), ...this.options() }
13
- flatpickr(this.inputTarget, options)
9
+ flatpickr(this.element, options)
14
10
  }
15
11
 
16
12
  options () {
17
- return JSON.parse(this.inputTarget.getAttribute('data-flatpickr'))
13
+ return JSON.parse(this.element.getAttribute('data-flatpickr'))
18
14
  }
19
15
 
20
16
  defaultOptions () {
@@ -1,3 +1,4 @@
1
+ /* global HTMLInputElement */
1
2
  import { Controller } from '@hotwired/stimulus'
2
3
  import { createPopper } from '@popperjs/core'
3
4
 
@@ -18,6 +19,9 @@ export default class extends Controller {
18
19
  }
19
20
 
20
21
  handleOutsideClick (event) {
22
+ const itemRemoved = !document.body.contains(event.target) // Ignore items that were removed from DOM (else this triggers a close)
23
+ if (itemRemoved) return
24
+
21
25
  const inPopup = event.target.closest('[data-popup-target="popup"]') !== null
22
26
  const inButton = event.target.closest('[data-popup-target="button"]') !== null
23
27
  const openPopup = document.querySelector('[data-popup-target="popup"]:not(.closed)')
@@ -32,21 +36,26 @@ export default class extends Controller {
32
36
  const popup = this.popupById(button.dataset.popupId)
33
37
  const passThru = button.dataset.popupPassThru
34
38
 
39
+ // Open the popup
40
+ createPopper(button, popup)
41
+ this.openPopup(popup)
42
+
35
43
  if (passThru) {
36
44
  // Pass click event to an element inside the popup
37
45
  const passThruElement = popup.querySelector(passThru)
38
46
  passThruElement.click()
39
- } else {
40
- // Open the popup
41
- createPopper(button, popup)
42
- this.openPopup(popup)
47
+
48
+ // Focus and select value if it's an input element
49
+ if (passThruElement instanceof HTMLInputElement) {
50
+ passThruElement.focus()
51
+ passThruElement.select()
52
+ }
43
53
  }
44
54
  }
45
55
 
46
56
  close (event) {
47
57
  const button = event.target.closest('[data-popup-target="button"]')
48
58
  const popup = this.popupById(button.dataset.popupId)
49
-
50
59
  this.closePopup(popup)
51
60
  }
52
61
 
@@ -2,12 +2,12 @@ import { Controller } from '@hotwired/stimulus'
2
2
 
3
3
  export default class extends Controller {
4
4
  static get targets () {
5
- return ['wrapper', 'form', 'select', 'method', 'button', 'idInputTemplate', 'id', 'counter']
5
+ return ['wrapper', 'form', 'select', 'method', 'button', 'idInput', 'counter']
6
6
  }
7
7
 
8
8
  connect () {
9
9
  this.wrapperTarget.addEventListener('idSelectionChanged', (event) => {
10
- this.updateIdFields(event.detail.ids)
10
+ this.updateIdInput(event.detail.ids)
11
11
  this.updateCounter(event.detail.count)
12
12
  this.toggleCounter(event.detail.count)
13
13
  })
@@ -21,17 +21,24 @@ export default class extends Controller {
21
21
  this.enableButton()
22
22
  }
23
23
 
24
- updateIdFields (ids) {
25
- this.removeIds()
26
- if (ids instanceof Array) {
27
- ids.forEach((id) => {
28
- this.addId(id)
29
- })
24
+ updateIdInput (ids) {
25
+ if (ids == null) {
26
+ this.disableIdInput()
27
+ this.idInputTarget.value = ''
30
28
  } else {
31
- this.addId('')
29
+ this.enableIdInput()
30
+ this.idInputTarget.value = `in:${ids.join(',')}`
32
31
  }
33
32
  }
34
33
 
34
+ disableIdInput () {
35
+ this.idInputTarget.removeAttribute('name')
36
+ }
37
+
38
+ enableIdInput () {
39
+ this.idInputTarget.setAttribute('name', 'id')
40
+ }
41
+
35
42
  updateCounter (count) {
36
43
  let htmlString = ''
37
44
  switch (count) {
@@ -94,16 +101,4 @@ export default class extends Controller {
94
101
  enableButton () {
95
102
  this.buttonTarget.removeAttribute('disabled')
96
103
  }
97
-
98
- addId (id) {
99
- const template = this.idInputTemplateTarget
100
- const input = template.innerHTML.replace(/ID/g, id)
101
- this.formTarget.insertAdjacentHTML('afterbegin', input)
102
- }
103
-
104
- removeIds () {
105
- this.idTargets.forEach((input) => {
106
- this.formTarget.removeChild(input)
107
- })
108
- }
109
104
  }
@@ -6,6 +6,7 @@ import DateRangeController from './controllers/date_range_controller'
6
6
  import DropzoneController from './controllers/dropzone_controller'
7
7
  import FilePreviewController from './controllers/file_preview_controller'
8
8
  import FilterController from './controllers/filter_controller'
9
+ import FilterRowController from './controllers/filter_row_controller'
9
10
  import FiltersController from './controllers/filters_controller'
10
11
  import FlatpickrController from './controllers/flatpickr_controller'
11
12
  import HelloController from './controllers/hello_controller'
@@ -26,6 +27,7 @@ export class Headmin {
26
27
  Stimulus.register('dropzone', DropzoneController)
27
28
  Stimulus.register('file-preview', FilePreviewController)
28
29
  Stimulus.register('filter', FilterController)
30
+ Stimulus.register('filter-row', FilterRowController)
29
31
  Stimulus.register('filters', FiltersController)
30
32
  Stimulus.register('flatpickr', FlatpickrController)
31
33
  Stimulus.register('hello', HelloController)
@@ -4979,28 +4979,42 @@ var autocomplete_controller_default = class extends Controller {
4979
4979
  this.inputTarget.addEventListener("keydown", (event) => {
4980
4980
  this.handleKeydown(event);
4981
4981
  });
4982
+ this.inputTarget.addEventListener("keyup", (event) => {
4983
+ this.handleKeyup(event);
4984
+ });
4982
4985
  document.addEventListener("click", (event) => {
4983
4986
  this.handleOutsideClick(event);
4984
4987
  });
4985
4988
  }
4986
4989
  handleKeydown(event) {
4987
- this.show();
4988
4990
  const keyCode = parseInt(event.keyCode, 10);
4989
4991
  if (this.isArrowKey(keyCode)) {
4990
4992
  this.handleArrowKey(keyCode);
4991
- } else if (this.isEnterKey(keyCode)) {
4992
- this.selectActiveItem();
4993
- } else {
4994
- this.handleTextKey();
4995
4993
  }
4994
+ if (this.isEnterKey(keyCode)) {
4995
+ this.selectActiveItem(event);
4996
+ }
4997
+ }
4998
+ handleKeyup(event) {
4999
+ const keyCode = parseInt(event.keyCode, 10);
5000
+ if (this.isArrowKey(keyCode) || this.isEnterKey(keyCode)) {
5001
+ return false;
5002
+ }
5003
+ this.handleTextKey();
4996
5004
  }
4997
- selectActiveItem() {
4998
- this.activeItem().click();
5005
+ selectActiveItem(event) {
5006
+ const activeItem = this.activeItem();
5007
+ if (activeItem) {
5008
+ this.deselectAll();
5009
+ this.setValue(activeItem.getAttribute("value"));
5010
+ event.preventDefault();
5011
+ }
4999
5012
  }
5000
5013
  isEnterKey(keyCode) {
5001
5014
  return keyCode === 13;
5002
5015
  }
5003
5016
  handleArrowKey(keyCode) {
5017
+ this.show();
5004
5018
  switch (keyCode) {
5005
5019
  case 38:
5006
5020
  this.handleArrowUp();
@@ -5019,8 +5033,10 @@ var autocomplete_controller_default = class extends Controller {
5019
5033
  }
5020
5034
  selectNextItem() {
5021
5035
  const next = this.nextItem();
5022
- this.deselectAll();
5023
- next.classList.add("active");
5036
+ if (next) {
5037
+ this.deselectAll();
5038
+ next.classList.add("active");
5039
+ }
5024
5040
  }
5025
5041
  nextItem() {
5026
5042
  const current = this.activeItem();
@@ -5036,8 +5052,10 @@ var autocomplete_controller_default = class extends Controller {
5036
5052
  }
5037
5053
  selectPreviousItem() {
5038
5054
  const previous = this.previousItem();
5039
- this.deselectAll();
5040
- previous.classList.add("active");
5055
+ if (previous) {
5056
+ this.deselectAll();
5057
+ previous.classList.add("active");
5058
+ }
5041
5059
  }
5042
5060
  previousItem() {
5043
5061
  const current = this.activeItem();
@@ -5087,8 +5105,16 @@ var autocomplete_controller_default = class extends Controller {
5087
5105
  this.renderCollection(html);
5088
5106
  }).then(() => {
5089
5107
  this.highlight();
5108
+ }).then(() => {
5109
+ this.activateFirstItem();
5110
+ }).then(() => {
5111
+ this.show();
5090
5112
  });
5091
5113
  }
5114
+ activateFirstItem() {
5115
+ this.deselectAll();
5116
+ this.firstItem().classList.add("active");
5117
+ }
5092
5118
  show() {
5093
5119
  if (this.isDropdownEmpty()) {
5094
5120
  this.dropdownTarget.classList.remove("d-none");
@@ -5108,7 +5134,7 @@ var autocomplete_controller_default = class extends Controller {
5108
5134
  }
5109
5135
  fetchCollection() {
5110
5136
  if (this.isRemote()) {
5111
- return fetch(this.urlValue).then((response) => {
5137
+ return fetch(this.remoteURL()).then((response) => {
5112
5138
  return response.text();
5113
5139
  }).catch((error2) => {
5114
5140
  console.error("The URL you provided for the autocomplete collection didn't return a successful result", error2);
@@ -5117,6 +5143,20 @@ var autocomplete_controller_default = class extends Controller {
5117
5143
  return Promise.resolve(this.dropdownTarget.innerHTML);
5118
5144
  }
5119
5145
  }
5146
+ remoteURL() {
5147
+ const base = "https://example.com";
5148
+ const url = new URL(this.urlValue, base);
5149
+ const params = new URLSearchParams(url.search);
5150
+ params.set("search", this.value());
5151
+ params.set("page", "1");
5152
+ params.set("per_page", "6");
5153
+ url.search = params.toString();
5154
+ let urlString = url.toString();
5155
+ if (urlString.includes(base)) {
5156
+ urlString = urlString.replace(base, "");
5157
+ }
5158
+ return urlString;
5159
+ }
5120
5160
  renderCollection(html) {
5121
5161
  this.dropdownTarget.innerHTML = html;
5122
5162
  }
@@ -5128,13 +5168,19 @@ var autocomplete_controller_default = class extends Controller {
5128
5168
  this.dropdownItemTargets.forEach((dropdownItem) => {
5129
5169
  let text = dropdownItem.innerHTML;
5130
5170
  text = text.replace(/<mark.*?>(.*?)<\/mark>/ig, "$1");
5131
- const regex2 = new RegExp(`(${query})`, "gi");
5132
- text = text.replace(regex2, "<mark>$1</mark>");
5171
+ if (query && query.length > 0) {
5172
+ const regex2 = new RegExp(`(?<!<[^>]*?)(&nbsp;)?(${query})`, "gi");
5173
+ text = text.replace(regex2, "<mark>$2</mark>");
5174
+ }
5133
5175
  dropdownItem.innerHTML = text;
5134
5176
  });
5135
5177
  }
5136
5178
  select(event) {
5137
- this.inputTarget.value = event.target.getAttribute("value");
5179
+ this.setValue(event.currentTarget.getAttribute("value"));
5180
+ }
5181
+ setValue(value) {
5182
+ this.inputTarget.value = value;
5183
+ this.inputTarget.dispatchEvent(new Event("change"));
5138
5184
  this.hide();
5139
5185
  }
5140
5186
  value() {
@@ -7411,9 +7457,6 @@ var blocks_controller_default = class extends Controller {
7411
7457
 
7412
7458
  // app/assets/javascripts/headmin/controllers/date_range_controller.js
7413
7459
  var date_range_controller_default = class extends Controller {
7414
- static get targets() {
7415
- return ["dateInput", "startDateInput", "endDateInput"];
7416
- }
7417
7460
  update(event) {
7418
7461
  const flatpickr2 = event.target._flatpickr;
7419
7462
  const startDate = flatpickr2.selectedDates[0];
@@ -7422,10 +7465,18 @@ var date_range_controller_default = class extends Controller {
7422
7465
  this.setEndDateInputValue(this.formatDate(endDate));
7423
7466
  }
7424
7467
  setStartDateInputValue(value) {
7425
- this.startDateInputTarget.value = value;
7468
+ const startDateInput = this.startDateInput();
7469
+ startDateInput.value = value;
7426
7470
  }
7427
7471
  setEndDateInputValue(value) {
7428
- this.endDateInputTarget.value = value;
7472
+ const endDateInput = this.endDateInput();
7473
+ endDateInput.value = value;
7474
+ }
7475
+ startDateInput() {
7476
+ return this.element.nextElementSibling;
7477
+ }
7478
+ endDateInput() {
7479
+ return this.startDateInput().nextElementSibling;
7429
7480
  }
7430
7481
  formatDate(date) {
7431
7482
  if (date instanceof Date) {
@@ -7658,18 +7709,16 @@ var file_preview_controller_default = class extends Controller {
7658
7709
  // app/assets/javascripts/headmin/controllers/filter_controller.js
7659
7710
  var filter_controller_default = class extends Controller {
7660
7711
  static get targets() {
7661
- return ["button", "popup"];
7712
+ return ["button", "popup", "conditional", "operator", "value", "hidden", "wrapper", "template", "row"];
7713
+ }
7714
+ static get values() {
7715
+ return {
7716
+ name: String
7717
+ };
7662
7718
  }
7663
7719
  connect() {
7664
7720
  this.element.controller = this;
7665
- document.addEventListener("click", (event) => {
7666
- this.handleOutsideClick(event);
7667
- });
7668
- }
7669
- handleOutsideClick(event) {
7670
- if (!this.isClickedInside(event)) {
7671
- this.close();
7672
- }
7721
+ this.updateHiddenValue();
7673
7722
  }
7674
7723
  toggle(event) {
7675
7724
  const expanded = this.buttonTarget.getAttribute("aria-expanded") === "true";
@@ -7687,6 +7736,29 @@ var filter_controller_default = class extends Controller {
7687
7736
  this.buttonTarget.setAttribute("aria-expanded", "false");
7688
7737
  this.popupTarget.classList.add("closed");
7689
7738
  }
7739
+ add(event) {
7740
+ event.preventDefault();
7741
+ const html = this.getTemplateHTML();
7742
+ this.wrapperTarget.insertAdjacentHTML("beforeend", html);
7743
+ }
7744
+ remove(event) {
7745
+ event.preventDefault();
7746
+ const inputGroup = event.currentTarget.closest('[data-filter-target="row"]');
7747
+ const conditional = inputGroup.previousElementSibling != null ? inputGroup.previousElementSibling : inputGroup.nextElementSibling;
7748
+ inputGroup.remove();
7749
+ if (conditional != null)
7750
+ conditional.remove();
7751
+ this.updateHiddenValue();
7752
+ if (this.valueTargets.length === 0) {
7753
+ this.removeFilter();
7754
+ }
7755
+ }
7756
+ removeFilter() {
7757
+ const form = this.buttonTarget.closest("form");
7758
+ this.buttonTarget.remove();
7759
+ this.popupTarget.remove();
7760
+ form.submit();
7761
+ }
7690
7762
  isClickedInside(event) {
7691
7763
  if (!event) {
7692
7764
  return false;
@@ -7696,6 +7768,65 @@ var filter_controller_default = class extends Controller {
7696
7768
  const inAddButton = event.target.dataset.action === "click->filters#add";
7697
7769
  return inPopup || inButton || inAddButton;
7698
7770
  }
7771
+ updateHiddenValue() {
7772
+ this.hiddenTarget.value = this.buildInstructionString();
7773
+ }
7774
+ buildInstructionString() {
7775
+ let string = "";
7776
+ for (const row of this.rowTargets) {
7777
+ const conditional = row.previousElementSibling ? row.previousElementSibling.querySelector('[data-filter-target="conditional"]').value : null;
7778
+ const operator = row.querySelector('[data-filter-target="operator"]').value;
7779
+ const value = row.querySelector('[data-filter-target="value"]').value;
7780
+ string += `${conditional || ""}${operator}:${value}`;
7781
+ }
7782
+ return string;
7783
+ }
7784
+ getTemplateHTML() {
7785
+ const template = this.templateTarget;
7786
+ return template.innerHTML;
7787
+ }
7788
+ };
7789
+
7790
+ // app/assets/javascripts/headmin/controllers/filter_row_controller.js
7791
+ var filter_row_controller_default = class extends Controller {
7792
+ static get targets() {
7793
+ return ["original", "operator", "null"];
7794
+ }
7795
+ connect() {
7796
+ this.operatorTarget.addEventListener("change", () => this.handleOperatorChange());
7797
+ this.handleOperatorChange();
7798
+ }
7799
+ handleOperatorChange() {
7800
+ if (this.operatorTarget.value === "is_null" || this.operatorTarget.value === "is_not_null") {
7801
+ this.toggleNullInput();
7802
+ } else {
7803
+ this.toggleOriginalInput();
7804
+ }
7805
+ }
7806
+ toggleNullInput() {
7807
+ this.hideOriginal();
7808
+ this.showNull();
7809
+ }
7810
+ toggleOriginalInput() {
7811
+ this.showOriginal();
7812
+ this.hideNull();
7813
+ }
7814
+ hideOriginal() {
7815
+ this.originalTarget.style.display = "none";
7816
+ this.originalTarget.setAttribute("data-filter-target", "value_original");
7817
+ }
7818
+ showOriginal() {
7819
+ this.originalTarget.style.display = "block";
7820
+ this.originalTarget.setAttribute("data-filter-target", "value");
7821
+ }
7822
+ hideNull() {
7823
+ this.nullTarget.style.display = "none";
7824
+ this.nullTarget.setAttribute("data-filter-target", "value_null");
7825
+ }
7826
+ showNull() {
7827
+ this.nullTarget.style.display = "block";
7828
+ this.nullTarget.setAttribute("data-filter-target", "value");
7829
+ }
7699
7830
  };
7700
7831
 
7701
7832
  // app/assets/javascripts/headmin/controllers/filters_controller.js
@@ -9929,15 +10060,12 @@ var i18n_default = class {
9929
10060
 
9930
10061
  // app/assets/javascripts/headmin/controllers/flatpickr_controller.js
9931
10062
  var flatpickr_controller_default = class extends Controller {
9932
- static get targets() {
9933
- return ["input"];
9934
- }
9935
10063
  connect() {
9936
10064
  const options = { ...this.defaultOptions(), ...this.options() };
9937
- esm_default(this.inputTarget, options);
10065
+ esm_default(this.element, options);
9938
10066
  }
9939
10067
  options() {
9940
- return JSON.parse(this.inputTarget.getAttribute("data-flatpickr"));
10068
+ return JSON.parse(this.element.getAttribute("data-flatpickr"));
9941
10069
  }
9942
10070
  defaultOptions() {
9943
10071
  return {
@@ -15035,6 +15163,9 @@ var popup_controller_default = class extends Controller {
15035
15163
  });
15036
15164
  }
15037
15165
  handleOutsideClick(event) {
15166
+ const itemRemoved = !document.body.contains(event.target);
15167
+ if (itemRemoved)
15168
+ return;
15038
15169
  const inPopup = event.target.closest('[data-popup-target="popup"]') !== null;
15039
15170
  const inButton = event.target.closest('[data-popup-target="button"]') !== null;
15040
15171
  const openPopup = document.querySelector('[data-popup-target="popup"]:not(.closed)');
@@ -15046,12 +15177,15 @@ var popup_controller_default = class extends Controller {
15046
15177
  const button = event.target.closest('[data-popup-target="button"]');
15047
15178
  const popup = this.popupById(button.dataset.popupId);
15048
15179
  const passThru = button.dataset.popupPassThru;
15180
+ createPopper3(button, popup);
15181
+ this.openPopup(popup);
15049
15182
  if (passThru) {
15050
15183
  const passThruElement = popup.querySelector(passThru);
15051
15184
  passThruElement.click();
15052
- } else {
15053
- createPopper3(button, popup);
15054
- this.openPopup(popup);
15185
+ if (passThruElement instanceof HTMLInputElement) {
15186
+ passThruElement.focus();
15187
+ passThruElement.select();
15188
+ }
15055
15189
  }
15056
15190
  }
15057
15191
  close(event) {
@@ -15256,11 +15390,11 @@ var select_controller_default = class extends Controller {
15256
15390
  // app/assets/javascripts/headmin/controllers/table_actions_controller.js
15257
15391
  var table_actions_controller_default = class extends Controller {
15258
15392
  static get targets() {
15259
- return ["wrapper", "form", "select", "method", "button", "idInputTemplate", "id", "counter"];
15393
+ return ["wrapper", "form", "select", "method", "button", "idInput", "counter"];
15260
15394
  }
15261
15395
  connect() {
15262
15396
  this.wrapperTarget.addEventListener("idSelectionChanged", (event) => {
15263
- this.updateIdFields(event.detail.ids);
15397
+ this.updateIdInput(event.detail.ids);
15264
15398
  this.updateCounter(event.detail.count);
15265
15399
  this.toggleCounter(event.detail.count);
15266
15400
  });
@@ -15272,16 +15406,21 @@ var table_actions_controller_default = class extends Controller {
15272
15406
  this.updateFormDataAttributes();
15273
15407
  this.enableButton();
15274
15408
  }
15275
- updateIdFields(ids) {
15276
- this.removeIds();
15277
- if (ids instanceof Array) {
15278
- ids.forEach((id) => {
15279
- this.addId(id);
15280
- });
15409
+ updateIdInput(ids) {
15410
+ if (ids == null) {
15411
+ this.disableIdInput();
15412
+ this.idInputTarget.value = "";
15281
15413
  } else {
15282
- this.addId("");
15414
+ this.enableIdInput();
15415
+ this.idInputTarget.value = `in:${ids.join(",")}`;
15283
15416
  }
15284
15417
  }
15418
+ disableIdInput() {
15419
+ this.idInputTarget.removeAttribute("name");
15420
+ }
15421
+ enableIdInput() {
15422
+ this.idInputTarget.setAttribute("name", "id");
15423
+ }
15285
15424
  updateCounter(count) {
15286
15425
  let htmlString = "";
15287
15426
  switch (count) {
@@ -15336,16 +15475,6 @@ var table_actions_controller_default = class extends Controller {
15336
15475
  enableButton() {
15337
15476
  this.buttonTarget.removeAttribute("disabled");
15338
15477
  }
15339
- addId(id) {
15340
- const template = this.idInputTemplateTarget;
15341
- const input = template.innerHTML.replace(/ID/g, id);
15342
- this.formTarget.insertAdjacentHTML("afterbegin", input);
15343
- }
15344
- removeIds() {
15345
- this.idTargets.forEach((input) => {
15346
- this.formTarget.removeChild(input);
15347
- });
15348
- }
15349
15478
  };
15350
15479
 
15351
15480
  // app/assets/javascripts/headmin/controllers/table_controller.js
@@ -15461,6 +15590,7 @@ var Headmin = class {
15461
15590
  Stimulus.register("dropzone", dropzone_controller_default);
15462
15591
  Stimulus.register("file-preview", file_preview_controller_default);
15463
15592
  Stimulus.register("filter", filter_controller_default);
15593
+ Stimulus.register("filter-row", filter_row_controller_default);
15464
15594
  Stimulus.register("filters", filters_controller_default);
15465
15595
  Stimulus.register("flatpickr", flatpickr_controller_default);
15466
15596
  Stimulus.register("hello", hello_controller_default);
@@ -23,11 +23,85 @@
23
23
  }
24
24
  }
25
25
 
26
+ .h-filter-row {
27
+ padding: 0.5rem 0 0.5rem 0;
28
+ position: relative;
29
+ flex-wrap: unset;
30
+ display: flex;
31
+ gap: 10px;
32
+
33
+ &:first-child {
34
+ padding-top: 0
35
+ }
36
+
37
+ &:hover {
38
+ .h-filter-add-input, .h-filter-remove-input {
39
+ display: block;
40
+ }
41
+ }
42
+
43
+ input, select:not(.h-filter-operator) {
44
+ min-width: 200px;
45
+ }
46
+ }
47
+
48
+ .h-filter-operator {
49
+ width: 62px;
50
+ }
51
+
52
+ .h-filter-conditional {
53
+ position: relative;
54
+ display: flex;
55
+ justify-content: center;
56
+ align-items: center;
57
+
58
+ select {
59
+ width: 50px;
60
+ }
61
+
62
+ &::before {
63
+ content: '';
64
+ position: absolute;
65
+ top: calc(50% - 1px);
66
+ left: 0;
67
+ width: calc(50% - 35px);
68
+ height: 1px;
69
+ background: $popover-border-color;
70
+ }
71
+
72
+ &::after {
73
+ content: '';
74
+ position: absolute;
75
+ top: calc(50% - 1px);
76
+ left: calc(50% + 35px);
77
+ width: calc(50% - 35px);
78
+ height: 1px;
79
+ background: $popover-border-color;
80
+ }
81
+ }
82
+
26
83
  .h-filter-remove {
27
84
  border-left: 1px solid $gray-300;
28
85
  padding-left: 8px;
29
86
  margin-left: 2px;
87
+
30
88
  i::before {
31
89
  font-size: 0.8em;
32
90
  }
91
+ }
92
+
93
+ .h-filter-add-input {
94
+ position: absolute !important;
95
+ top: calc(100% - 12px);
96
+ left: calc(50% - 17px);
97
+ z-index: 9;
98
+ display: none;
99
+ }
100
+
101
+ .h-filter-remove-input {
102
+ position: absolute !important;
103
+ top: calc(50% - 15px);
104
+ left: calc(100% - 4px);
105
+ z-index: 9;
106
+ display: none;
33
107
  }
@@ -2,6 +2,14 @@ html {
2
2
  height: 100%;
3
3
  }
4
4
 
5
+ .list-group-item {
6
+ &.active {
7
+ .text-muted {
8
+ color: $list-group-active-color !important;
9
+ }
10
+ }
11
+ }
12
+
5
13
  mark {
6
14
  padding: 0;
7
15
  box-shadow: 0 $mark-padding 0 $mark-bg, 0 (-$mark-padding) 0 $mark-bg;
@@ -2,5 +2,10 @@
2
2
  background: $card-cap-bg;
3
3
  height: 100vh;
4
4
  overflow-y: scroll;
5
+ display: flex;
6
+ flex-direction: column;
5
7
  }
6
8
 
9
+ .content {
10
+ flex-grow: 1
11
+ }
@@ -2,7 +2,6 @@
2
2
  padding: 10px;
3
3
  background: $white;
4
4
  z-index: $zindex-popover;
5
- width: $popover-max-width;
6
5
  @include reset-text();
7
6
  @include font-size($popover-font-size);
8
7
  word-wrap: break-word;