decidim-core 0.31.7 → 0.32.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 (488) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -13
  3. data/app/cells/decidim/amendable/amend_button_card/show.erb +2 -2
  4. data/app/cells/decidim/amendable/amend_button_card_cell.rb +1 -1
  5. data/app/cells/decidim/author_cell.rb +4 -0
  6. data/app/cells/decidim/card_metadata_cell.rb +1 -1
  7. data/app/cells/decidim/comments_button_cell.rb +1 -9
  8. data/app/cells/decidim/content_blocks/announcement_settings_form/show.erb +5 -0
  9. data/app/cells/decidim/content_blocks/announcement_settings_form_cell.rb +17 -0
  10. data/app/cells/decidim/content_blocks/cta_settings_form/show.erb +1 -1
  11. data/app/cells/decidim/content_blocks/hero_settings_form/show.erb +2 -2
  12. data/app/cells/decidim/content_blocks/highlighted_content_banner_cell.rb +1 -1
  13. data/app/cells/decidim/content_blocks/highlighted_content_banner_settings_form/show.erb +1 -1
  14. data/app/cells/decidim/content_blocks/highlighted_participatory_spaces_cell.rb +1 -1
  15. data/app/cells/decidim/content_blocks/how_to_participate/show.erb +1 -1
  16. data/app/cells/decidim/content_blocks/html_cell.rb +1 -1
  17. data/app/cells/decidim/content_blocks/participatory_space_announcement_cell.rb +1 -3
  18. data/app/cells/decidim/content_blocks/participatory_space_hero_cell.rb +1 -5
  19. data/app/cells/decidim/content_blocks/participatory_space_hero_settings_form/show.erb +1 -1
  20. data/app/cells/decidim/content_blocks/static_page/section_cell.rb +1 -1
  21. data/app/cells/decidim/content_blocks/static_page/section_settings_form/show.erb +1 -3
  22. data/app/cells/decidim/content_blocks/static_page/summary_cell.rb +1 -1
  23. data/app/cells/decidim/content_blocks/static_page/summary_settings_form/show.erb +1 -3
  24. data/app/cells/decidim/content_blocks/static_page/two_pane_section_cell.rb +2 -2
  25. data/app/cells/decidim/content_blocks/static_page/two_pane_section_settings_form/show.erb +2 -4
  26. data/app/cells/decidim/data_consent/category.erb +38 -25
  27. data/app/cells/decidim/footer_topics/show.erb +8 -5
  28. data/app/cells/decidim/{participatory_space_private_user_cell.rb → member_cell.rb} +1 -1
  29. data/app/cells/decidim/onboarding_action_message_cell.rb +1 -0
  30. data/app/cells/decidim/profile/details.erb +1 -1
  31. data/app/cells/decidim/profile_cell.rb +1 -0
  32. data/app/cells/decidim/profile_member_of/show.erb +13 -0
  33. data/app/cells/decidim/profile_member_of_cell.rb +35 -0
  34. data/app/cells/decidim/resource_types_filter/show.erb +2 -2
  35. data/app/cells/decidim/share_widget/qr_code_modal.erb +1 -1
  36. data/app/cells/decidim/tags_cell.rb +1 -1
  37. data/app/cells/decidim/upload_modal_cell.rb +0 -5
  38. data/app/cells/decidim/versions_list/show.erb +25 -0
  39. data/app/commands/decidim/create_follow.rb +3 -5
  40. data/app/commands/decidim/create_user_report.rb +1 -1
  41. data/app/commands/decidim/destroy_account.rb +4 -15
  42. data/app/commands/decidim/gallery_methods.rb +107 -0
  43. data/app/commands/decidim/multiple_attachments_methods.rb +27 -28
  44. data/app/commands/decidim/search.rb +5 -5
  45. data/app/controllers/concerns/decidim/ajax_permission_handler.rb +1 -1
  46. data/app/controllers/concerns/decidim/devise_controllers.rb +0 -10
  47. data/app/controllers/concerns/decidim/direct_upload.rb +3 -2
  48. data/app/controllers/concerns/decidim/ephemeral_session_checker.rb +1 -1
  49. data/app/controllers/concerns/decidim/force_authentication.rb +10 -1
  50. data/app/controllers/concerns/decidim/impersonate_users.rb +4 -0
  51. data/app/controllers/concerns/decidim/locale_switcher.rb +52 -2
  52. data/app/controllers/concerns/decidim/needs_tos_accepted.rb +1 -1
  53. data/app/controllers/concerns/decidim/participatory_space/has_members_page.rb +27 -0
  54. data/app/controllers/concerns/decidim/participatory_space_context.rb +1 -5
  55. data/app/controllers/decidim/account_controller.rb +1 -1
  56. data/app/controllers/decidim/amendments_controller.rb +5 -4
  57. data/app/controllers/decidim/components/base_controller.rb +20 -3
  58. data/app/controllers/decidim/devise/omniauth_registrations_controller.rb +1 -1
  59. data/app/controllers/decidim/devise/passwords_controller.rb +1 -1
  60. data/app/controllers/decidim/devise/registrations_controller.rb +1 -1
  61. data/app/controllers/decidim/devise/sessions_controller.rb +1 -1
  62. data/app/controllers/decidim/download_your_data_controller.rb +2 -2
  63. data/app/controllers/decidim/editor_images_controller.rb +1 -1
  64. data/app/controllers/decidim/follows_controller.rb +4 -3
  65. data/app/controllers/decidim/geolocation_controller.rb +1 -1
  66. data/app/controllers/decidim/likes_controller.rb +2 -1
  67. data/app/controllers/decidim/locales_controller.rb +3 -11
  68. data/app/controllers/decidim/messaging/conversations_controller.rb +3 -3
  69. data/app/controllers/decidim/newsletters_controller.rb +1 -1
  70. data/app/controllers/decidim/notifications_settings_controller.rb +1 -1
  71. data/app/controllers/decidim/notifications_subscriptions_controller.rb +0 -8
  72. data/app/controllers/decidim/open_data_controller.rb +1 -1
  73. data/app/controllers/decidim/report_users_controller.rb +2 -2
  74. data/app/controllers/decidim/reports_controller.rb +1 -1
  75. data/app/controllers/decidim/user_activities_controller.rb +1 -2
  76. data/app/events/decidim/amendable/amendment_base_event.rb +1 -7
  77. data/app/events/decidim/welcome_notification_event.rb +1 -1
  78. data/app/forms/decidim/upload_validation_form.rb +1 -1
  79. data/app/helpers/concerns/decidim/flash_helper_extensions.rb +1 -0
  80. data/app/helpers/decidim/active_link_to_helper.rb +136 -0
  81. data/app/helpers/decidim/amendments_helper.rb +3 -3
  82. data/app/helpers/decidim/check_boxes_tree_helper.rb +2 -2
  83. data/app/helpers/decidim/component_path_helper.rb +9 -4
  84. data/app/helpers/decidim/filters_helper.rb +0 -2
  85. data/app/helpers/decidim/menu_helper.rb +5 -3
  86. data/app/helpers/decidim/meta_tags_helper.rb +0 -2
  87. data/app/helpers/decidim/paginate_helper.rb +14 -1
  88. data/app/helpers/decidim/sanitize_helper.rb +1 -0
  89. data/app/helpers/decidim/short_link_helper.rb +5 -5
  90. data/app/helpers/decidim/translations_helper.rb +4 -4
  91. data/app/jobs/decidim/delete_inactive_participants_job.rb +2 -2
  92. data/app/jobs/decidim/process_inactive_participant_job.rb +7 -0
  93. data/app/mailers/decidim/application_mailer.rb +7 -2
  94. data/app/mailers/decidim/decidim_devise_mailer.rb +1 -0
  95. data/app/mailers/decidim/participants_account_mailer.rb +16 -0
  96. data/app/mailers/decidim/reported_mailer.rb +4 -1
  97. data/app/models/decidim/action_log.rb +1 -1
  98. data/app/models/decidim/attachment.rb +2 -20
  99. data/app/models/decidim/authorization.rb +0 -7
  100. data/app/models/decidim/authorization_transfer.rb +1 -1
  101. data/app/models/decidim/component.rb +6 -9
  102. data/app/models/decidim/content_block.rb +3 -3
  103. data/app/models/decidim/moderation.rb +1 -1
  104. data/app/models/decidim/newsletter.rb +2 -2
  105. data/app/models/decidim/participatory_space/member.rb +55 -0
  106. data/app/models/decidim/private_export.rb +0 -6
  107. data/app/models/decidim/resource_link.rb +1 -1
  108. data/app/models/decidim/short_link.rb +1 -1
  109. data/app/models/decidim/user.rb +2 -2
  110. data/app/models/decidim/user_base_entity.rb +2 -17
  111. data/app/models/decidim/user_moderation.rb +1 -1
  112. data/app/packs/src/decidim/controllers/accordion/accordion.test.js +42 -196
  113. data/app/packs/src/decidim/controllers/accordion/controller.js +17 -93
  114. data/app/packs/src/decidim/controllers/assign_role/assign_role.test.js +80 -0
  115. data/app/packs/src/decidim/controllers/assign_role/controller.js +27 -0
  116. data/app/packs/src/decidim/controllers/dropdown/controller.js +0 -6
  117. data/app/packs/src/decidim/controllers/dropdown/dropdown.test.js +0 -28
  118. data/app/packs/src/decidim/controllers/form_validator/form_validator.js +6 -6
  119. data/app/packs/src/decidim/controllers/form_validator/form_validator.test.js +1 -23
  120. data/app/packs/src/decidim/controllers/main_menu/controller.js +115 -0
  121. data/app/packs/src/decidim/controllers/main_menu/main_menu.test.js +185 -0
  122. data/app/packs/src/decidim/controllers/mention/controller.js +140 -296
  123. data/app/packs/src/decidim/controllers/mention/input_mentions.test.js +457 -120
  124. data/app/packs/src/decidim/controllers/multiple_mentions/controller.js +32 -68
  125. data/app/packs/src/decidim/controllers/multiple_mentions/input_multiple_mentions.test.js +23 -30
  126. data/app/packs/src/decidim/controllers/password_toggler/controller.js +1 -22
  127. data/app/packs/src/decidim/controllers/password_toggler/password_toggler.test.js +20 -11
  128. data/app/packs/src/decidim/datepicker/generate_datepicker.js +1 -13
  129. data/app/packs/src/decidim/direct_uploads/upload_field.js +4 -4
  130. data/app/packs/src/decidim/direct_uploads/upload_modal.js +7 -12
  131. data/app/packs/src/decidim/editor/common/suggestion.js +1 -1
  132. data/app/packs/src/decidim/editor/extensions/decidim_kit/index.js +4 -1
  133. data/app/packs/src/decidim/editor/extensions/image/index.js +8 -5
  134. data/app/packs/src/decidim/editor/extensions/link/index.js +8 -0
  135. data/app/packs/src/decidim/editor/extensions/mention/index.js +18 -5
  136. data/app/packs/src/decidim/editor/extensions/mention_resource/index.js +16 -7
  137. data/app/packs/src/decidim/editor/test/extensions/decidim_kit.test.js +13 -4
  138. data/app/packs/src/decidim/editor/test/extensions/heading.test.js +1 -1
  139. data/app/packs/src/decidim/editor/test/extensions/image.test.js +26 -28
  140. data/app/packs/src/decidim/editor/test/extensions/mention.test.js +6 -2
  141. data/app/packs/src/decidim/editor/test/extensions/video_embed.test.js +27 -15
  142. data/app/packs/src/decidim/editor/test/helpers.js +8 -10
  143. data/app/packs/src/decidim/editor/test/toolbar/full.test.js +15 -4
  144. data/app/packs/src/decidim/editor/test/toolbar/shared/behaves_like_basic_block.js +8 -4
  145. data/app/packs/src/decidim/editor/test/toolbar/shared/behaves_like_basic_formatting.js +5 -1
  146. data/app/packs/src/decidim/editor/test/toolbar/shared/behaves_like_basic_indent.js +9 -5
  147. data/app/packs/src/decidim/editor/test/toolbar/shared/behaves_like_basic_link.js +5 -1
  148. data/app/packs/src/decidim/editor/test/toolbar/shared/behaves_like_basic_list.js +8 -4
  149. data/app/packs/src/decidim/editor/test/toolbar/shared/behaves_like_basic_styling.js +13 -9
  150. data/app/packs/src/decidim/editor/test/toolbar/shared/behaves_like_content_styling.js +6 -2
  151. data/app/packs/src/decidim/geocoding/reverse_geocoding.js +5 -15
  152. data/app/packs/src/decidim/index.js +3 -5
  153. data/app/packs/src/decidim/map/controller.js +1 -10
  154. data/app/packs/src/decidim/refactor/moved/focus_guard.js +12 -12
  155. data/app/packs/src/decidim/sw/push-permissions.js +13 -48
  156. data/app/packs/src/decidim/sw/sw.js +1 -1
  157. data/app/packs/src/decidim/utilities/text.js +6 -6
  158. data/app/packs/src/decidim/vendor/tribute.js +1890 -0
  159. data/app/packs/stylesheets/decidim/_cards.scss +3 -2
  160. data/app/packs/stylesheets/decidim/_conversations.scss +0 -14
  161. data/app/packs/stylesheets/decidim/_cookies.scss +5 -13
  162. data/app/packs/stylesheets/decidim/_datepicker.scss +0 -8
  163. data/app/packs/stylesheets/decidim/_documents.scss +1 -1
  164. data/app/packs/stylesheets/decidim/_dropdown.scss +21 -31
  165. data/app/packs/stylesheets/decidim/_floating_help.scss +1 -1
  166. data/app/packs/stylesheets/decidim/_footer.scss +1 -69
  167. data/app/packs/stylesheets/decidim/_forms.scss +5 -1
  168. data/app/packs/stylesheets/decidim/_header.scss +148 -46
  169. data/app/packs/stylesheets/decidim/_language_chooser.scss +79 -0
  170. data/app/packs/stylesheets/decidim/_participatory_spaces.scss +1 -1
  171. data/app/packs/stylesheets/decidim/_tribute.scss +36 -0
  172. data/app/packs/stylesheets/decidim/application.scss +1 -2
  173. data/app/packs/stylesheets/decidim/editor.scss +33 -2
  174. data/app/packs/stylesheets/decidim/geocoding_addons.scss +2 -10
  175. data/app/presenters/decidim/admin_log/participatory_space/member_presenter.rb +40 -0
  176. data/app/presenters/decidim/admin_log/static_page_resource_presenter.rb +1 -1
  177. data/app/presenters/decidim/authorization_transfer_presenter.rb +1 -1
  178. data/app/presenters/decidim/breadcrumb_root_menu_item_presenter.rb +3 -3
  179. data/app/presenters/decidim/footer_menu_presenter.rb +4 -2
  180. data/app/presenters/decidim/log/user_presenter.rb +1 -0
  181. data/app/presenters/decidim/log/value_types/access_mode_presenter.rb +45 -0
  182. data/app/presenters/decidim/menu_item_presenter.rb +3 -9
  183. data/app/presenters/decidim/participatory_space/member_presenter.rb +48 -0
  184. data/app/presenters/decidim/resource_locator_presenter.rb +22 -3
  185. data/app/presenters/decidim/stats_presenter.rb +1 -1
  186. data/app/queries/decidim/last_activity.rb +1 -1
  187. data/app/resolvers/decidim/meta_image_url_resolver.rb +0 -7
  188. data/app/serializers/decidim/exporters/participatory_space_serializer.rb +15 -1
  189. data/app/services/decidim/email_notification_generator.rb +2 -0
  190. data/app/services/decidim/notification_generator.rb +4 -0
  191. data/app/services/decidim/notifications_subscriptions_persistor.rb +0 -6
  192. data/app/services/decidim/send_push_notification.rb +1 -5
  193. data/app/uploaders/decidim/image_uploader.rb +1 -1
  194. data/app/uploaders/decidim/organization_favicon_uploader.rb +4 -9
  195. data/app/validators/uploader_image_dimensions_validator.rb +13 -11
  196. data/app/views/decidim/gamification/badges/index.html.erb +1 -1
  197. data/app/views/decidim/homepage/show.html.erb +2 -2
  198. data/app/views/decidim/last_activities/index.html.erb +1 -1
  199. data/app/views/decidim/messaging/conversations/_error_modal.html.erb +19 -11
  200. data/app/views/decidim/messaging/conversations/_new_conversation_button.html.erb +1 -1
  201. data/app/views/decidim/messaging/conversations/_reply.html.erb +1 -1
  202. data/app/views/decidim/messaging/conversations/error.js.erb +7 -12
  203. data/app/views/decidim/messaging/conversations/new.html.erb +1 -1
  204. data/app/views/decidim/messaging/conversations/show.html.erb +2 -2
  205. data/app/views/decidim/newsletters/unsubscribe.html.erb +1 -1
  206. data/app/views/decidim/notifications_settings/show.html.erb +5 -5
  207. data/app/views/decidim/offline/show.html.erb +1 -1
  208. data/app/views/decidim/pages/index.html.erb +1 -1
  209. data/app/views/decidim/participants_account_mailer/removal_notification.html.erb +11 -0
  210. data/app/views/decidim/participatory_space/members/_member.html.erb +1 -0
  211. data/app/views/decidim/profiles/show.html.erb +1 -1
  212. data/app/views/decidim/searches/_filters.html.erb +1 -1
  213. data/app/views/decidim/shared/_resource_actions.html.erb +4 -4
  214. data/app/views/decidim/shared/_results_per_page.html.erb +1 -1
  215. data/app/views/decidim/shared/filters/_check_boxes_tree.html.erb +1 -1
  216. data/app/views/decidim/shared/filters/_dropdown_label.html.erb +4 -3
  217. data/app/views/decidim/user_activities/index.html.erb +1 -1
  218. data/app/views/devise/mailer/{invite_private_user.html.erb → invite_member.html.erb} +2 -2
  219. data/app/views/devise/mailer/{invite_private_user.text.erb → invite_member.text.erb} +2 -2
  220. data/app/views/layouts/decidim/_head.html.erb +2 -2
  221. data/app/views/layouts/decidim/_wrapper.html.erb +3 -3
  222. data/app/views/layouts/decidim/footer/_focus_mode_main.html.erb +0 -1
  223. data/app/views/layouts/decidim/footer/_main.html.erb +1 -2
  224. data/app/views/layouts/decidim/footer/_main_links.html.erb +28 -56
  225. data/app/views/layouts/decidim/header/_focus_mode_back_button.html.erb +4 -1
  226. data/app/views/layouts/decidim/header/_main.html.erb +17 -8
  227. data/app/views/layouts/decidim/header/_main_language_chooser.html.erb +24 -0
  228. data/app/views/layouts/decidim/header/_main_links_desktop.html.erb +56 -56
  229. data/app/views/layouts/decidim/header/_main_links_mobile_account.html.erb +2 -2
  230. data/app/views/layouts/decidim/header/_main_links_mobile_item_account.html.erb +3 -1
  231. data/app/views/layouts/decidim/header/_main_menu_mobile.html.erb +18 -5
  232. data/app/views/layouts/decidim/header/_main_search.html.erb +3 -3
  233. data/app/views/layouts/decidim/header/_menu.html.erb +5 -5
  234. data/app/views/layouts/decidim/header/_menu_breadcrumb_desktop.html.erb +13 -12
  235. data/app/views/layouts/decidim/header/_menu_breadcrumb_mobile.html.erb +28 -0
  236. data/app/views/layouts/decidim/shared/_layout_center.html.erb +1 -1
  237. data/app/views/layouts/decidim/shared/_layout_item.html.erb +1 -1
  238. data/app/views/layouts/decidim/shared/_layout_two_col.html.erb +1 -1
  239. data/config/initializers/invisible_captcha.rb +1 -0
  240. data/config/locales/ar.yml +29 -12
  241. data/config/locales/bg.yml +31 -14
  242. data/config/locales/bs-BA.yml +0 -1
  243. data/config/locales/ca-IT.yml +68 -60
  244. data/config/locales/ca.yml +68 -60
  245. data/config/locales/cs.yml +56 -37
  246. data/config/locales/da.yml +0 -1
  247. data/config/locales/de.yml +58 -27
  248. data/config/locales/el.yml +26 -9
  249. data/config/locales/en.yml +64 -55
  250. data/config/locales/eo.yml +0 -1
  251. data/config/locales/es-MX.yml +67 -59
  252. data/config/locales/es-PY.yml +67 -59
  253. data/config/locales/es.yml +67 -59
  254. data/config/locales/et.yml +0 -1
  255. data/config/locales/eu.yml +104 -94
  256. data/config/locales/fa-IR.yml +0 -1
  257. data/config/locales/fi-plain.yml +63 -60
  258. data/config/locales/fi.yml +65 -62
  259. data/config/locales/fr-CA.yml +63 -39
  260. data/config/locales/fr.yml +63 -39
  261. data/config/locales/ga-IE.yml +3 -2
  262. data/config/locales/gl.yml +26 -9
  263. data/config/locales/gn-PY.yml +0 -1
  264. data/config/locales/hr.yml +0 -1
  265. data/config/locales/hu.yml +26 -12
  266. data/config/locales/id-ID.yml +25 -9
  267. data/config/locales/is-IS.yml +7 -2
  268. data/config/locales/it.yml +24 -23
  269. data/config/locales/ja.yml +65 -75
  270. data/config/locales/ka-GE.yml +0 -1
  271. data/config/locales/kaa.yml +0 -1
  272. data/config/locales/ko.yml +0 -1
  273. data/config/locales/lb.yml +27 -10
  274. data/config/locales/lt.yml +28 -12
  275. data/config/locales/lv.yml +25 -9
  276. data/config/locales/mt.yml +0 -1
  277. data/config/locales/nl.yml +27 -10
  278. data/config/locales/no.yml +27 -10
  279. data/config/locales/oc-FR.yml +0 -1
  280. data/config/locales/pl.yml +33 -18
  281. data/config/locales/pt-BR.yml +61 -26
  282. data/config/locales/pt.yml +27 -10
  283. data/config/locales/ro-RO.yml +43 -21
  284. data/config/locales/ru.yml +8 -7
  285. data/config/locales/sk.yml +25 -1409
  286. data/config/locales/sl.yml +0 -1
  287. data/config/locales/sr-CS.yml +0 -1
  288. data/config/locales/sv.yml +72 -30
  289. data/config/locales/tr-TR.yml +28 -10
  290. data/config/locales/uk.yml +6 -4
  291. data/config/locales/vi.yml +0 -1
  292. data/config/locales/zh-CN.yml +25 -9
  293. data/config/locales/zh-TW.yml +28 -10
  294. data/config/routes.rb +138 -77
  295. data/db/data/20251213075429_rename_members_in_action_log.rb +22 -0
  296. data/db/data/20260319145808_rename_send_to_members_in_newsletter.rb +27 -0
  297. data/db/migrate/20200730142511_add_file_upload_settings_to_decidim_organizations.rb +13 -15
  298. data/db/migrate/20250217192438_convert_user_groups_into_users.rb +2 -3
  299. data/db/migrate/20250523104311_move_cta_to_hero_content_block.rb +1 -1
  300. data/db/migrate/20250527122040_move_highlighted_content_banner_settings_to_content_block.rb +1 -1
  301. data/db/migrate/20250819110800_convert_private_exports_id_to_uuid.rb +2 -2
  302. data/db/migrate/20251112132305_remove_legacy_images_from_core_module.rb +27 -0
  303. data/db/migrate/20251205122428_rename_participatory_space_private_users_to_members.rb +9 -0
  304. data/db/migrate/20251216185133_rename_privatable_to_to_participatory_space_in_members.rb +10 -0
  305. data/db/migrate/20260208201405_remove_user_group_core.rb +21 -0
  306. data/db/migrate/20260208201406_remove_user_group_memberships.rb +30 -0
  307. data/db/migrate/20260208201407_remove_user_group_organizations.rb +11 -0
  308. data/db/migrate/20260217152425_remove_enable_participatory_space_filters_from_decidim_organizations.rb +7 -0
  309. data/db/migrate/20260314081619_add_index_on_decidim_members.rb +25 -0
  310. data/decidim-core.gemspec +25 -31
  311. data/lib/decidim/acts_as_tree.rb +12 -12
  312. data/lib/decidim/admin_filters_registry.rb +1 -1
  313. data/lib/decidim/amendable.rb +5 -1
  314. data/lib/decidim/api/functions/category_list.rb +1 -0
  315. data/lib/decidim/api/functions/component_finder_base.rb +1 -1
  316. data/lib/decidim/api/functions/component_list.rb +3 -1
  317. data/lib/decidim/api/functions/component_list_base.rb +1 -0
  318. data/lib/decidim/api/functions/participatory_space_finder_base.rb +1 -1
  319. data/lib/decidim/api/functions/participatory_space_list_base.rb +1 -0
  320. data/lib/decidim/api/functions/user_entity_list.rb +0 -3
  321. data/lib/decidim/api/interfaces/amendable_entity_interface.rb +1 -0
  322. data/lib/decidim/api/interfaces/amendable_interface.rb +1 -0
  323. data/lib/decidim/api/interfaces/attachable_collection_interface.rb +1 -0
  324. data/lib/decidim/api/interfaces/attachable_interface.rb +1 -0
  325. data/lib/decidim/api/interfaces/author_interface.rb +2 -1
  326. data/lib/decidim/api/interfaces/authorable_interface.rb +1 -0
  327. data/lib/decidim/api/interfaces/categories_container_interface.rb +1 -0
  328. data/lib/decidim/api/interfaces/categorizable_interface.rb +1 -0
  329. data/lib/decidim/api/interfaces/coauthorable_interface.rb +1 -0
  330. data/lib/decidim/api/interfaces/component_interface.rb +9 -8
  331. data/lib/decidim/api/interfaces/fingerprint_interface.rb +1 -0
  332. data/lib/decidim/api/interfaces/followable_interface.rb +2 -0
  333. data/lib/decidim/api/interfaces/likeable_interface.rb +2 -2
  334. data/lib/decidim/api/interfaces/localizable_interface.rb +1 -0
  335. data/lib/decidim/api/interfaces/participatory_space_interface.rb +1 -0
  336. data/lib/decidim/api/interfaces/referable_interface.rb +1 -0
  337. data/lib/decidim/api/interfaces/scopable_interface.rb +1 -0
  338. data/lib/decidim/api/interfaces/taxonomizable_interface.rb +1 -0
  339. data/lib/decidim/api/interfaces/timestamps_interface.rb +1 -0
  340. data/lib/decidim/api/interfaces/traceable_interface.rb +1 -0
  341. data/lib/decidim/api/types/attachment_collection_type.rb +5 -3
  342. data/lib/decidim/api/types/attachment_type.rb +11 -4
  343. data/lib/decidim/api/types/badge_score_type.rb +28 -0
  344. data/lib/decidim/api/types/moderation_type.rb +25 -0
  345. data/lib/decidim/api/types/participant_details_type.rb +17 -0
  346. data/lib/decidim/api/types/reportable_type.rb +18 -0
  347. data/lib/decidim/api/types/reportable_user_type.rb +17 -0
  348. data/lib/decidim/api/types/static_page_topic_type.rb +16 -0
  349. data/lib/decidim/api/types/static_page_type.rb +22 -0
  350. data/lib/decidim/api/types/taxonomy_type.rb +2 -0
  351. data/lib/decidim/api/types/user_moderation_type.rb +36 -0
  352. data/lib/decidim/api/types/user_type.rb +13 -0
  353. data/lib/decidim/asset_router/pipeline.rb +1 -1
  354. data/lib/decidim/asset_router/storage.rb +9 -9
  355. data/lib/decidim/assets/tailwind/tailwind.config.js.erb +3 -0
  356. data/lib/decidim/attachment_attributes.rb +9 -58
  357. data/lib/decidim/attribute_encryptor.rb +51 -10
  358. data/lib/decidim/attribute_object/model.rb +0 -5
  359. data/lib/decidim/block_registry.rb +1 -1
  360. data/lib/decidim/coauthorable.rb +2 -1
  361. data/lib/decidim/command.rb +2 -1
  362. data/lib/decidim/content_processor.rb +1 -1
  363. data/lib/decidim/content_renderers/base_renderer.rb +0 -112
  364. data/lib/decidim/content_renderers/blob_renderer.rb +7 -4
  365. data/lib/decidim/content_renderers/mention_resource_renderer.rb +6 -10
  366. data/lib/decidim/content_renderers/resource_renderer.rb +7 -16
  367. data/lib/decidim/content_renderers/user_renderer.rb +9 -11
  368. data/lib/decidim/core/api.rb +8 -0
  369. data/lib/decidim/core/content_blocks/registry_manager.rb +4 -10
  370. data/lib/decidim/core/engine.rb +2 -42
  371. data/lib/decidim/core/menu.rb +0 -26
  372. data/lib/decidim/core/test/factories.rb +29 -21
  373. data/lib/decidim/core/test/shared_examples/access_mode_restricted_participatory_spaces.rb +172 -0
  374. data/lib/decidim/core/test/shared_examples/access_mode_transparent_participatory_spaces.rb +241 -0
  375. data/lib/decidim/core/test/shared_examples/admin_resource_gallery_examples.rb +10 -10
  376. data/lib/decidim/core/test/shared_examples/amendable/amendment_event_examples.rb +2 -2
  377. data/lib/decidim/core/test/shared_examples/amendable/amendment_promoted_event_examples.rb +3 -3
  378. data/lib/decidim/core/test/shared_examples/comments_examples.rb +121 -143
  379. data/lib/decidim/core/test/shared_examples/fingerprint_examples.rb +2 -0
  380. data/lib/decidim/core/test/shared_examples/has_members.rb +63 -0
  381. data/lib/decidim/core/test/shared_examples/has_space_in_mcell_examples.rb +1 -2
  382. data/lib/decidim/core/test/shared_examples/manage_share_tokens_examples.rb +1 -1
  383. data/lib/decidim/core/test/shared_examples/map_examples.rb +1 -1
  384. data/lib/decidim/core/test/shared_examples/participant_details_examples.rb +16 -0
  385. data/lib/decidim/core/test/shared_examples/participatory_space_members_page_examples.rb +10 -10
  386. data/lib/decidim/core/test/shared_examples/resource_liked_event_examples.rb +3 -3
  387. data/lib/decidim/core/test/shared_examples/resource_locator_presenter_examples.rb +7 -7
  388. data/lib/decidim/core/test/shared_examples/searchable_participatory_space_examples.rb +3 -3
  389. data/lib/decidim/core/test/shared_examples/searchable_results_examples.rb +2 -2
  390. data/lib/decidim/core/test/shared_examples/simple_event.rb +1 -0
  391. data/lib/decidim/core/test/shared_examples/translated_event_examples.rb +2 -2
  392. data/lib/decidim/core/test/shared_examples/versions_controller_examples.rb +1 -1
  393. data/lib/decidim/core/test.rb +2 -1
  394. data/lib/decidim/core/version.rb +1 -1
  395. data/lib/decidim/core.rb +222 -335
  396. data/lib/decidim/download_your_data_serializers/{download_your_data_participatory_space_private_user_serializer.rb → download_your_data_member_serializer.rb} +6 -6
  397. data/lib/decidim/download_your_data_serializers.rb +2 -2
  398. data/lib/decidim/engine_router.rb +31 -6
  399. data/lib/decidim/exporters/csv.rb +1 -1
  400. data/lib/decidim/filter_form_builder.rb +1 -1
  401. data/{app/controllers/concerns → lib}/decidim/form_factory.rb +11 -1
  402. data/lib/decidim/has_settings.rb +2 -2
  403. data/lib/decidim/legacy_form_builder.rb +1 -0
  404. data/lib/decidim/locale_router_detector.rb +41 -0
  405. data/lib/decidim/maintenance/import_models/scope.rb +1 -1
  406. data/lib/decidim/maintenance/import_models.rb +0 -2
  407. data/lib/decidim/map/autocomplete.rb +3 -4
  408. data/lib/decidim/map/dynamic_map.rb +1 -3
  409. data/lib/decidim/map/frontend.rb +1 -1
  410. data/lib/decidim/map/geocoding.rb +2 -2
  411. data/lib/decidim/map/static_map.rb +10 -22
  412. data/lib/decidim/map/utility.rb +3 -6
  413. data/lib/decidim/map.rb +2 -2
  414. data/lib/decidim/menu.rb +1 -1
  415. data/lib/decidim/menu_item.rb +1 -0
  416. data/lib/decidim/menu_registry.rb +1 -1
  417. data/lib/decidim/moderation_tools.rb +1 -1
  418. data/lib/decidim/newsletter_encryptor.rb +5 -8
  419. data/lib/decidim/organization_settings.rb +2 -2
  420. data/lib/decidim/participable.rb +1 -0
  421. data/lib/decidim/participatory_space/has_members.rb +61 -0
  422. data/lib/decidim/participatory_space_manifest.rb +0 -4
  423. data/lib/decidim/participatory_space_resourceable.rb +10 -1
  424. data/lib/decidim/routes/locale_redirects.rb +47 -0
  425. data/lib/decidim/routes.rb +7 -0
  426. data/lib/decidim/search_resource_fields_mapper.rb +3 -4
  427. data/lib/decidim/searchable.rb +0 -5
  428. data/lib/decidim/seeds.rb +17 -0
  429. data/lib/decidim/shakapacker/runner.rb +4 -4
  430. data/lib/decidim/shakapacker/shakapacker.yml +28 -3
  431. data/lib/decidim/taxonomizable.rb +1 -1
  432. data/lib/decidim/translatable_attributes.rb +9 -3
  433. data/lib/decidim/view_model.rb +5 -1
  434. data/lib/devise/models/decidim_newsletterable.rb +1 -1
  435. data/lib/tasks/decidim_mailers_tasks.rake +9 -31
  436. data/lib/tasks/decidim_procfile.rake +1 -1
  437. data/lib/tasks/decidim_tasks.rake +0 -1
  438. data/lib/tasks/decidim_taxonomies.rake +1 -1
  439. data/lib/tasks/upgrade/decidim_active_storage_migration_tasks.rake +0 -1
  440. data/lib/tasks/upgrade/decidim_encryption.rake +12 -0
  441. data/lib/tasks/upgrade/decidim_migrate_wysiwyg_content.rake +0 -1
  442. data/lib/tasks/upgrade/decidim_remove_deleted_users_left_data_tasks.rake +1 -1
  443. data/lib/tasks/upgrade/{fix_deleted_private_follows.rake → fix_deleted_members_follows.rake} +5 -5
  444. data/lib/tasks/upgrade/migrations.rake +0 -1
  445. data/lib/tasks/upgrade/user_groups_migration.rake +0 -1
  446. metadata +206 -155
  447. data/app/cells/decidim/content_blocks/global_menu/show.erb +0 -11
  448. data/app/cells/decidim/content_blocks/global_menu_cell.rb +0 -35
  449. data/app/cells/decidim/content_blocks/menu_breadcrumb_last_activity/show.erb +0 -10
  450. data/app/cells/decidim/content_blocks/menu_breadcrumb_last_activity_cell.rb +0 -49
  451. data/app/cells/decidim/participatory_space_dropdown_metadata/links.erb +0 -9
  452. data/app/cells/decidim/participatory_space_dropdown_metadata/metadata.erb +0 -1
  453. data/app/cells/decidim/participatory_space_dropdown_metadata/show.erb +0 -11
  454. data/app/cells/decidim/participatory_space_dropdown_metadata_cell.rb +0 -26
  455. data/app/controllers/concerns/decidim/has_members_page.rb +0 -25
  456. data/app/controllers/decidim/private_downloads_controller.rb +0 -29
  457. data/app/jobs/decidim/newsletters_opt_in_job.rb +0 -11
  458. data/app/jobs/decidim/user_report_job.rb +0 -11
  459. data/app/mailers/decidim/delete_user_mailer.rb +0 -14
  460. data/app/models/decidim/participatory_space_private_user.rb +0 -51
  461. data/app/models/decidim/private_download.rb +0 -61
  462. data/app/packs/src/decidim/controllers/sticky_header/controller.js +0 -116
  463. data/app/packs/src/decidim/controllers/sticky_header/sticky_header.test.js +0 -320
  464. data/app/packs/src/decidim/dropdown_menu.js +0 -18
  465. data/app/packs/src/decidim/geocoding/reverse_geocoding.test.js +0 -197
  466. data/app/packs/src/decidim/identity_selector_dialog.js +0 -33
  467. data/app/packs/src/decidim/refactor/moved/focus_guard.test.js +0 -136
  468. data/app/packs/stylesheets/decidim/_editor_suggestions.scss +0 -49
  469. data/app/packs/stylesheets/decidim/_tom_select.scss +0 -23
  470. data/app/presenters/decidim/admin_log/participatory_space_private_user_presenter.rb +0 -38
  471. data/app/presenters/decidim/participatory_space_private_user_presenter.rb +0 -46
  472. data/app/services/decidim/push_subscription_endpoint_validator.rb +0 -34
  473. data/app/views/decidim/delete_user_mailer/delete.html.erb +0 -6
  474. data/app/views/decidim/participatory_space_private_users/_participatory_space_private_user.html.erb +0 -1
  475. data/app/views/layouts/decidim/footer/_main_language_chooser.html.erb +0 -26
  476. data/app/views/layouts/decidim/header/_menu_breadcrumb_items.html.erb +0 -34
  477. data/app/views/layouts/decidim/header/_menu_breadcrumb_main_dropdown_desktop.html.erb +0 -10
  478. data/app/views/layouts/decidim/header/_menu_breadcrumb_main_dropdown_mobile.html.erb +0 -17
  479. data/app/views/layouts/decidim/header/_menu_breadcrumb_main_dropdown_top_left.html.erb +0 -2
  480. data/app/views/layouts/decidim/header/_menu_breadcrumb_mobile_tablet.html.erb +0 -33
  481. data/app/views/layouts/decidim/header/_mobile_language_choose.html.erb +0 -25
  482. data/lib/decidim/core/test/shared_examples/has_private_users.rb +0 -63
  483. data/lib/decidim/core/test/shared_examples/participatory_space_dropdown_metadata_cell_examples.rb +0 -81
  484. data/lib/decidim/has_private_users.rb +0 -57
  485. data/lib/decidim/maintenance/import_models/assembly_type.rb +0 -49
  486. data/lib/decidim/maintenance/import_models/participatory_process_type.rb +0 -21
  487. data/lib/decidim/query_extensions.rb +0 -64
  488. /data/app/cells/decidim/{participatory_space_private_user → member}/show.erb +0 -0
@@ -1,44 +1,36 @@
1
1
  /* global jest */
2
2
 
3
3
  import AccordionController from "src/decidim/controllers/accordion/controller";
4
- import { screens } from "tailwindcss/defaultTheme"
5
4
 
6
5
  jest.mock("a11y-accordion-component", () => ({
7
6
  render: jest.fn(),
8
7
  destroy: jest.fn()
9
8
  }));
10
9
 
11
- const createController = (controllerElement) => {
12
- const instance = Object.create(AccordionController.prototype);
13
- Reflect.defineProperty(instance, "element", {
14
- get: () => controllerElement,
15
- configurable: true
16
- });
17
- return instance;
18
- };
19
-
20
10
  describe("AccordionController", () => {
21
11
  let controller = null;
22
12
  let accordionElement = null;
23
13
  let panel1 = null;
24
14
  let panel2 = null;
25
15
 
26
- const mockMatchMedia = (matchesOrPredicate = false) => {
27
- window.matchMedia = jest.fn().mockImplementation((query) => {
28
- const matches = typeof matchesOrPredicate === "function"
29
- ? matchesOrPredicate(query)
30
- : matchesOrPredicate;
31
- return {
32
- matches,
33
- media: query,
34
- addEventListener: jest.fn(),
35
- removeEventListener: jest.fn()
36
- };
16
+ const createController = (controllerElement) => {
17
+ const ControllerClass = AccordionController;
18
+ const instance = Object.create(ControllerClass.prototype);
19
+ Reflect.defineProperty(instance, "element", {
20
+ get: () => controllerElement,
21
+ configurable: true
37
22
  });
23
+ return instance;
38
24
  };
39
25
 
40
26
  beforeEach(() => {
41
- mockMatchMedia(false);
27
+ window.matchMedia = jest.fn().mockImplementation((query) => ({
28
+ matches: false,
29
+ media: query,
30
+ addListener: jest.fn(),
31
+ removeListener: jest.fn()
32
+ }));
33
+
42
34
  document.body.innerHTML = `
43
35
  <div id="test-accordion" data-controller="accordion">
44
36
  <button id="trigger-1" data-controls="panel-1">Trigger 1</button>
@@ -47,9 +39,11 @@ describe("AccordionController", () => {
47
39
  <div id="panel-2">Panel 2 Content</div>
48
40
  </div>
49
41
  `;
42
+
50
43
  accordionElement = document.getElementById("test-accordion");
51
44
  panel1 = document.getElementById("panel-1");
52
45
  panel2 = document.getElementById("panel-2");
46
+
53
47
  controller = createController(accordionElement);
54
48
  });
55
49
 
@@ -62,8 +56,10 @@ describe("AccordionController", () => {
62
56
  it("changes role from region to group when data-panel-role is group", () => {
63
57
  panel1.setAttribute("role", "region");
64
58
  panel2.setAttribute("role", "region");
59
+
65
60
  accordionElement.dataset.panelRole = "group";
66
61
  controller.fixPanelRole();
62
+
67
63
  expect(panel1.getAttribute("role")).toBe("group");
68
64
  expect(panel2.getAttribute("role")).toBe("group");
69
65
  });
@@ -71,202 +67,52 @@ describe("AccordionController", () => {
71
67
  it("removes role attribute when data-panel-role is none", () => {
72
68
  panel1.setAttribute("role", "region");
73
69
  panel2.setAttribute("role", "region");
70
+
74
71
  accordionElement.dataset.panelRole = "none";
75
72
  controller.fixPanelRole();
73
+
76
74
  expect(panel1.getAttribute("role")).toBeNull();
77
75
  expect(panel2.getAttribute("role")).toBeNull();
78
76
  });
79
77
 
80
- it("does nothing when data-panel-role is not set or empty", () => {
78
+ it("does nothing when data-panel-role is not set", () => {
81
79
  panel1.setAttribute("role", "region");
82
- controller.fixPanelRole();
83
- expect(panel1.getAttribute("role")).toBe("region");
84
- });
80
+ panel2.setAttribute("role", "region");
85
81
 
86
- it("sets custom role value when data-panel-role is set", () => {
87
- panel1.setAttribute("role", "region");
88
- accordionElement.dataset.panelRole = "navigation";
82
+ Reflect.deleteProperty(accordionElement.dataset, "panelRole");
89
83
  controller.fixPanelRole();
90
- expect(panel1.getAttribute("role")).toBe("navigation");
91
- });
92
-
93
- it("handles nonexistent panels gracefully", () => {
94
- accordionElement.dataset.panelRole = "group";
95
- const trigger = document.createElement("button");
96
- trigger.dataset.controls = "nonexistent-panel";
97
- accordionElement.appendChild(trigger);
98
- expect(() => controller.fixPanelRole()).not.toThrow();
99
- });
100
- });
101
-
102
- describe("_applyBreakpointOpenAttributes", () => {
103
- beforeEach(() => {
104
- controller.initialize();
105
- });
106
-
107
- it("removes data-open when no breakpoint matches and no original exists", () => {
108
- accordionElement.innerHTML = `
109
- <button data-controls="panel-1" data-open-lg="true">Trigger</button>
110
- <div id="panel-1">Panel 1</div>
111
- `;
112
- controller._applyBreakpointOpenAttributes();
113
- expect(accordionElement.querySelector("[data-controls]").hasAttribute("data-open")).toBe(false);
114
- });
115
-
116
- it("sets data-open from breakpoint attribute when viewport matches", () => {
117
- mockMatchMedia((query) => query === `(min-width: ${screens.lg})`);
118
- accordionElement.innerHTML = `
119
- <button data-controls="panel-1" data-open-lg="true">Trigger</button>
120
- <div id="panel-1">Panel 1</div>
121
- `;
122
- controller.initialize();
123
- controller._applyBreakpointOpenAttributes();
124
- expect(accordionElement.querySelector("[data-controls]").getAttribute("data-open")).toBe("true");
125
- });
126
-
127
- it("restores original data-open when breakpoint no longer matches", () => {
128
- const lgQuery = `(min-width: ${screens.lg})`;
129
- accordionElement.innerHTML = `
130
- <button data-controls="panel-1" data-open="false" data-open-lg="true">Trigger</button>
131
- <div id="panel-1">Panel 1</div>
132
- `;
133
- const trigger = accordionElement.querySelector("[data-controls]");
134
- controller.initialize();
135
-
136
- mockMatchMedia((query) => query === lgQuery);
137
- controller._applyBreakpointOpenAttributes();
138
- expect(trigger.getAttribute("data-open")).toBe("true");
139
-
140
- mockMatchMedia(() => false);
141
- controller._applyBreakpointOpenAttributes();
142
- expect(trigger.getAttribute("data-open")).toBe("false");
143
- });
144
84
 
145
- it("removes data-open when breakpoint stops matching and no original existed", () => {
146
- const lgQuery = `(min-width: ${screens.lg})`;
147
- accordionElement.innerHTML = `
148
- <button data-controls="panel-1" data-open-lg="true">Trigger</button>
149
- <div id="panel-1">Panel 1</div>
150
- `;
151
- const trigger = accordionElement.querySelector("[data-controls]");
152
- controller.initialize();
153
-
154
- mockMatchMedia((query) => query === lgQuery);
155
- controller._applyBreakpointOpenAttributes();
156
- expect(trigger.getAttribute("data-open")).toBe("true");
157
-
158
- mockMatchMedia(() => false);
159
- controller._applyBreakpointOpenAttributes();
160
- expect(trigger.hasAttribute("data-open")).toBe(false);
161
- });
162
-
163
- it("applies the last matching breakpoint when multiple match", () => {
164
- mockMatchMedia((query) =>
165
- query === `(min-width: ${screens.sm})` || query === `(min-width: ${screens.lg})`
166
- );
167
- accordionElement.innerHTML = `
168
- <button data-controls="panel-1" data-open-sm="false" data-open-lg="true">Trigger</button>
169
- <div id="panel-1">Panel 1</div>
170
- `;
171
- controller.initialize();
172
- controller._applyBreakpointOpenAttributes();
173
- expect(accordionElement.querySelector("[data-controls]").getAttribute("data-open")).toBe("true");
174
- });
175
-
176
- it("leaves triggers without breakpoint attributes unchanged", () => {
177
- controller.initialize();
178
- controller._applyBreakpointOpenAttributes();
179
- expect(accordionElement.querySelector("#trigger-1").hasAttribute("data-open")).toBe(false);
180
- expect(accordionElement.querySelector("#trigger-2").hasAttribute("data-open")).toBe(false);
181
- });
182
- });
183
-
184
- describe("_listenForBreakpointChanges", () => {
185
- beforeEach(() => {
186
- controller.initialize();
187
- });
188
-
189
- it("registers change listeners for breakpoints with matching triggers", () => {
190
- accordionElement.innerHTML = `
191
- <button data-controls="panel-1" data-open-lg="true">Trigger</button>
192
- <div id="panel-1">Panel 1</div>
193
- `;
194
- controller._listenForBreakpointChanges();
195
- expect(window.matchMedia).toHaveBeenCalledWith(`(min-width: ${screens.lg})`);
196
- expect(window.matchMedia.mock.results[0].value.addEventListener).toHaveBeenCalledWith("change", expect.any(Function));
197
- });
198
-
199
- it("skips breakpoints without matching triggers", () => {
200
- accordionElement.innerHTML = `
201
- <button data-controls="panel-1" data-open-lg="true">Trigger</button>
202
- <div id="panel-1">Panel 1</div>
203
- `;
204
- controller._listenForBreakpointChanges();
205
- expect(window.matchMedia).toHaveBeenCalledTimes(1);
85
+ expect(panel1.getAttribute("role")).toBe("region");
86
+ expect(panel2.getAttribute("role")).toBe("region");
206
87
  });
207
88
 
208
- it("registers nothing when there are no breakpoint triggers", () => {
209
- controller._listenForBreakpointChanges();
210
- expect(controller._mediaQueries.length).toBe(0);
211
- });
89
+ it("does nothing when data-panel-role is empty", () => {
90
+ panel1.setAttribute("role", "region");
212
91
 
213
- it("registers listeners for multiple breakpoints", () => {
214
- accordionElement.innerHTML = `
215
- <button data-controls="panel-1" data-open-sm="true" data-open-lg="false">Trigger</button>
216
- <div id="panel-1">Panel 1</div>
217
- `;
218
- controller._listenForBreakpointChanges();
219
- expect(window.matchMedia).toHaveBeenCalledWith(`(min-width: ${screens.sm})`);
220
- expect(window.matchMedia).toHaveBeenCalledWith(`(min-width: ${screens.lg})`);
221
- expect(controller._mediaQueries.length).toBe(2);
222
- });
223
- });
92
+ accordionElement.dataset.panelRole = "";
93
+ controller.fixPanelRole();
224
94
 
225
- describe("_stopListeningForBreakpointChanges", () => {
226
- beforeEach(() => {
227
- controller.initialize();
95
+ expect(panel1.getAttribute("role")).toBe("region");
228
96
  });
229
97
 
230
- it("removes all registered change listeners", () => {
231
- accordionElement.innerHTML = `
232
- <button data-controls="panel-1" data-open-lg="true">Trigger</button>
233
- <div id="panel-1">Panel 1</div>
234
- `;
235
- controller._listenForBreakpointChanges();
236
- const mql = window.matchMedia.mock.results[0].value;
237
- controller._stopListeningForBreakpointChanges();
238
- expect(mql.removeEventListener).toHaveBeenCalledWith("change", expect.any(Function));
239
- expect(controller._mediaQueries.length).toBe(0);
240
- });
98
+ it("sets custom role value when data-panel-role is set", () => {
99
+ panel1.setAttribute("role", "region");
241
100
 
242
- it("does nothing when no listeners exist", () => {
243
- expect(() => controller._stopListeningForBreakpointChanges()).not.toThrow();
244
- });
245
- });
101
+ accordionElement.dataset.panelRole = "navigation";
102
+ controller.fixPanelRole();
246
103
 
247
- describe("connect/disconnect/reconnect with breakpoints", () => {
248
- beforeEach(() => {
249
- controller.initialize();
104
+ expect(panel1.getAttribute("role")).toBe("navigation");
250
105
  });
251
106
 
252
- it("connect applies breakpoint attributes and sets up listeners", () => {
253
- const applySpy = jest.spyOn(controller, "_applyBreakpointOpenAttributes");
254
- const listenSpy = jest.spyOn(controller, "_listenForBreakpointChanges");
255
- controller.connect();
256
- expect(applySpy).toHaveBeenCalled();
257
- expect(listenSpy).toHaveBeenCalled();
258
- });
107
+ it("handles nonexistent panels gracefully", () => {
108
+ accordionElement.dataset.panelRole = "group";
259
109
 
260
- it("disconnect tears down breakpoint listeners", () => {
261
- const spy = jest.spyOn(controller, "_stopListeningForBreakpointChanges");
262
- controller.disconnect();
263
- expect(spy).toHaveBeenCalled();
264
- });
110
+ const nonExistentTrigger = document.createElement("button");
111
+ nonExistentTrigger.dataset.controls = "nonexistent-panel";
112
+ accordionElement.appendChild(nonExistentTrigger);
265
113
 
266
- it("reconnect re-evaluates breakpoint attributes", () => {
267
- const spy = jest.spyOn(controller, "_applyBreakpointOpenAttributes");
268
- controller.reconnect({});
269
- expect(spy).toHaveBeenCalled();
114
+ expect(() => controller.fixPanelRole()).not.toThrow();
270
115
  });
271
116
  });
272
117
  });
118
+
@@ -2,9 +2,6 @@ import { Controller } from "@hotwired/stimulus"
2
2
  import Accordions from "a11y-accordion-component";
3
3
  import { screens } from "tailwindcss/defaultTheme"
4
4
  export default class extends Controller {
5
- initialize() {
6
- this._originalOpenValues = new WeakMap();
7
- }
8
5
 
9
6
  /**
10
7
  * Create accordion from a component
@@ -19,8 +16,21 @@ export default class extends Controller {
19
16
  accordionOptions.isMultiSelectable = this.element.dataset.multiselectable !== "false";
20
17
  accordionOptions.isCollapsible = this.element.dataset.collapsible !== "false";
21
18
 
22
- this._applyBreakpointOpenAttributes();
23
- this._listenForBreakpointChanges();
19
+ // This snippet allows to change the OPEN data-attribute based on the current viewport
20
+ // Just include the breakpoint where the different value will be applied from.
21
+ // Ex:
22
+ // data-open="false" data-open-md="true"
23
+ Object.keys(screens).forEach((key) => {
24
+ if (!this.isScreenSize(key)) {
25
+ return;
26
+ }
27
+
28
+ const elementsToOpen = this.element.querySelectorAll(`[data-controls][data-open-${key}]`);
29
+
30
+ elementsToOpen.forEach((elem) => {
31
+ (elem.dataset.open = elem.dataset[`open-${key}`.replace(/-([a-z])/g, (str) => str[1].toUpperCase())])
32
+ })
33
+ })
24
34
 
25
35
  if (!this.element.id) {
26
36
  // when component has no id, we enforce to have it one
@@ -49,106 +59,20 @@ export default class extends Controller {
49
59
  }
50
60
 
51
61
  if (this.boundExpand) {
52
- this.toggleButton.removeEventListener("click", this.boundExpand);
62
+ this.toggleButton.addEventListener("click", this.boundExpand);
53
63
  }
54
-
55
- this._stopListeningForBreakpointChanges();
56
64
  }
57
65
 
58
66
  reconnect(event) {
59
67
  this.disconnect();
60
68
 
61
- if (event && event.detail && event.detail.collapse) {
69
+ if (event.detail && event.detail.collapse) {
62
70
  this.previouslyExpanded = false;
63
71
  }
64
72
 
65
73
  this.connect();
66
74
  }
67
75
 
68
- /**
69
- * Applies viewport-conditional data-open attributes and resets
70
- * previously applied values when the viewport no longer matches.
71
- *
72
- * @returns {void}
73
- */
74
- _applyBreakpointOpenAttributes() {
75
- const controlledTriggers = new Set();
76
-
77
- Object.keys(screens).forEach((key) => {
78
- this.element.querySelectorAll(`[data-controls][data-open-${key}]`).forEach((elem) => {
79
- controlledTriggers.add(elem);
80
-
81
- if (!this._originalOpenValues.has(elem)) {
82
- this._originalOpenValues.set(elem, elem.getAttribute("data-open"));
83
- }
84
- });
85
- });
86
-
87
- // Reset all controlled triggers to their original data-open state
88
- controlledTriggers.forEach((elem) => {
89
- const original = this._originalOpenValues.get(elem);
90
- if (original === null) {
91
- elem.removeAttribute("data-open");
92
- } else {
93
- elem.setAttribute("data-open", original);
94
- }
95
- });
96
-
97
- // Apply breakpoint-specific values for the current viewport
98
- Object.keys(screens).forEach((key) => {
99
- if (!this.isScreenSize(key)) {
100
- return;
101
- }
102
-
103
- this.element.querySelectorAll(`[data-controls][data-open-${key}]`).forEach((elem) => {
104
- elem.dataset.open = elem.getAttribute(`data-open-${key}`);
105
- });
106
- });
107
- }
108
-
109
- /**
110
- * Listens for viewport changes that cross breakpoints relevant
111
- * to this accordion and reconnects to keep state in sync.
112
- *
113
- * @returns {void}
114
- */
115
- _listenForBreakpointChanges() {
116
- this._mediaQueries = [];
117
-
118
- Object.keys(screens).forEach((key) => {
119
- const hasRelevantTriggers = this.element.querySelector(`[data-controls][data-open-${key}]`);
120
- if (!hasRelevantTriggers) {
121
- return;
122
- }
123
-
124
- const mql = window.matchMedia(`(min-width: ${screens[key]})`);
125
- const handler = () => {
126
- this.reconnect({ detail: { collapse: true } });
127
- };
128
-
129
- if (mql.addEventListener) {
130
- mql.addEventListener("change", handler);
131
- } else if (mql.addListener) {
132
- mql.addListener(handler);
133
- }
134
-
135
- this._mediaQueries.push({ mql, handler });
136
- });
137
- }
138
-
139
- _stopListeningForBreakpointChanges() {
140
- if (this._mediaQueries) {
141
- this._mediaQueries.forEach(({ mql, handler }) => {
142
- if (mql.removeEventListener) {
143
- mql.removeEventListener("change", handler);
144
- } else if (mql.removeListener) {
145
- mql.removeListener(handler);
146
- }
147
- });
148
- this._mediaQueries = [];
149
- }
150
- }
151
-
152
76
  expandIfNeeded()
153
77
  {
154
78
  if (!this.toggleButton) {
@@ -0,0 +1,80 @@
1
+ /* global global, jest */
2
+
3
+ import { Application } from "@hotwired/stimulus";
4
+ import AssignRoleController from "src/decidim/controllers/assign_role/controller";
5
+
6
+ describe("AssignRoleController", () => {
7
+ let application = null;
8
+ let element = null;
9
+ let controller = null;
10
+
11
+ beforeEach(() => {
12
+ document.body.innerHTML = `
13
+ <div data-controller="assign-role" data-role="navigation"></div>
14
+ `;
15
+
16
+ application = Application.start();
17
+ application.register("assign-role", AssignRoleController);
18
+
19
+ element = document.querySelector('[data-controller="assign-role"]');
20
+
21
+ return new Promise((resolve) => {
22
+ setTimeout(() => {
23
+ controller = application.getControllerForElementAndIdentifier(element, "assign-role");
24
+ resolve();
25
+ }, 0);
26
+ });
27
+ });
28
+
29
+ afterEach(() => {
30
+ application.stop();
31
+ document.body.innerHTML = "";
32
+ jest.useRealTimers();
33
+ jest.restoreAllMocks();
34
+ });
35
+
36
+ describe("connect", () => {
37
+ it("sets the role attribute after the delay", () => {
38
+ controller.disconnect();
39
+ jest.useFakeTimers();
40
+
41
+ controller.connect();
42
+
43
+ expect(element.getAttribute("role")).toBeNull();
44
+ jest.advanceTimersByTime(300);
45
+ expect(element.getAttribute("role")).toBe("navigation");
46
+ });
47
+
48
+ it("returns early when no data-role is provided", () => {
49
+ controller.disconnect();
50
+
51
+ element.dataset.role = "";
52
+ const setTimeoutSpy = jest.spyOn(global, "setTimeout");
53
+ jest.useFakeTimers();
54
+
55
+ controller.connect();
56
+
57
+ expect(element.getAttribute("role")).toBeNull();
58
+ expect(setTimeoutSpy).not.toHaveBeenCalled();
59
+ jest.advanceTimersByTime(300);
60
+ expect(element.getAttribute("role")).toBeNull();
61
+ });
62
+ });
63
+
64
+ describe("disconnect", () => {
65
+ it("clears any pending timeout", () => {
66
+ controller.disconnect();
67
+ jest.useFakeTimers();
68
+
69
+ const clearTimeoutSpy = jest.spyOn(global, "clearTimeout");
70
+
71
+ controller.connect();
72
+ controller.disconnect();
73
+
74
+ expect(clearTimeoutSpy).toHaveBeenCalled();
75
+
76
+ jest.advanceTimersByTime(300);
77
+ expect(element.getAttribute("role")).toBeNull();
78
+ });
79
+ });
80
+ });
@@ -0,0 +1,27 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ /**
4
+ * This controller is used to set a role attribute for any element where is being assigned.
5
+ * It requires a data-role attribute with the value of the role attribute to be set.
6
+ * We are using to change the value "menu" of role attribute set by a11y on div dropdown-menu-account and
7
+ * dropdown-menu-account-mobile which are inappropriate for accessibility
8
+ */
9
+ export default class extends Controller {
10
+ connect() {
11
+ const role = this.element.dataset.role
12
+
13
+ if (!role) {
14
+ return
15
+ }
16
+
17
+ this.timeoutId = setTimeout(() => {
18
+ this.element.setAttribute("role", role)
19
+ }, 300)
20
+ }
21
+
22
+ disconnect() {
23
+ if (this.timeoutId) {
24
+ clearTimeout(this.timeoutId)
25
+ }
26
+ }
27
+ }
@@ -47,12 +47,6 @@ export default class extends Controller {
47
47
  this.element.id = `dropdown-${Math.random().toString(36).substring(7)}`
48
48
  }
49
49
 
50
- if (!this.element.hasAttribute("aria-expanded")) {
51
- this.element.setAttribute("aria-expanded", dropdownOptions.isOpen
52
- ? "true"
53
- : "false");
54
- }
55
-
56
50
  const autofocus = this.element.dataset.autofocus;
57
51
  if (autofocus) {
58
52
  // set the focus to some inner element, use setTimeout hack due to waiting for element to display
@@ -142,34 +142,6 @@ describe("DropdownController", () => {
142
142
  });
143
143
  });
144
144
 
145
- describe("aria-expanded initialization", () => {
146
- it("sets aria-expanded to false when missing and isOpen is false", () => {
147
- const testController = createController(element);
148
-
149
- testController.connect();
150
-
151
- expect(element.getAttribute("aria-expanded")).toBe("false");
152
- });
153
-
154
- it("sets aria-expanded to true when missing and data-open is true", () => {
155
- element.setAttribute("data-open", "true");
156
- const testController = createController(element);
157
-
158
- testController.connect();
159
-
160
- expect(element.getAttribute("aria-expanded")).toBe("true");
161
- });
162
-
163
- it("does not override existing aria-expanded attribute", () => {
164
- element.setAttribute("aria-expanded", "true");
165
- const testController = createController(element);
166
-
167
- testController.connect();
168
-
169
- expect(element.getAttribute("aria-expanded")).toBe("true");
170
- });
171
- });
172
-
173
145
  describe("data-add-aria-roles option", () => {
174
146
  it("keeps role menu when data-add-aria-roles is true", () => {
175
147
  element.setAttribute("data-add-aria-roles", "true");
@@ -419,13 +419,13 @@ class FormValidator {
419
419
  return true;
420
420
  }
421
421
 
422
- if (isValid) {
423
- this.removeCheckboxGroupErrorClasses(groupName);
424
- } else {
425
- checkboxGroup.forEach((checkboxElement) => {
422
+ checkboxGroup.forEach((checkboxElement) => {
423
+ if (isValid) {
424
+ this.removeInputErrorClasses(checkboxElement);
425
+ } else {
426
426
  this.addInputErrorClasses(checkboxElement, ["required"]);
427
- });
428
- }
427
+ }
428
+ });
429
429
 
430
430
  return isValid;
431
431
  }
@@ -1,4 +1,4 @@
1
- /* eslint max-lines: ["error", 712] */
1
+ /* eslint max-lines: ["error", 690] */
2
2
  /* global jest */
3
3
  /**
4
4
  * @jest-environment jsdom
@@ -253,28 +253,6 @@ describe("FormValidator", () => {
253
253
  const isValidTwo = validatorInstance.validateCheckboxGroup("multiCheckboxes");
254
254
  expect(isValidTwo).toBe(true);
255
255
  });
256
-
257
- it("should remove error classes once for the group, not per checkbox", () => {
258
- formElement.innerHTML = `
259
- <fieldset>
260
- <input type="checkbox" id="cb-1" name="largeGroup" value="1" required>
261
- <input type="checkbox" id="cb-2" name="largeGroup" value="2" required>
262
- <input type="checkbox" id="cb-3" name="largeGroup" value="3" required>
263
- </fieldset>
264
- <button type="submit">Submit</button>
265
- `;
266
- validatorInstance = new FormValidator(formElement);
267
- formElement.querySelector("#cb-1").checked = true;
268
-
269
- const removeGroupSpy = jest.spyOn(validatorInstance, "removeCheckboxGroupErrorClasses");
270
- const removeInputSpy = jest.spyOn(validatorInstance, "removeInputErrorClasses");
271
-
272
- const isValid = validatorInstance.validateCheckboxGroup("largeGroup");
273
-
274
- expect(isValid).toBe(true);
275
- expect(removeGroupSpy).toHaveBeenCalledTimes(1);
276
- expect(removeInputSpy).not.toHaveBeenCalled();
277
- });
278
256
  });
279
257
 
280
258
  describe("Custom Validators", () => {