decidim-admin 0.30.2 → 0.31.0.rc1

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 (255) hide show
  1. checksums.yaml +4 -4
  2. data/app/commands/decidim/admin/update_organization.rb +18 -3
  3. data/app/controllers/concerns/decidim/admin/content_blocks/landing_page_content_blocks.rb +1 -1
  4. data/app/controllers/concerns/decidim/admin/filterable.rb +1 -1
  5. data/app/controllers/decidim/admin/area_types_controller.rb +2 -2
  6. data/app/controllers/decidim/admin/areas_controller.rb +2 -2
  7. data/app/controllers/decidim/admin/block_user_controller.rb +2 -2
  8. data/app/controllers/decidim/admin/component_permissions_controller.rb +1 -1
  9. data/app/controllers/decidim/admin/components_controller.rb +2 -2
  10. data/app/controllers/decidim/admin/concerns/has_attachment_collections.rb +2 -2
  11. data/app/controllers/decidim/admin/concerns/has_attachments.rb +2 -2
  12. data/app/controllers/decidim/admin/concerns/has_private_users.rb +3 -3
  13. data/app/controllers/decidim/admin/concerns/has_private_users_csv_import.rb +1 -1
  14. data/app/controllers/decidim/admin/conflicts_controller.rb +1 -1
  15. data/app/controllers/decidim/admin/dashboard_controller.rb +0 -9
  16. data/app/controllers/decidim/admin/impersonations_controller.rb +1 -1
  17. data/app/controllers/decidim/admin/imports_controller.rb +1 -1
  18. data/app/controllers/decidim/admin/managed_users/promotions_controller.rb +1 -1
  19. data/app/controllers/decidim/admin/newsletters_controller.rb +4 -4
  20. data/app/controllers/decidim/admin/organization_appearance_controller.rb +1 -3
  21. data/app/controllers/decidim/admin/organization_controller.rb +3 -5
  22. data/app/controllers/decidim/admin/organization_external_domain_allowlist_controller.rb +1 -1
  23. data/app/controllers/decidim/admin/organization_homepage_controller.rb +4 -0
  24. data/app/controllers/decidim/admin/participatory_space/user_role_controller.rb +2 -2
  25. data/app/controllers/decidim/admin/reminders_controller.rb +1 -1
  26. data/app/controllers/decidim/admin/resource_permissions_controller.rb +1 -1
  27. data/app/controllers/decidim/admin/scope_types_controller.rb +2 -2
  28. data/app/controllers/decidim/admin/scopes_controller.rb +2 -2
  29. data/app/controllers/decidim/admin/share_tokens_controller.rb +2 -2
  30. data/app/controllers/decidim/admin/static_page_topics_controller.rb +2 -2
  31. data/app/controllers/decidim/admin/static_pages_controller.rb +2 -2
  32. data/app/controllers/decidim/admin/statistics_controller.rb +34 -0
  33. data/app/controllers/decidim/admin/taxonomies_controller.rb +2 -2
  34. data/app/controllers/decidim/admin/taxonomy_filters_controller.rb +2 -2
  35. data/app/controllers/decidim/admin/taxonomy_items_controller.rb +2 -2
  36. data/app/controllers/decidim/admin/users_controller.rb +1 -1
  37. data/app/forms/decidim/admin/block_user_form.rb +1 -1
  38. data/app/forms/decidim/admin/block_users_form.rb +1 -1
  39. data/app/forms/decidim/admin/organization_form.rb +35 -6
  40. data/app/helpers/decidim/admin/filterable_helper.rb +27 -8
  41. data/app/helpers/decidim/admin/icon_link_helper.rb +2 -3
  42. data/app/helpers/decidim/admin/icon_with_tooltip_helper.rb +1 -2
  43. data/app/helpers/decidim/admin/imports_helper.rb +0 -5
  44. data/app/helpers/decidim/admin/menu_helper.rb +3 -2
  45. data/app/helpers/decidim/admin/moderations/reports_helper.rb +1 -1
  46. data/app/helpers/decidim/admin/resource_permissions_helper.rb +13 -13
  47. data/app/helpers/decidim/admin/settings_helper.rb +11 -3
  48. data/app/jobs/decidim/admin/newsletter_job.rb +1 -0
  49. data/app/packs/entrypoints/decidim_admin.js +2 -3
  50. data/app/packs/entrypoints/decidim_admin.scss +1 -1
  51. data/app/packs/entrypoints/decidim_admin_overrides.scss +1 -1
  52. data/app/packs/src/decidim/admin/admin_autocomplete.js +2 -2
  53. data/app/packs/src/decidim/admin/application.js +11 -10
  54. data/app/packs/src/decidim/admin/budget_rule_toggler.component.js +84 -34
  55. data/app/packs/src/decidim/admin/controllers/slug/controller.js +25 -0
  56. data/app/packs/src/decidim/admin/controllers/slug/slug.test.js +239 -0
  57. data/app/packs/src/decidim/admin/css_preview.js +1 -1
  58. data/app/packs/src/decidim/admin/draggable-list.js +1 -1
  59. data/app/packs/src/decidim/admin/draggable-table.js +1 -1
  60. data/app/packs/src/decidim/admin/dynamic_fields.component.js +13 -12
  61. data/app/packs/src/decidim/admin/external_domain_allowlist.js +1 -1
  62. data/app/packs/src/decidim/admin/form.js +21 -4
  63. data/app/packs/src/decidim/admin/global_moderations.js +2 -2
  64. data/app/packs/src/decidim/admin/managed_moderated_users.js +2 -2
  65. data/app/packs/src/decidim/admin/moderations.js +1 -1
  66. data/app/packs/src/decidim/admin/newsletters.js +1 -1
  67. data/app/packs/src/decidim/admin/officializations.js +1 -1
  68. data/app/packs/src/decidim/admin/participatory_space_search.js +2 -2
  69. data/app/packs/src/decidim/admin/proposal_infinite_edit.js +1 -1
  70. data/app/packs/src/decidim/admin/resources_permissions.js +1 -1
  71. data/app/packs/src/decidim/admin/sortable.js +1 -1
  72. data/app/packs/src/decidim/admin/sync_radio_buttons.js +1 -1
  73. data/app/packs/src/decidim/admin/tab_focus.js +1 -1
  74. data/app/packs/src/decidim/admin/text_copy.js +1 -1
  75. data/app/packs/src/decidim/admin/triadic_color_picker.js +1 -1
  76. data/app/packs/stylesheets/decidim/admin/_datepicker.scss +2 -2
  77. data/app/packs/stylesheets/decidim/admin/_dropdown.scss +12 -2
  78. data/app/packs/stylesheets/decidim/admin/_filters.scss +1 -57
  79. data/app/packs/stylesheets/decidim/admin/_forms.scss +1 -10
  80. data/app/packs/stylesheets/decidim/admin/_item_edit.scss +2 -10
  81. data/app/packs/stylesheets/decidim/admin/_item_show.scss +1 -5
  82. data/app/packs/stylesheets/decidim/admin/_legacy_foundation.scss +0 -412
  83. data/app/packs/stylesheets/decidim/admin/_main-nav.scss +75 -3
  84. data/app/packs/stylesheets/decidim/admin/_secondary-nav.scss +6 -10
  85. data/app/packs/stylesheets/decidim/admin/_select_picker.scss +1 -1
  86. data/app/packs/stylesheets/decidim/admin/_sidebar-menu.scss +2 -2
  87. data/app/packs/stylesheets/decidim/admin/_table-list.scss +25 -7
  88. data/app/packs/stylesheets/decidim/admin/_tabs.scss +4 -0
  89. data/app/packs/stylesheets/decidim/admin/_taxonomies.scss +6 -2
  90. data/app/packs/stylesheets/decidim/admin/application.scss +28 -29
  91. data/app/permissions/decidim/admin/permissions.rb +17 -5
  92. data/app/presenters/decidim/admin/dashboard_statistic_charts_presenter.rb +18 -0
  93. data/app/queries/decidim/admin/user_filter.rb +3 -3
  94. data/app/views/decidim/admin/area_types/index.html.erb +34 -11
  95. data/app/views/decidim/admin/areas/index.html.erb +32 -12
  96. data/app/views/decidim/admin/attachment_collections/index.html.erb +42 -14
  97. data/app/views/decidim/admin/attachments/index.html.erb +32 -13
  98. data/app/views/decidim/admin/block_user/bulk_new.html.erb +1 -1
  99. data/app/views/decidim/admin/block_user/new.html.erb +1 -1
  100. data/app/views/decidim/admin/components/_actions.html.erb +114 -54
  101. data/app/views/decidim/admin/components/_component_row.html.erb +5 -5
  102. data/app/views/decidim/admin/components/_components_table.html.erb +1 -1
  103. data/app/views/decidim/admin/components/_form.html.erb +4 -4
  104. data/app/views/decidim/admin/components/index.html.erb +12 -13
  105. data/app/views/decidim/admin/dashboard/show.html.erb +9 -21
  106. data/app/views/decidim/admin/exports/_dropdown.html.erb +16 -14
  107. data/app/views/decidim/admin/help_sections/_form.html.erb +1 -1
  108. data/app/views/decidim/admin/impersonatable_users/index.html.erb +68 -33
  109. data/app/views/decidim/admin/imports/_dropdown.html.erb +13 -11
  110. data/app/views/decidim/admin/imports/new.html.erb +13 -12
  111. data/app/views/decidim/admin/moderated_users/bulk_actions/_dropdown.html.erb +29 -32
  112. data/app/views/decidim/admin/moderated_users/index.html.erb +61 -21
  113. data/app/views/decidim/admin/moderations/_moderation-tr.html.erb +87 -32
  114. data/app/views/decidim/admin/moderations/_report.html.erb +2 -4
  115. data/app/views/decidim/admin/moderations/bulk_actions/_dropdown.html.erb +30 -34
  116. data/app/views/decidim/admin/moderations/index.html.erb +1 -1
  117. data/app/views/decidim/admin/moderations/reports/index.html.erb +1 -2
  118. data/app/views/decidim/admin/newsletters/index.html.erb +64 -21
  119. data/app/views/decidim/admin/officializations/index.html.erb +91 -32
  120. data/app/views/decidim/admin/organization/_form.html.erb +6 -92
  121. data/app/views/decidim/admin/organization/edit.html.erb +1 -5
  122. data/app/views/decidim/admin/organization/form/_admin_terms_of_service.html.erb +15 -0
  123. data/app/views/decidim/admin/organization/form/_basic_configuration.html.erb +31 -0
  124. data/app/views/decidim/admin/{organization_appearance → organization}/form/_colors.html.erb +9 -9
  125. data/app/views/decidim/admin/organization/form/_extra_features.html.erb +84 -0
  126. data/app/views/decidim/admin/{organization_appearance/form/_images.html.erb → organization/form/_logos.html.erb} +4 -4
  127. data/app/views/decidim/admin/organization/form/_welcome_notification.html.erb +25 -0
  128. data/app/views/decidim/admin/organization_external_domain_allowlist/_external_domain.html.erb +8 -6
  129. data/app/views/decidim/admin/participatory_space_private_users/index.html.erb +49 -22
  130. data/app/views/decidim/admin/resource_permissions/edit.html.erb +1 -1
  131. data/app/views/decidim/admin/scope_types/index.html.erb +31 -12
  132. data/app/views/decidim/admin/scopes/index.html.erb +40 -13
  133. data/app/views/decidim/admin/share_tokens/_form.html.erb +1 -1
  134. data/app/views/decidim/admin/share_tokens/edit.html.erb +1 -1
  135. data/app/views/decidim/admin/share_tokens/index.html.erb +69 -12
  136. data/app/views/decidim/admin/share_tokens/new.html.erb +1 -1
  137. data/app/views/decidim/admin/shared/_filters.html.erb +8 -10
  138. data/app/views/decidim/admin/shared/landing_page/_content_blocks.html.erb +14 -11
  139. data/app/views/decidim/admin/shared/landing_page_content_blocks/edit.html.erb +2 -4
  140. data/app/views/decidim/admin/static_page_topics/index.html.erb +31 -10
  141. data/app/views/decidim/admin/static_pages/_form.html.erb +1 -1
  142. data/app/views/decidim/admin/static_pages/_topic.html.erb +46 -16
  143. data/app/views/decidim/admin/statistics/_statistics.html.erb +13 -0
  144. data/app/views/decidim/admin/statistics/index.html.erb +6 -0
  145. data/app/views/decidim/admin/taxonomies/_form.html.erb +1 -1
  146. data/app/views/decidim/admin/taxonomies/_row.html.erb +24 -6
  147. data/app/views/decidim/admin/taxonomies/_table.html.erb +2 -2
  148. data/app/views/decidim/admin/taxonomies/_taxonomy_actions.html.erb +48 -15
  149. data/app/views/decidim/admin/taxonomies/edit.html.erb +10 -0
  150. data/app/views/decidim/admin/taxonomy_filters/_form.html.erb +1 -1
  151. data/app/views/decidim/admin/taxonomy_filters/_table.html.erb +52 -15
  152. data/app/views/decidim/admin/taxonomy_filters/new.html.erb +1 -1
  153. data/app/views/decidim/admin/taxonomy_filters_selector/_component_table.html.erb +38 -23
  154. data/app/views/decidim/admin/taxonomy_items/_form.html.erb +1 -1
  155. data/app/views/decidim/admin/users/index.html.erb +48 -24
  156. data/app/views/layouts/decidim/admin/_application.html.erb +3 -6
  157. data/app/views/layouts/decidim/admin/_header.html.erb +3 -1
  158. data/app/views/layouts/decidim/admin/_js_configuration.html.erb +11 -12
  159. data/app/views/layouts/decidim/admin/_sidebar_menu.html.erb +1 -1
  160. data/app/views/layouts/decidim/admin/_title_bar.html.erb +4 -4
  161. data/app/views/layouts/decidim/admin/_title_bar_responsive.html.erb +4 -4
  162. data/app/views/layouts/decidim/admin/insights.html.erb +6 -0
  163. data/config/assets.rb +2 -2
  164. data/config/locales/ar.yml +0 -90
  165. data/config/locales/bg.yml +1 -110
  166. data/config/locales/bs-BA.yml +0 -44
  167. data/config/locales/ca-IT.yml +59 -117
  168. data/config/locales/ca.yml +59 -117
  169. data/config/locales/cs.yml +51 -119
  170. data/config/locales/de.yml +60 -118
  171. data/config/locales/el.yml +2 -102
  172. data/config/locales/en.yml +61 -119
  173. data/config/locales/eo.yml +0 -4
  174. data/config/locales/es-MX.yml +61 -119
  175. data/config/locales/es-PY.yml +61 -119
  176. data/config/locales/es.yml +61 -119
  177. data/config/locales/eu.yml +61 -119
  178. data/config/locales/fi-plain.yml +60 -118
  179. data/config/locales/fi.yml +60 -118
  180. data/config/locales/fr-CA.yml +61 -119
  181. data/config/locales/fr.yml +61 -119
  182. data/config/locales/ga-IE.yml +1 -34
  183. data/config/locales/gl.yml +0 -95
  184. data/config/locales/he-IL.yml +0 -8
  185. data/config/locales/hu.yml +2 -105
  186. data/config/locales/id-ID.yml +0 -62
  187. data/config/locales/is-IS.yml +1 -36
  188. data/config/locales/it.yml +35 -116
  189. data/config/locales/ja.yml +60 -118
  190. data/config/locales/kaa.yml +0 -12
  191. data/config/locales/ko.yml +0 -75
  192. data/config/locales/lb.yml +2 -95
  193. data/config/locales/lt.yml +1 -103
  194. data/config/locales/lv.yml +2 -74
  195. data/config/locales/nl.yml +2 -96
  196. data/config/locales/no.yml +2 -96
  197. data/config/locales/pl.yml +2 -108
  198. data/config/locales/pt-BR.yml +6 -110
  199. data/config/locales/pt.yml +2 -96
  200. data/config/locales/ro-RO.yml +10 -102
  201. data/config/locales/ru.yml +2 -54
  202. data/config/locales/sk.yml +2 -72
  203. data/config/locales/sl.yml +1 -18
  204. data/config/locales/sq-AL.yml +0 -45
  205. data/config/locales/sr-CS.yml +1 -44
  206. data/config/locales/sv.yml +58 -119
  207. data/config/locales/th-TH.yml +0 -10
  208. data/config/locales/tr-TR.yml +5 -88
  209. data/config/locales/uk.yml +1 -45
  210. data/config/locales/val-ES.yml +0 -1
  211. data/config/locales/zh-CN.yml +0 -72
  212. data/config/locales/zh-TW.yml +1 -102
  213. data/config/routes.rb +1 -12
  214. data/lib/decidim/admin/engine.rb +5 -1
  215. data/lib/decidim/admin/form_builder.rb +1 -1
  216. data/lib/decidim/admin/menu.rb +32 -16
  217. data/lib/decidim/admin/test/filterable_examples.rb +8 -18
  218. data/lib/decidim/admin/test/invite_participatory_space_admins_shared_examples.rb +4 -4
  219. data/lib/decidim/admin/test/invite_participatory_space_collaborators_shared_examples.rb +2 -2
  220. data/lib/decidim/admin/test/invite_participatory_space_moderators_shared_examples.rb +9 -6
  221. data/lib/decidim/admin/test/invite_participatory_space_users_shared_context.rb +1 -0
  222. data/lib/decidim/admin/test/manage_attachment_collections_examples.rb +5 -1
  223. data/lib/decidim/admin/test/manage_attachments_examples.rb +5 -1
  224. data/lib/decidim/admin/test/manage_component_permissions_examples.rb +31 -4
  225. data/lib/decidim/admin/test/manage_moderations_examples.rb +12 -5
  226. data/lib/decidim/admin/test/manage_participatory_space_publications_examples.rb +12 -6
  227. data/lib/decidim/admin/test/manage_resource_soft_deletion_examples.rb +19 -4
  228. data/lib/decidim/admin/test/manage_taxonomy_filters_examples.rb +28 -8
  229. data/lib/decidim/admin/version.rb +1 -1
  230. metadata +22 -35
  231. data/app/commands/decidim/admin/process_user_group_verification_csv.rb +0 -44
  232. data/app/commands/decidim/admin/reject_user_group.rb +0 -42
  233. data/app/commands/decidim/admin/update_organization_appearance.rb +0 -39
  234. data/app/commands/decidim/admin/update_user_groups.rb +0 -44
  235. data/app/commands/decidim/admin/verify_user_group.rb +0 -43
  236. data/app/controllers/concerns/decidim/admin/user_groups/filterable.rb +0 -45
  237. data/app/controllers/concerns/decidim/admin/user_groups.rb +0 -24
  238. data/app/controllers/decidim/admin/metrics_controller.rb +0 -23
  239. data/app/controllers/decidim/admin/user_groups_controller.rb +0 -76
  240. data/app/controllers/decidim/admin/user_groups_csv_verifications_controller.rb +0 -36
  241. data/app/forms/decidim/admin/organization_appearance_form.rb +0 -88
  242. data/app/forms/decidim/admin/user_group_csv_verification_form.rb +0 -25
  243. data/app/jobs/decidim/admin/verify_user_group_from_csv_job.rb +0 -30
  244. data/app/packs/src/decidim/admin/welcome_notification.js +0 -32
  245. data/app/packs/stylesheets/decidim/admin/_bulk_actions.scss +0 -16
  246. data/app/presenters/decidim/admin/dashboard_metric_charts_presenter.rb +0 -33
  247. data/app/queries/decidim/admin/user_groups_evaluation.rb +0 -56
  248. data/app/views/decidim/admin/metrics/_metrics.html.erb +0 -16
  249. data/app/views/decidim/admin/metrics/index.html.erb +0 -7
  250. data/app/views/decidim/admin/organization_appearance/_form.html.erb +0 -100
  251. data/app/views/decidim/admin/organization_appearance/edit.html.erb +0 -18
  252. data/app/views/decidim/admin/organization_appearance/form/_header_snippets.html.erb +0 -17
  253. data/app/views/decidim/admin/user_groups/index.html.erb +0 -68
  254. data/app/views/decidim/admin/user_groups_csv_verifications/new.html.erb +0 -31
  255. /data/app/views/decidim/admin/{organization_appearance → organization}/form/_minimap.html.erb +0 -0
@@ -0,0 +1,239 @@
1
+ /* global jest */
2
+
3
+ import { Application } from "@hotwired/stimulus"
4
+ import SlugController from "./controller"
5
+
6
+ describe("SlugController", () => {
7
+ let application = null;
8
+ let controller = null;
9
+ let element = null;
10
+ let input = null;
11
+ let target = null;
12
+
13
+ beforeEach(() => {
14
+ // Set up the DOM structure
15
+ document.body.innerHTML = `
16
+ <div data-controller="slug" class="slug-wrapper">
17
+ <input type="text" placeholder="Enter slug" />
18
+ <span class="slug-url-value"></span>
19
+ </div>
20
+ `
21
+
22
+ element = document.querySelector('[data-controller="slug"]')
23
+ input = element.querySelector("input")
24
+ target = element.querySelector("span.slug-url-value")
25
+
26
+ // Set up Stimulus application
27
+ application = Application.start()
28
+ application.register("slug", SlugController)
29
+
30
+ return new Promise((resolve) => {
31
+ setTimeout(() => {
32
+ controller = application.getControllerForElementAndIdentifier(element, "slug")
33
+ resolve();
34
+ }, 0);
35
+ });
36
+ })
37
+
38
+ afterEach(() => {
39
+ controller.disconnect()
40
+ application.stop()
41
+ document.body.innerHTML = ""
42
+ })
43
+
44
+ describe("connect", () => {
45
+ it("finds and stores the input element", () => {
46
+ expect(controller.input).toBe(input)
47
+ })
48
+
49
+ it("finds and stores the target element", () => {
50
+ expect(controller.target).toBe(target)
51
+ })
52
+
53
+ it("adds keyup event listener to input when input exists", () => {
54
+ const addEventListenerSpy = jest.spyOn(input, "addEventListener")
55
+
56
+ controller.connect()
57
+
58
+ expect(addEventListenerSpy).toHaveBeenCalledWith("keyup", controller.boundUpdate)
59
+ })
60
+
61
+ it("binds the slugUpdater method correctly", () => {
62
+ controller.connect()
63
+
64
+ expect(controller.boundUpdate).toBeDefined()
65
+ expect(typeof controller.boundUpdate).toBe("function")
66
+ })
67
+
68
+ it("handles missing input gracefully", () => {
69
+ // Remove input from DOM
70
+ input.remove()
71
+
72
+ controller.disconnect()
73
+ controller.connect()
74
+
75
+ expect(() => controller.connect()).not.toThrow()
76
+ expect(controller.boundUpdate).toBeNull()
77
+ })
78
+ })
79
+
80
+ describe("disconnect", () => {
81
+ beforeEach(() => {
82
+ controller.connect()
83
+ })
84
+
85
+ it("removes keyup event listener when boundUpdate exists", () => {
86
+ const removeEventListenerSpy = jest.spyOn(controller.input, "removeEventListener")
87
+
88
+ let bound = controller.boundUpdate;
89
+
90
+ controller.disconnect()
91
+
92
+ expect(removeEventListenerSpy).toHaveBeenCalledWith("keyup", bound)
93
+ })
94
+
95
+ it("handles disconnect when boundUpdate is undefined", () => {
96
+ // eslint-disable-next-line no-undefined
97
+ controller.boundUpdate = undefined
98
+
99
+ expect(() => controller.disconnect()).not.toThrow()
100
+ })
101
+ })
102
+
103
+ describe("slugUpdater", () => {
104
+ beforeEach(() => {
105
+ controller.connect()
106
+ })
107
+
108
+ it("updates target innerHTML with input value", () => {
109
+ const mockEvent = {
110
+ target: {
111
+ value: "test-slug"
112
+ }
113
+ }
114
+
115
+ controller.slugUpdater(mockEvent)
116
+
117
+ expect(target.innerHTML).toBe("test-slug")
118
+ })
119
+
120
+ it("handles empty input value", () => {
121
+ const mockEvent = {
122
+ target: {
123
+ value: ""
124
+ }
125
+ }
126
+
127
+ controller.slugUpdater(mockEvent)
128
+
129
+ expect(target.innerHTML).toBe("")
130
+ })
131
+
132
+ it("handles special characters in input value", () => {
133
+ const mockEvent = {
134
+ target: {
135
+ value: "test-slug-with-special-chars-123"
136
+ }
137
+ }
138
+
139
+ controller.slugUpdater(mockEvent)
140
+
141
+ expect(target.innerHTML).toBe("test-slug-with-special-chars-123")
142
+ })
143
+ })
144
+
145
+ describe("integration tests", () => {
146
+ beforeEach(() => {
147
+ controller.connect()
148
+ })
149
+
150
+ it("updates target when typing in input", () => {
151
+ input.value = "my-new-slug"
152
+
153
+ // Simulate keyup event
154
+ const keyupEvent = new KeyboardEvent("keyup", { bubbles: true })
155
+ Reflect.defineProperty(keyupEvent, "target", {
156
+ value: input,
157
+ enumerable: true
158
+ })
159
+
160
+ input.dispatchEvent(keyupEvent)
161
+
162
+ expect(target.innerHTML).toBe("my-new-slug")
163
+ })
164
+
165
+ it("updates target multiple times as user types", () => {
166
+ const testValues = ["m", "my", "my-", "my-s", "my-sl", "my-slug"]
167
+
168
+ testValues.forEach((value) => {
169
+ input.value = value
170
+ const keyupEvent = new KeyboardEvent("keyup", { bubbles: true })
171
+ Reflect.defineProperty(keyupEvent, "target", {
172
+ value: input,
173
+ enumerable: true
174
+ })
175
+ input.dispatchEvent(keyupEvent)
176
+
177
+ expect(target.innerHTML).toBe(value)
178
+ })
179
+ })
180
+ })
181
+
182
+ describe("edge cases", () => {
183
+ it("handles DOM structure without input", async () => {
184
+ // Stop the current application to avoid conflicts
185
+ application.stop()
186
+
187
+ // Set up new DOM without input
188
+ document.body.innerHTML = `
189
+ <div data-controller="slug" class="slug-wrapper">
190
+ <span class="slug-url-value"></span>
191
+ </div>
192
+ `
193
+
194
+ // Start a fresh application
195
+ application = Application.start()
196
+ application.register("slug", SlugController)
197
+
198
+ const elementWithoutInput = document.querySelector('[data-controller="slug"]')
199
+ let controllerWithoutInput = null
200
+
201
+ await new Promise((resolve) => {
202
+ setTimeout(() => {
203
+ controllerWithoutInput = application.getControllerForElementAndIdentifier(elementWithoutInput, "slug")
204
+ resolve();
205
+ }, 0);
206
+ });
207
+
208
+ expect(() => controllerWithoutInput.connect()).not.toThrow()
209
+ expect(controllerWithoutInput.input).toBeNull()
210
+ })
211
+
212
+ it("handles DOM structure without target", async () => {
213
+ document.body.innerHTML = `
214
+ <div data-controller="slug" class="slug-wrapper">
215
+ <input type="text" placeholder="Enter slug" />
216
+ </div>
217
+ `
218
+
219
+ const elementWithoutTarget = document.querySelector('[data-controller="slug"]')
220
+ let controllerWithoutInput = null
221
+
222
+ await new Promise((resolve) => {
223
+ setTimeout(() => {
224
+ controllerWithoutInput = application.getControllerForElementAndIdentifier(elementWithoutTarget, "slug")
225
+ resolve();
226
+ }, 0);
227
+ });
228
+
229
+
230
+ controllerWithoutInput.connect()
231
+
232
+ expect(controllerWithoutInput.target).toBeNull()
233
+
234
+ // Should not throw when trying to update nonexistent target
235
+ const mockEvent = { target: { value: "test" } }
236
+ expect(() => controllerWithoutInput.slugUpdater(mockEvent)).toThrow()
237
+ })
238
+ })
239
+ })
@@ -23,7 +23,7 @@
23
23
  // for example:
24
24
  // strong[data-css-example]:color:#0000ff; strong[data-css-example]:backgroundColor:#eeeeee;
25
25
  //
26
- window.addEventListener("DOMContentLoaded", () => {
26
+ document.addEventListener("turbo:load", () => {
27
27
  document.querySelectorAll("input[data-css-preview=true]").forEach((element) => {
28
28
  element.addEventListener("change", (event) => {
29
29
  const updateRules = event.target.dataset.cssPreviewUpdates.split(";");
@@ -10,7 +10,7 @@ export default function createSortableList(lists) {
10
10
  }
11
11
 
12
12
  // Once in DOM
13
- $(() => {
13
+ document.addEventListener("turbo:load", () => {
14
14
  const $draggables = $(".draggable-list")
15
15
  let draggablesClassNames = []
16
16
 
@@ -9,7 +9,7 @@ import createSortList from "src/decidim/admin/sort_list.component"
9
9
  * - data-draggable-table: The table that will be draggable.
10
10
  * - data-sort-url: The URL where the order will be sent.
11
11
  */
12
- $(() => {
12
+ document.addEventListener("turbo:load", () => {
13
13
  createSortList("[data-draggable-table]", {
14
14
  onSortUpdate: ($children) => {
15
15
  const children = $children.toArray();
@@ -34,9 +34,12 @@ class DynamicFieldsComponent {
34
34
  }
35
35
 
36
36
  $.fn.template = function(placeholder, value) {
37
+ // Clean up the template HTML to ensure it only contains elements.
38
+ const $template = $(this).filter((_idx, el) => el.nodeType === Node.ELEMENT_NODE);
39
+
37
40
  // See the comment below in the `_addField()` method regarding the
38
41
  // `<template>` tag support in IE11.
39
- const $subtemplate = $(this).find("template, .decidim-template");
42
+ const $subtemplate = $template.find("template, .decidim-template");
40
43
 
41
44
  if ($subtemplate.length > 0) {
42
45
  $subtemplate.html((index, oldHtml) => $(oldHtml).template(placeholder, value)[0].outerHTML);
@@ -44,7 +47,7 @@ class DynamicFieldsComponent {
44
47
 
45
48
  // Handle those subtemplates that are mapped with the `data-template`
46
49
  // attribute. This is also because of the IE11 support.
47
- const $subtemplateParents = $(this).find("[data-template]");
50
+ const $subtemplateParents = $template.find("[data-template]");
48
51
 
49
52
  if ($subtemplateParents.length > 0) {
50
53
  $subtemplateParents.each((_i, elem) => {
@@ -64,15 +67,15 @@ class DynamicFieldsComponent {
64
67
  });
65
68
  }
66
69
 
67
- $(this).replaceAttribute("id", placeholder, value);
68
- $(this).replaceAttribute("name", placeholder, value);
69
- $(this).replaceAttribute("data-tabs-content", placeholder, value);
70
- $(this).replaceAttribute("for", placeholder, value);
71
- $(this).replaceAttribute("tabs_id", placeholder, value);
72
- $(this).replaceAttribute("href", placeholder, value);
73
- $(this).replaceAttribute("value", placeholder, value);
70
+ $template.replaceAttribute("id", placeholder, value);
71
+ $template.replaceAttribute("name", placeholder, value);
72
+ $template.replaceAttribute("data-tabs-content", placeholder, value);
73
+ $template.replaceAttribute("for", placeholder, value);
74
+ $template.replaceAttribute("tabs_id", placeholder, value);
75
+ $template.replaceAttribute("href", placeholder, value);
76
+ $template.replaceAttribute("value", placeholder, value);
74
77
 
75
- return this;
78
+ return $template;
76
79
  }
77
80
  }
78
81
 
@@ -147,9 +150,7 @@ class DynamicFieldsComponent {
147
150
  // as they are submitted with them.
148
151
  $template = $wrapper.children(`template, ${templateClass}`);
149
152
  }
150
-
151
153
  const $newField = $($template.html()).template(this.placeholderId, this._getUID());
152
-
153
154
  $newField.find("ul.tabs").attr("data-tabs", true);
154
155
 
155
156
  const $lastQuestion = $container.find(this.fieldSelector).last()
@@ -3,7 +3,7 @@ import AutoLabelByPositionComponent from "src/decidim/admin/auto_label_by_positi
3
3
  import createDynamicFields from "src/decidim/admin/dynamic_fields.component"
4
4
  import createSortList from "src/decidim/admin/sort_list.component"
5
5
 
6
- $(() => {
6
+ document.addEventListener("turbo:load", () => {
7
7
  const dynamicFieldDefinitions = [
8
8
  {
9
9
  placeHolderId: "external-domain-id",
@@ -4,12 +4,29 @@ import BudgetRuleTogglerComponent from "src/decidim/admin/budget_rule_toggler.co
4
4
 
5
5
  // Checks if the form contains fields with special CSS classes added in
6
6
  // Decidim::Admin::SettingsHelper and acts accordingly.
7
- $(() => {
8
- const budgetRuleToggler = new BudgetRuleTogglerComponent({
9
- ruleCheckboxes: $("input[id^='component_settings_vote_rule_']")
7
+ document.addEventListener("turbo:load", () => {
8
+
9
+ const budgetTogglerRadios = Array.from(
10
+ document.querySelectorAll(
11
+ "input[type='radio'][name='component[settings][voting_rule]']"
12
+ )
13
+ );
14
+
15
+ const budgetTogglerMapping = {
16
+ thresholdPercent: [".vote_threshold_percent_container"],
17
+ minimumProjects: [".vote_minimum_budget_projects_number_container"],
18
+ selectedProjects: [
19
+ ".vote_selected_projects_minimum_container",
20
+ ".vote_selected_projects_maximum_container"
21
+ ]
22
+ };
23
+
24
+ const budgetToggler = new BudgetRuleTogglerComponent({
25
+ ruleRadios: budgetTogglerRadios,
26
+ mapping: budgetTogglerMapping
10
27
  });
11
28
 
12
- budgetRuleToggler.run();
29
+ budgetToggler.init();
13
30
 
14
31
  // Prevents readonly containers from being modified.
15
32
  const $readonlyContainer = $(".readonly_container input");
@@ -2,7 +2,7 @@
2
2
  /* eslint no-unused-vars: 0 */
3
3
  /* eslint id-length: ["error", { "exceptions": ["e"] }] */
4
4
 
5
- document.addEventListener("DOMContentLoaded", () => {
5
+ document.addEventListener("turbo:load", () => {
6
6
  const selectedModerationsCount = () => {
7
7
  return document.querySelectorAll(".table-list .js-check-all-moderations:checked").length;
8
8
  };
@@ -69,7 +69,7 @@ document.addEventListener("DOMContentLoaded", () => {
69
69
  hideBulkActionForms();
70
70
  bulkActionsButton.classList.add("hide");
71
71
 
72
- document.querySelectorAll("#js-bulk-actions-dropdown ul li button").forEach((button) => {
72
+ document.querySelectorAll("#js-bulk-actions-dropdown li button").forEach((button) => {
73
73
  button.addEventListener("click", (event) => {
74
74
  const bulkActionsDropdown = document.getElementById("js-bulk-actions-dropdown");
75
75
  bulkActionsDropdown.classList.remove("is-open");
@@ -2,7 +2,7 @@
2
2
  /* eslint no-unused-vars: 0 */
3
3
  /* eslint id-length: ["error", { "exceptions": ["e"] }] */
4
4
 
5
- document.addEventListener("DOMContentLoaded", () => {
5
+ document.addEventListener("turbo:load", () => {
6
6
  const selectedModeratedUsersCount = () => {
7
7
  return document.querySelectorAll(".table-list .js-check-all-moderated_users:checked").length;
8
8
  };
@@ -69,7 +69,7 @@ document.addEventListener("DOMContentLoaded", () => {
69
69
  hideBulkActionForms();
70
70
  bulkActionsButton.classList.add("hide");
71
71
 
72
- document.querySelectorAll("#js-bulk-actions-dropdown ul li button").forEach((button) => {
72
+ document.querySelectorAll("#js-bulk-actions-dropdown li button").forEach((button) => {
73
73
  button.addEventListener("click", (event) => {
74
74
  const bulkActionsDropdown = document.getElementById("js-bulk-actions-dropdown");
75
75
  bulkActionsDropdown.classList.remove("is-open");
@@ -1,4 +1,4 @@
1
- $(() => {
1
+ document.addEventListener("turbo:load", () => {
2
2
  const $moderationDetails = $(".moderation-details");
3
3
  const $toggleContentButton = $moderationDetails.find(".toggle-content");
4
4
  const $reportedContent = $moderationDetails.find(".reported-content");
@@ -1,6 +1,6 @@
1
1
  import TomSelect from "tom-select/dist/cjs/tom-select.popular";
2
2
 
3
- document.addEventListener("DOMContentLoaded", () => {
3
+ document.addEventListener("turbo:load", () => {
4
4
  const isOnSelectRecipientsPage = window.location.pathname.includes("/select_recipients_to_deliver");
5
5
 
6
6
  const selectors = {
@@ -1,4 +1,4 @@
1
- $(() => {
1
+ document.addEventListener("turbo:load", () => {
2
2
  const $modal = $("#show-email-modal");
3
3
 
4
4
  if ($modal.length === 0) {
@@ -1,4 +1,4 @@
1
- import AutoComplete from "src/decidim/autocomplete";
1
+ import AutoComplete from "src/decidim/refactor/moved/autocomplete";
2
2
 
3
3
  /**
4
4
  * Sends a query to the API and resolves the resulting data in the returned
@@ -252,7 +252,7 @@ const createAutocomplete = (searchInput, spaces, inputIndex) => {
252
252
  return ac;
253
253
  }
254
254
 
255
- document.addEventListener("DOMContentLoaded", () => {
255
+ document.addEventListener("turbo:load", () => {
256
256
  const searchElements = document.querySelectorAll("input.participatory-space-search")
257
257
  if (searchElements.length < 1) {
258
258
  return;
@@ -1,4 +1,4 @@
1
- $(() => {
1
+ document.addEventListener("turbo:load", () => {
2
2
  const $limitedTimeRadioButton = $("#component_settings_proposal_edit_time_limited");
3
3
  const $infiniteTimeRadioButton = $("#component_settings_proposal_edit_time_infinite");
4
4
  const $editTimeContainer = $(".edit_time_container");
@@ -1,6 +1,6 @@
1
1
  import SubformMultiTogglerComponent from "src/decidim/admin/subform_multi_toggler.component"
2
2
 
3
- $(() => {
3
+ document.addEventListener("turbo:load", () => {
4
4
  const subformMultiToggler = new SubformMultiTogglerComponent({
5
5
  controllerSelect: $("input[name$=\\[authorization_handlers\\]\\[\\]]"),
6
6
  subformWrapperClass: "authorization-handler",
@@ -10,7 +10,7 @@ import sortable from "html5sortable/dist/html5sortable.es";
10
10
  console.log('The new order is:', event.target.children);
11
11
  });
12
12
  */
13
- window.addEventListener("DOMContentLoaded", () => {
13
+ document.addEventListener("turbo:load", () => {
14
14
  const draggables = document.querySelectorAll(".js-sortable");
15
15
 
16
16
  if (draggables) {
@@ -14,7 +14,7 @@
14
14
  // } %>
15
15
  // ```
16
16
  //
17
- window.addEventListener("DOMContentLoaded", () => {
17
+ document.addEventListener("turbo:load", () => {
18
18
  document.querySelectorAll("input[data-sync-radio-buttons=true]").forEach((element) => {
19
19
  element.addEventListener("change", (event) => {
20
20
  const value = event.target.dataset.syncRadioButtonsValue;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * When switching tabs in i18n fields, autofocus on the input to save clicks #212
3
3
  */
4
- $(() => {
4
+ document.addEventListener("turbo:load", () => {
5
5
  // Event launched by foundation
6
6
  $("[data-tabs]").on("change.zf.tabs", (event) => {
7
7
  const $container = $(event.target).parent().next(".tabs-content").find(".tabs-panel.is-active");
@@ -9,7 +9,7 @@
9
9
  // </div>
10
10
  // ```
11
11
  //
12
- window.addEventListener("DOMContentLoaded", () => {
12
+ document.addEventListener("turbo:load", () => {
13
13
  document.querySelectorAll("input[data-text-copy=true], textarea[data-text-copy=true]").forEach((element) => {
14
14
  element.addEventListener("change", (event) => {
15
15
  const target = document.querySelector(event.target.dataset.target);
@@ -93,7 +93,7 @@ const setTheme = (primary, saturation) => {
93
93
  (document.getElementById("preview-tertiary") || {}).value = tertiary;
94
94
  }
95
95
 
96
- document.addEventListener("DOMContentLoaded", () => {
96
+ document.addEventListener("turbo:load", () => {
97
97
  const selector = document.querySelector("#primary-selector")
98
98
 
99
99
  if (selector) {
@@ -15,11 +15,11 @@
15
15
 
16
16
  .datepicker {
17
17
  &__clock-button {
18
- @apply top-0;
18
+ @apply top-2;
19
19
  }
20
20
 
21
21
  &__calendar-button {
22
- @apply top-0;
22
+ @apply top-2;
23
23
  }
24
24
 
25
25
  &__pick-calendar {
@@ -8,11 +8,11 @@
8
8
  }
9
9
 
10
10
  .dropdown__trigger {
11
- @apply flex underline p-0;
11
+ @apply flex p-0;
12
12
  }
13
13
 
14
14
  .dropdown {
15
- @apply divide-y divide-gray-3 z-20;
15
+ @apply divide-y divide-gray-3;
16
16
 
17
17
  &.button {
18
18
  @apply z-10;
@@ -33,3 +33,13 @@
33
33
  }
34
34
  }
35
35
  }
36
+
37
+ .dropdown {
38
+ &[aria-hidden="true"] {
39
+ @apply hidden;
40
+ }
41
+
42
+ &[aria-hidden="false"] {
43
+ @apply block;
44
+ }
45
+ }
@@ -7,18 +7,6 @@
7
7
  }
8
8
  }
9
9
 
10
- .dropdown .is-dropdown-submenu a {
11
- @apply w-full inline-block;
12
- }
13
-
14
- .dropdown .is-dropdown-submenu li {
15
- @apply w-full relative;
16
- }
17
-
18
- .dropdown {
19
- @apply relative p-0 border-0;
20
- }
21
-
22
10
  .button,
23
11
  .input-group {
24
12
  @apply mb-0;
@@ -33,36 +21,7 @@
33
21
  }
34
22
 
35
23
  .input-group-button {
36
- @apply absolute right-4 top-0 h-full top-1;
37
- }
38
-
39
- .dropdown.menu {
40
- z-index: 5;
41
-
42
- li {
43
- @apply p-0;
44
- }
45
-
46
- .button {
47
- @apply drop-shadow-none;
48
- }
49
-
50
- & > li.is-dropdown-submenu-parent > a {
51
- @apply px-4 py-1 text-white bg-secondary flex items-center gap-x-2;
52
-
53
- &:hover {
54
- @apply text-white bg-secondary;
55
- }
56
- }
57
-
58
- li.is-active > a,
59
- li.is-active li.is-submenu-item > a:hover {
60
- @apply text-white bg-secondary;
61
- }
62
-
63
- .dropdown-filter-icon {
64
- @apply fill-white w-5 h-5 p-0;
65
- }
24
+ @apply absolute right-4 h-full top-[0.35rem];
66
25
  }
67
26
  }
68
27
 
@@ -75,20 +34,5 @@
75
34
  .type-icon {
76
35
  @apply align-top;
77
36
  }
78
-
79
- .action-icon {
80
- span {
81
- @apply ml-1;
82
- }
83
-
84
- &.action-icon--remove {
85
- color: #e66a5d;
86
- margin-left: 0.3rem;
87
-
88
- &:hover {
89
- color: #e66a5d;
90
- }
91
- }
92
- }
93
37
  }
94
38
  }
@@ -16,7 +16,7 @@
16
16
  input[type="week"],
17
17
  select,
18
18
  textarea {
19
- @apply mt-0;
19
+ @apply mt-2;
20
20
  }
21
21
 
22
22
  & label:not(&-checkbox-label),
@@ -24,10 +24,6 @@
24
24
  @apply text-sm;
25
25
  }
26
26
 
27
- .help-text {
28
- @apply mt-1;
29
- }
30
-
31
27
  .row .columns,
32
28
  .row.column {
33
29
  @apply mb-4;
@@ -43,11 +39,6 @@
43
39
  @apply block my-2;
44
40
  }
45
41
 
46
- textarea#component_step_settings_1_automatic_hashtags,
47
- textarea#component_step_settings_1_suggested_hashtags {
48
- @apply block w-full;
49
- }
50
-
51
42
  #choose-template label {
52
43
  @apply block my-2;
53
44
  }