flexi_admin 0.0.5

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 (127) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +42 -0
  3. data/Rakefile +7 -0
  4. data/lib/flexi_admin/components/actions/checkbox_component.html.slim +8 -0
  5. data/lib/flexi_admin/components/actions/checkbox_component.rb +13 -0
  6. data/lib/flexi_admin/components/actions/select_component.html.slim +11 -0
  7. data/lib/flexi_admin/components/actions/select_component.rb +17 -0
  8. data/lib/flexi_admin/components/base_component.rb +19 -0
  9. data/lib/flexi_admin/components/form/field_component.rb +11 -0
  10. data/lib/flexi_admin/components/form/label_component.html.slim +2 -0
  11. data/lib/flexi_admin/components/form/label_component.rb +13 -0
  12. data/lib/flexi_admin/components/form/rows_component.rb +9 -0
  13. data/lib/flexi_admin/components/form/text_input_component.html.slim +5 -0
  14. data/lib/flexi_admin/components/form/text_input_component.rb +17 -0
  15. data/lib/flexi_admin/components/helpers/action_button_helper.rb +28 -0
  16. data/lib/flexi_admin/components/helpers/action_helper.rb +12 -0
  17. data/lib/flexi_admin/components/helpers/icon_helper.rb +11 -0
  18. data/lib/flexi_admin/components/helpers/link_helper.rb +8 -0
  19. data/lib/flexi_admin/components/helpers/resource_helper.rb +71 -0
  20. data/lib/flexi_admin/components/helpers/selectable.rb +11 -0
  21. data/lib/flexi_admin/components/helpers/url_helper.rb +8 -0
  22. data/lib/flexi_admin/components/helpers/value_formatter.rb +32 -0
  23. data/lib/flexi_admin/components/nav/floating_toc_component.html.slim +2 -0
  24. data/lib/flexi_admin/components/nav/floating_toc_component.rb +6 -0
  25. data/lib/flexi_admin/components/resource/autocomplete_component.html.slim +30 -0
  26. data/lib/flexi_admin/components/resource/autocomplete_component.rb +83 -0
  27. data/lib/flexi_admin/components/resource/button_select_component.html.slim +13 -0
  28. data/lib/flexi_admin/components/resource/button_select_component.rb +19 -0
  29. data/lib/flexi_admin/components/resource/form_component.rb +18 -0
  30. data/lib/flexi_admin/components/resource/form_element_component.html.slim +3 -0
  31. data/lib/flexi_admin/components/resource/form_element_component.rb +31 -0
  32. data/lib/flexi_admin/components/resource/form_mixin.rb +287 -0
  33. data/lib/flexi_admin/components/resource/link_action_component.html.slim +8 -0
  34. data/lib/flexi_admin/components/resource/link_action_component.rb +16 -0
  35. data/lib/flexi_admin/components/resource/show_page_component.rb +21 -0
  36. data/lib/flexi_admin/components/resource/view_component.html.slim +26 -0
  37. data/lib/flexi_admin/components/resource/view_component.rb +22 -0
  38. data/lib/flexi_admin/components/resources/bulk_action/button_component.html.slim +7 -0
  39. data/lib/flexi_admin/components/resources/bulk_action/button_component.rb +29 -0
  40. data/lib/flexi_admin/components/resources/bulk_action/modal_component.html.slim +32 -0
  41. data/lib/flexi_admin/components/resources/bulk_action/modal_component.rb +54 -0
  42. data/lib/flexi_admin/components/resources/grid_view/card_component.html.slim +18 -0
  43. data/lib/flexi_admin/components/resources/grid_view/card_component.rb +65 -0
  44. data/lib/flexi_admin/components/resources/grid_view/grid_component.html.slim +10 -0
  45. data/lib/flexi_admin/components/resources/grid_view/grid_component.rb +16 -0
  46. data/lib/flexi_admin/components/resources/grid_view_component.rb +89 -0
  47. data/lib/flexi_admin/components/resources/index_page_component.html.slim +13 -0
  48. data/lib/flexi_admin/components/resources/index_page_component.rb +19 -0
  49. data/lib/flexi_admin/components/resources/list_view/cell_component.html.slim +2 -0
  50. data/lib/flexi_admin/components/resources/list_view/cell_component.rb +26 -0
  51. data/lib/flexi_admin/components/resources/list_view/table_component.html.slim +17 -0
  52. data/lib/flexi_admin/components/resources/list_view/table_component.rb +21 -0
  53. data/lib/flexi_admin/components/resources/list_view_component.rb +80 -0
  54. data/lib/flexi_admin/components/resources/pagination_component.html.slim +42 -0
  55. data/lib/flexi_admin/components/resources/pagination_component.rb +64 -0
  56. data/lib/flexi_admin/components/resources/resources_component.rb +34 -0
  57. data/lib/flexi_admin/components/resources/switch_view_component.html.slim +16 -0
  58. data/lib/flexi_admin/components/resources/switch_view_component.rb +45 -0
  59. data/lib/flexi_admin/components/resources/view_component.html.slim +12 -0
  60. data/lib/flexi_admin/components/resources/view_component.rb +15 -0
  61. data/lib/flexi_admin/components/shared/alert_component.html.slim +2 -0
  62. data/lib/flexi_admin/components/shared/alert_component.rb +11 -0
  63. data/lib/flexi_admin/components/shared/autocomplete/results_component.html.slim +15 -0
  64. data/lib/flexi_admin/components/shared/autocomplete/results_component.rb +50 -0
  65. data/lib/flexi_admin/components/shared/autocomplete.rb +6 -0
  66. data/lib/flexi_admin/components/shared/datalist_component.html.slim +24 -0
  67. data/lib/flexi_admin/components/shared/datalist_component.rb +20 -0
  68. data/lib/flexi_admin/components/shared/link_component.html.slim +3 -0
  69. data/lib/flexi_admin/components/shared/link_component.rb +15 -0
  70. data/lib/flexi_admin/components/shared/medium_component.html.slim +7 -0
  71. data/lib/flexi_admin/components/shared/medium_component.rb +51 -0
  72. data/lib/flexi_admin/components/shared/table/header_item_component.html.slim +9 -0
  73. data/lib/flexi_admin/components/shared/table/header_item_component.rb +78 -0
  74. data/lib/flexi_admin/components/shared/trix_component.html.slim +22 -0
  75. data/lib/flexi_admin/components/shared/trix_component.rb +21 -0
  76. data/lib/flexi_admin/components.rb +87 -0
  77. data/lib/flexi_admin/config.rb +24 -0
  78. data/lib/flexi_admin/controllers/modals_controller.rb +13 -0
  79. data/lib/flexi_admin/controllers/resources_controller.rb +240 -0
  80. data/lib/flexi_admin/controllers.rb +9 -0
  81. data/lib/flexi_admin/engine.rb +34 -0
  82. data/lib/flexi_admin/helpers/application_helper.rb +4 -0
  83. data/lib/flexi_admin/helpers.rb +8 -0
  84. data/lib/flexi_admin/javascript/controllers/application.js +9 -0
  85. data/lib/flexi_admin/javascript/controllers/autocomplete_controller.js +142 -0
  86. data/lib/flexi_admin/javascript/controllers/bulk_action_controller.js +158 -0
  87. data/lib/flexi_admin/javascript/controllers/button_select_controller.js +32 -0
  88. data/lib/flexi_admin/javascript/controllers/datalist_controller.js +104 -0
  89. data/lib/flexi_admin/javascript/controllers/floating_toc_controller.js +39 -0
  90. data/lib/flexi_admin/javascript/controllers/form_controller.js +17 -0
  91. data/lib/flexi_admin/javascript/controllers/form_validation_controller.js +86 -0
  92. data/lib/flexi_admin/javascript/controllers/index.js +44 -0
  93. data/lib/flexi_admin/javascript/controllers/pagination_controller.js +13 -0
  94. data/lib/flexi_admin/javascript/controllers/sorting_controller.js +17 -0
  95. data/lib/flexi_admin/javascript/controllers/switch_view_controller.js +15 -0
  96. data/lib/flexi_admin/javascript/controllers/toast_controller.js +18 -0
  97. data/lib/flexi_admin/javascript/controllers/trix_controller.js +32 -0
  98. data/lib/flexi_admin/javascript/controllers/uploads_controller.js +164 -0
  99. data/lib/flexi_admin/javascript/flexi_admin.js +4 -0
  100. data/lib/flexi_admin/javascript/utils.js +26 -0
  101. data/lib/flexi_admin/models/concerns/application_resource.rb +25 -0
  102. data/lib/flexi_admin/models/concerns/parentable.rb +17 -0
  103. data/lib/flexi_admin/models/context_params.rb +127 -0
  104. data/lib/flexi_admin/models/resources/context.rb +59 -0
  105. data/lib/flexi_admin/models/struct.rb +29 -0
  106. data/lib/flexi_admin/models/toast.rb +23 -0
  107. data/lib/flexi_admin/models.rb +20 -0
  108. data/lib/flexi_admin/prompts/codegen-system-prompt.md +50 -0
  109. data/lib/flexi_admin/railtie.rb +78 -0
  110. data/lib/flexi_admin/routes.rb +15 -0
  111. data/lib/flexi_admin/services/code_gen/code_export.rb +22 -0
  112. data/lib/flexi_admin/services/code_gen/gemini.rb +104 -0
  113. data/lib/flexi_admin/services/code_gen/gpt.rb +68 -0
  114. data/lib/flexi_admin/services/code_gen/runner.rb +210 -0
  115. data/lib/flexi_admin/services/code_gen.rb +14 -0
  116. data/lib/flexi_admin/services/create_resource.rb +32 -0
  117. data/lib/flexi_admin/services/update_resource.rb +30 -0
  118. data/lib/flexi_admin/services.rb +10 -0
  119. data/lib/flexi_admin/version.rb +10 -0
  120. data/lib/flexi_admin/views/shared/_redirect.slim +2 -0
  121. data/lib/flexi_admin/views/shared/_reload.slim +2 -0
  122. data/lib/flexi_admin/views/shared/_toasts.slim +15 -0
  123. data/lib/flexi_admin/views/shared/not_authorized.slim +11 -0
  124. data/lib/flexi_admin.rb +58 -0
  125. data/lib/tasks/flexi_admin.rake +49 -0
  126. data/lib/tasks/semantic.rake +57 -0
  127. metadata +333 -0
@@ -0,0 +1,142 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import { markValid } from "../utils";
3
+
4
+ // Connects to data-controller="autocomplete"
5
+ export default class extends Controller {
6
+ static targets = [
7
+ "input",
8
+ "list",
9
+ "clearIcon",
10
+ "loadingIcon",
11
+ "resourceId",
12
+ "isDisabled",
13
+ ];
14
+
15
+ connect() {
16
+ this._disableInput(this.inputTarget.dataset.autocompleteIsDisabled);
17
+ this.timeout = null;
18
+ this.blurTimeout = null;
19
+ if (this.inputTarget.value && !this.inputTarget.disabled) {
20
+ this._clearIcon("show");
21
+ } else {
22
+ this._clearIcon("hide");
23
+ }
24
+ this._loadingIcon("hide");
25
+ }
26
+
27
+ disconnect() {
28
+ clearTimeout(this.timeout);
29
+ clearTimeout(this.blurTimeout);
30
+ this.hideResults();
31
+ }
32
+
33
+ keyup(event) {
34
+ if (this.inputTarget.value.length > 0) {
35
+ this.listTarget.classList.remove("d-none");
36
+ this._clearIcon("show");
37
+ markValid(event);
38
+ } else {
39
+ this.hideResults();
40
+ this._clearIcon("hide");
41
+ return;
42
+ }
43
+
44
+ clearTimeout(this.timeout);
45
+
46
+ this.timeout = setTimeout(() => {
47
+ this._search(this.inputTarget.value);
48
+ }, 200);
49
+ }
50
+
51
+ hideResults() {
52
+ this.listTarget.classList.add("d-none");
53
+ }
54
+
55
+ onFocusOut(event) {
56
+ // Delay hiding results to allow for click events on results
57
+ this.blurTimeout = setTimeout(() => {
58
+ this.hideResults();
59
+ }, 200);
60
+ }
61
+
62
+ preventBlur(event) {
63
+ // Prevent the blur event when clicking on a result
64
+ event.preventDefault();
65
+ clearTimeout(this.blurTimeout);
66
+ }
67
+
68
+ select(event) {
69
+ this.resourceIdTarget.value =
70
+ event.currentTarget.dataset.autocompleteResourceIdValue;
71
+ this.inputTarget.value = event.currentTarget.innerText;
72
+ this.inputTarget.dispatchEvent(new Event("input"));
73
+ clearTimeout(this.blurTimeout);
74
+ this.hideResults();
75
+ }
76
+
77
+ inputValue(event) {
78
+ this.inputTarget.value = event.currentTarget.innerText;
79
+ this.inputTarget.dispatchEvent(new Event("input"));
80
+ this.hideResults();
81
+ }
82
+
83
+ clear() {
84
+ this.inputTarget.value = "";
85
+ this.resourceIdTarget.value = "";
86
+ this.inputTarget.dispatchEvent(new Event("input"));
87
+ this.hideResults();
88
+ this._clearIcon("hide");
89
+ this.inputTarget.focus();
90
+ }
91
+
92
+ _loadingIcon(string) {
93
+ if (string === "show") {
94
+ this.loadingIconTarget.classList.remove("d-none");
95
+ } else {
96
+ this.loadingIconTarget.classList.add("d-none");
97
+ }
98
+ }
99
+
100
+ _clearIcon(string) {
101
+ if (string === "show") {
102
+ this.clearIconTarget.classList.remove("d-none");
103
+ } else {
104
+ this.clearIconTarget.classList.add("d-none");
105
+ }
106
+ }
107
+
108
+ _disableInput(string) {
109
+ if (string === "true") {
110
+ this.inputTarget.disabled = true;
111
+ } else {
112
+ this.inputTarget.disabled = false;
113
+ }
114
+ }
115
+
116
+ async _search(string) {
117
+ const path = this.inputTarget.dataset.autocompleteSearchPath;
118
+ const url = new URL(path, window.location.origin);
119
+ url.searchParams.set("q", string);
120
+
121
+ try {
122
+ this.loadingIconTarget.classList.remove("d-none"); // Show loading icon
123
+ const response = await fetch(url.toString(), {
124
+ headers: {
125
+ Accept: "text/html",
126
+ },
127
+ });
128
+
129
+ if (response.ok) {
130
+ const html = await response.text();
131
+ this._clearIcon("show");
132
+ this.listTarget.innerHTML = html; // Set the results as inner HTML
133
+ } else {
134
+ console.error("Error fetching search results:", response.statusText);
135
+ }
136
+ } catch (error) {
137
+ console.error("Network error:", error);
138
+ } finally {
139
+ this.loadingIconTarget.classList.add("d-none"); // Hide loading icon
140
+ }
141
+ }
142
+ }
@@ -0,0 +1,158 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import { fetchTurboContent } from "../utils";
3
+
4
+ // Connects to data-controller="bulk-action"
5
+ export default class extends Controller {
6
+ static values = {
7
+ scope: String,
8
+ };
9
+
10
+ connect() {
11
+ this.selectedIds = [];
12
+
13
+ document.addEventListener("bulk-action-modal-opened", (event) => {
14
+ this._modalOpened(event);
15
+ });
16
+ }
17
+
18
+ disconnect() {
19
+ document.removeEventListener("bulk-action-modal-opened", this._modalOpened);
20
+ }
21
+
22
+ async requestModal(event) {
23
+ const url = await event.target.dataset.urlWithId;
24
+ fetchTurboContent(event, url);
25
+ }
26
+
27
+ submitForm(event) {
28
+ event.preventDefault();
29
+
30
+ const modal = this._withModal();
31
+ const form = modal.querySelector("form");
32
+ form.requestSubmit();
33
+ }
34
+
35
+ toggle(event) {
36
+ const id = event.target.value;
37
+
38
+ if (event.target.checked) {
39
+ // Add ID to the array if the checkbox is checked
40
+ this.selectedIds.push(id);
41
+ } else {
42
+ // Remove ID from the array if the checkbox is unchecked
43
+ this.selectedIds = this.selectedIds.filter(
44
+ (selectedId) => selectedId !== id
45
+ );
46
+ }
47
+
48
+ if (this.selectedIds.length > 0) {
49
+ this._enableActions();
50
+ } else {
51
+ this._disableActions();
52
+ }
53
+
54
+ this._persist();
55
+ // console.log(`${this.scopeValue} select one`, this.selectedIds.length);
56
+ }
57
+
58
+ toggleAll(event) {
59
+ if (event.target.checked) {
60
+ this._selectAll();
61
+ } else {
62
+ this._unselectAll();
63
+ }
64
+ }
65
+
66
+ _modalOpened(event) {
67
+ if (event.detail.scope !== this.scopeValue) {
68
+ return;
69
+ }
70
+
71
+ const modal = this._withModal();
72
+
73
+ this._populateCountElements(modal);
74
+ this._populateIds(modal);
75
+ this._addProcessor(modal, event.detail.modalId);
76
+ }
77
+
78
+ _populateCountElements(modal) {
79
+ const countElements = modal.querySelectorAll("span.count");
80
+ countElements.forEach((countElement) => {
81
+ countElement.textContent = this.selectedIds.length;
82
+ });
83
+ }
84
+
85
+ _addProcessor(modal, processor) {
86
+ const form = modal.querySelector("form");
87
+ const hiddenInput = document.createElement("input");
88
+ hiddenInput.type = "hidden";
89
+ hiddenInput.name = "processor";
90
+ hiddenInput.value = processor;
91
+ form.appendChild(hiddenInput);
92
+ }
93
+
94
+ _populateIds(modal) {
95
+ const form = modal.querySelector("form");
96
+ const hiddenInput = document.createElement("input");
97
+ hiddenInput.type = "hidden";
98
+ hiddenInput.name = "ids";
99
+ hiddenInput.value = JSON.stringify(this.selectedIds);
100
+ form.appendChild(hiddenInput);
101
+ }
102
+
103
+ _withModal() {
104
+ return document.querySelector(`#modalx_${this.scopeValue}`);
105
+ }
106
+
107
+ _selectAll() {
108
+ // find all checkboxes with the name of the actionScope
109
+ const checkboxes = document.querySelectorAll(
110
+ `.bulk-action-checkbox > input[name="${this.scopeValue}"]`
111
+ );
112
+
113
+ this.selectedIds = Array.from(checkboxes).map((checkbox) => checkbox.value);
114
+
115
+ Array.from(checkboxes).forEach((checkbox) => {
116
+ checkbox.checked = true;
117
+ });
118
+
119
+ this._persist();
120
+ this._enableActions();
121
+ // console.log(`${this.scopeValue} select all`, this.selectedIds.length);
122
+ }
123
+
124
+ _unselectAll() {
125
+ this.selectedIds = [];
126
+ const checkboxes = document.querySelectorAll(
127
+ `.bulk-action-checkbox > input[name="${this.scopeValue}"]`
128
+ );
129
+ Array.from(checkboxes).forEach((checkbox) => {
130
+ checkbox.checked = false;
131
+ });
132
+
133
+ this._persist();
134
+ this._disableActions();
135
+ // console.log(`${this.scopeValue} unselect all`, this.selectedIds.length);
136
+ }
137
+
138
+ _persist() {
139
+ this.element.dataset.ids = JSON.stringify(this.selectedIds);
140
+ // console.log("persist", this.element.dataset.ids);
141
+ }
142
+
143
+ _enableActions() {
144
+ document
145
+ .querySelectorAll(".dropdown-item.bulk-action.selection-dependent")
146
+ .forEach((item) => {
147
+ item.classList.remove("disabled");
148
+ });
149
+ }
150
+
151
+ _disableActions() {
152
+ document
153
+ .querySelectorAll(".dropdown-item.bulk-action.selection-dependent")
154
+ .forEach((item) => {
155
+ item.classList.add("disabled");
156
+ });
157
+ }
158
+ }
@@ -0,0 +1,32 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ // Connects to data-controller="button-select"
4
+ export default class extends Controller {
5
+ static targets = ["button", "input"];
6
+ static values = { disabled: Boolean };
7
+
8
+ connect() {
9
+ this._updateSelectedButton();
10
+ }
11
+
12
+ select(event) {
13
+ if (this.disabledValue) {
14
+ return;
15
+ }
16
+
17
+ event.preventDefault();
18
+ const selectedValue = event.currentTarget.dataset.value;
19
+ this.inputTarget.value = selectedValue;
20
+ this._updateSelectedButton();
21
+ }
22
+
23
+ _updateSelectedButton() {
24
+ this.buttonTargets.forEach((button) => {
25
+ button.classList.toggle(
26
+ "selected",
27
+ button.dataset.value === this.inputTarget.value
28
+ );
29
+ button.classList.toggle("disabled", this.disabledValue);
30
+ });
31
+ }
32
+ }
@@ -0,0 +1,104 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ // Connects to data-controller="autocomplete"
4
+ export default class extends Controller {
5
+ static targets = ["input", "datalist", "clearIcon", "loadingIcon"];
6
+
7
+ static values = {
8
+ searchPath: String,
9
+ isDisabled: Boolean,
10
+ resourceId: String,
11
+ };
12
+
13
+ connect() {
14
+ this._disableInput(this.isDisabledValue);
15
+ this.timeout = null;
16
+ if (this.inputTarget.value && !this.inputTarget.disabled) {
17
+ this._clearIcon("show");
18
+ } else {
19
+ this._clearIcon("hide");
20
+ }
21
+ this._loadingIcon("hide");
22
+ }
23
+
24
+ disconnect() {
25
+ clearTimeout(this.timeout);
26
+ while (this.datalistTarget.firstChild) {
27
+ this.datalistTarget.removeChild(this.datalistTarget.firstChild);
28
+ }
29
+ }
30
+
31
+ keyup() {
32
+ if (this.inputTarget.value.length > 0) {
33
+ this._clearIcon("show");
34
+ } else {
35
+ this._clearIcon("hide");
36
+ return;
37
+ }
38
+
39
+ clearTimeout(this.timeout);
40
+
41
+ this.timeout = setTimeout(() => {
42
+ this._search(this.inputTarget.value);
43
+ }, 200);
44
+ }
45
+
46
+ clear() {
47
+ this.inputTarget.value = "";
48
+ this.inputTarget.dispatchEvent(new Event("input"));
49
+ this._clearIcon("hide");
50
+ this.inputTarget.focus();
51
+ }
52
+
53
+ _loadingIcon(string) {
54
+ if (string === "show") {
55
+ this.loadingIconTarget.classList.remove("d-none");
56
+ } else {
57
+ this.loadingIconTarget.classList.add("d-none");
58
+ }
59
+ }
60
+
61
+ _clearIcon(string) {
62
+ if (string === "show") {
63
+ this.clearIconTarget.classList.remove("d-none");
64
+ } else {
65
+ this.clearIconTarget.classList.add("d-none");
66
+ }
67
+ }
68
+
69
+ _disableInput(string) {
70
+ if (string === "true") {
71
+ this.inputTarget.disabled = true;
72
+ } else {
73
+ this.inputTarget.disabled = false;
74
+ }
75
+ }
76
+
77
+ async _search(string) {
78
+ const path = this.searchPathValue;
79
+ const url = new URL(path, window.location.origin);
80
+ url.searchParams.set("q", string);
81
+
82
+ try {
83
+ this.loadingIconTarget.classList.remove("d-none"); // Show loading icon
84
+ const response = await fetch(url.toString(), {
85
+ headers: {
86
+ Accept: "text/html",
87
+ },
88
+ });
89
+
90
+ if (response.ok) {
91
+ const html = await response.text();
92
+ this._clearIcon("show");
93
+ this.datalistTarget.innerHTML = html; // Set the results as inner HTML
94
+ this.inputTarget.dispatchEvent(new Event("input"));
95
+ } else {
96
+ console.error("Error fetching search results:", response.statusText);
97
+ }
98
+ } catch (error) {
99
+ console.error("Network error:", error);
100
+ } finally {
101
+ this.loadingIconTarget.classList.add("d-none"); // Hide loading icon
102
+ }
103
+ }
104
+ }
@@ -0,0 +1,39 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ // Connects to data-controller="floating-toc"
4
+ export default class extends Controller {
5
+ static targets = ["toc"];
6
+
7
+ connect() {
8
+ this._buildToc();
9
+ }
10
+
11
+ _buildToc() {
12
+ // Find the TOC target element
13
+ const tocTarget = this.tocTarget;
14
+ if (!tocTarget) return;
15
+
16
+ // Find all elements with the .toc class
17
+ const tocChapters = document.querySelectorAll(".toc");
18
+
19
+ // Create a list item for each chapter
20
+ tocChapters.forEach((chapter) => {
21
+ const title = chapter.innerHTML;
22
+ const id = chapter.id;
23
+
24
+ // Create the list item and link
25
+ const listItem = document.createElement("li");
26
+ listItem.classList.add("nav-item");
27
+
28
+ const link = document.createElement("a");
29
+ link.classList.add("nav-link");
30
+ link.href = `#${id}`;
31
+ link.textContent = title;
32
+ link.setAttribute("data-turbo", "false");
33
+
34
+ // Append the link to the list item, and the list item to the TOC target
35
+ listItem.appendChild(link);
36
+ tocTarget.appendChild(listItem);
37
+ });
38
+ }
39
+ }
@@ -0,0 +1,17 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import { fetchTurboContent } from "../utils";
3
+
4
+ // Connects to data-controller="form"
5
+ export default class extends Controller {
6
+ static values = {
7
+ resourcePath: String,
8
+ };
9
+
10
+ enable(event) {
11
+ fetchTurboContent(event, this.resourcePathValue);
12
+ }
13
+
14
+ disable(event) {
15
+ fetchTurboContent(event, this.resourcePathValue);
16
+ }
17
+ }
@@ -0,0 +1,86 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ connect() {
5
+ this.setCustomValidationMessages();
6
+ this._unhideValidationMessages();
7
+ }
8
+
9
+ disconnect() {
10
+ this.removeCustomValidationMessages();
11
+ }
12
+
13
+ setCustomValidationMessages() {
14
+ this.getInputFields().forEach((field) => {
15
+ field.addEventListener("invalid", this.handleInvalid);
16
+ });
17
+ }
18
+
19
+ removeCustomValidationMessages() {
20
+ this.getInputFields().forEach((field) => {
21
+ field.removeEventListener("invalid", this.handleInvalid);
22
+ });
23
+ }
24
+
25
+ getInputFields() {
26
+ return Array.from(
27
+ this.element.querySelectorAll(
28
+ 'input:not([type="hidden"]), select, textarea'
29
+ )
30
+ );
31
+ }
32
+
33
+ handleInvalid = (event) => {
34
+ const field = event.target;
35
+ if (this.isFieldValid(field)) {
36
+ field.setCustomValidity("");
37
+ return;
38
+ }
39
+
40
+ const type = this._isCustomType(field)
41
+ ? field.dataset.fieldType
42
+ : field.type;
43
+ let message = "Toto je povinné pole.";
44
+
45
+ switch (type) {
46
+ case "text":
47
+ message = "Toto je povinné pole.";
48
+ break;
49
+ case "autocomplete":
50
+ message = "Najděte a vyberte si položku ze seznamu.";
51
+ break;
52
+ case "email":
53
+ message = "Zadejte platnou emailovou adresu.";
54
+ break;
55
+ case "tel":
56
+ message = "Zadejte platné telefonní číslo.";
57
+ break;
58
+ // Add more cases for other field types as needed
59
+ }
60
+
61
+ field.setCustomValidity(message);
62
+ };
63
+
64
+ isFieldValid(field) {
65
+ return (
66
+ !field.validity.badInput &&
67
+ !field.validity.valueMissing &&
68
+ !field.validity.typeMismatch &&
69
+ !field.validity.patternMismatch &&
70
+ !field.validity.tooShort &&
71
+ !field.validity.tooLong &&
72
+ !field.validity.rangeUnderflow &&
73
+ !field.validity.rangeOverflow
74
+ );
75
+ }
76
+
77
+ _isCustomType(field) {
78
+ return field.dataset.fieldType !== undefined;
79
+ }
80
+
81
+ _unhideValidationMessages() {
82
+ this.element.querySelectorAll(".invalid-feedback").forEach((el) => {
83
+ el.classList.add("d-table-cell");
84
+ });
85
+ }
86
+ }
@@ -0,0 +1,44 @@
1
+ // This file is auto-generated by ./bin/rails stimulus:manifest:update
2
+ // Run that command whenever you add a new controller or create them with
3
+ // ./bin/rails generate stimulus controllerName
4
+
5
+ import { application } from "./application";
6
+
7
+ import AutocompleteController from "./autocomplete_controller";
8
+ application.register("autocomplete", AutocompleteController);
9
+
10
+ import BulkActionController from "./bulk_action_controller";
11
+ application.register("bulk-action", BulkActionController);
12
+
13
+ import ButtonSelectController from "./button_select_controller";
14
+ application.register("button-select", ButtonSelectController);
15
+
16
+ import DatalistController from "./datalist_controller";
17
+ application.register("datalist", DatalistController);
18
+
19
+ import FormController from "./form_controller";
20
+ application.register("form", FormController);
21
+
22
+ import FormValidationController from "./form_validation_controller";
23
+ application.register("form-validation", FormValidationController);
24
+
25
+ import PaginationController from "./pagination_controller";
26
+ application.register("pagination", PaginationController);
27
+
28
+ import SwitchViewController from "./switch_view_controller";
29
+ application.register("switch-view", SwitchViewController);
30
+
31
+ import ToastController from "./toast_controller";
32
+ application.register("toast", ToastController);
33
+
34
+ import TrixController from "./trix_controller";
35
+ application.register("trix", TrixController);
36
+
37
+ import UploadsController from "./uploads_controller";
38
+ application.register("uploads", UploadsController);
39
+
40
+ import SortingController from "./sorting_controller";
41
+ application.register("sorting", SortingController);
42
+
43
+ import FloatingTocController from "./floating_toc_controller";
44
+ application.register("floating-toc", FloatingTocController);
@@ -0,0 +1,13 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import { fetchTurboContent } from "../utils";
3
+
4
+ // Connects to data-controller="pagination"
5
+ export default class extends Controller {
6
+ connect() {}
7
+
8
+ paginate(event) {
9
+ const path = event.target.dataset.resourcePath;
10
+
11
+ fetchTurboContent(event, path);
12
+ }
13
+ }
@@ -0,0 +1,17 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import { fetchTurboContent } from "../utils";
3
+
4
+ // Connects to data-controller="sorting"
5
+ export default class extends Controller {
6
+ static values = {
7
+ sortPath: String,
8
+ };
9
+
10
+ // connect() {}
11
+
12
+ sort(event) {
13
+ event.preventDefault();
14
+
15
+ fetchTurboContent(event, this.sortPathValue);
16
+ }
17
+ }
@@ -0,0 +1,15 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import { fetchTurboContent } from "../utils";
3
+
4
+ // Connects to data-controller="switch-view"
5
+ export default class extends Controller {
6
+ static values = {
7
+ resourcePath: String,
8
+ };
9
+
10
+ connect() {}
11
+
12
+ switch(event) {
13
+ fetchTurboContent(event, this.resourcePathValue);
14
+ }
15
+ }
@@ -0,0 +1,18 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ // Connects to data-controller="toast"
4
+ export default class extends Controller {
5
+ connect() {
6
+ //Set a delay to hide the toast message after 5 seconds (5000 milliseconds)
7
+ setTimeout(() => {
8
+ this.element.classList.add("slide-out");
9
+ this.element.addEventListener(
10
+ "transitionend",
11
+ () => {
12
+ this.element.style.display = "none";
13
+ },
14
+ { once: true }
15
+ );
16
+ }, 5000);
17
+ }
18
+ }