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,6 +1,5 @@
1
- /* eslint max-lines: ["error", 400] */
2
-
3
1
  import { Controller } from "@hotwired/stimulus"
2
+ import Tribute from "src/decidim/vendor/tribute";
4
3
 
5
4
  export default class extends Controller {
6
5
  connect() {
@@ -10,12 +9,7 @@ export default class extends Controller {
10
9
  menuItemLimit: 5
11
10
  };
12
11
 
13
- this.suggestion = null;
14
- this.suggestions = [];
15
- this.selectedIndex = -1;
16
- this.isActive = false;
17
- this.currentMentionStart = null;
18
- this.requestId = 0;
12
+ this.tribute = null;
19
13
  this.isInitialized = false;
20
14
 
21
15
  // Prevent initialization inside editor components
@@ -23,62 +17,100 @@ export default class extends Controller {
23
17
  return;
24
18
  }
25
19
 
26
- this.createSuggestionContainer();
20
+ if (this.element.hasAttribute("data-tribute")) {
21
+ this.element.removeAttribute("data-tribute");
22
+ }
23
+
24
+ this.createTribute();
27
25
  this.setupEventListeners();
28
26
  this.isInitialized = true;
29
27
  }
30
28
 
31
29
  disconnect() {
32
- if (this.suggestion) {
33
- this.suggestion.remove();
30
+ if (this.tribute) {
31
+ this.tribute.detach(this.element);
34
32
  }
35
33
 
36
34
  this.element.removeEventListener("focusin", this.handleFocusIn);
37
35
  this.element.removeEventListener("focusout", this.handleFocusOut);
38
36
  this.element.removeEventListener("input", this.handleInput);
39
- this.element.removeEventListener("keydown", this.handleKeyDown);
40
- document.removeEventListener("click", this.handleDocumentClick);
41
37
 
42
- this.suggestion = null;
43
- this.suggestions = [];
38
+ this.tribute = null;
44
39
  this.isInitialized = false;
45
40
  }
46
41
 
47
- createSuggestionContainer() {
48
- this.suggestion = document.createElement("div");
49
- this.suggestion.classList.add("editor-suggestions", "hidden", "hide");
50
- document.body.append(this.suggestion);
51
- this.suggestion.addEventListener("mousedown", (event) => event.preventDefault());
42
+ /**
43
+ * Create and configure the Tribute instance
44
+ * @returns {void}
45
+ * @private
46
+ */
47
+ createTribute() {
48
+ const noMatchTemplate = this.options.noDataFoundMessage
49
+ ? () => `<li>${this.options.noDataFoundMessage}</li>`
50
+ : null;
51
+
52
+ this.tribute = new Tribute({
53
+ trigger: "@",
54
+ values: this.debounce((text, callback) => {
55
+ this.performRemoteSearch(text, callback);
56
+ }, this.options.debounceDelay),
57
+ positionMenu: true,
58
+ menuContainer: null,
59
+ allowSpaces: true,
60
+ menuItemLimit: this.options.menuItemLimit,
61
+ fillAttr: "nickname",
62
+ selectClass: "highlight",
63
+ noMatchTemplate: noMatchTemplate,
64
+ lookup: (item) => item.nickname + item.name,
65
+ selectTemplate: (item) => {
66
+ if (typeof item === "undefined") {
67
+ return null;
68
+ }
69
+ return item.original.nickname;
70
+ },
71
+ menuItemTemplate: (item) => {
72
+ return `
73
+ <img src="${item.original.avatarUrl}" alt="author-avatar">
74
+ <strong>${item.original.nickname}</strong>
75
+ <small>${item.original.name}</small>
76
+ `;
77
+ }
78
+ });
52
79
 
53
- this.performRemoteSearch = this.debounce(this.performRemoteSearch.bind(this), this.options.debounceDelay);
80
+ this.tribute.attach(this.element);
54
81
  }
55
82
 
83
+ /**
84
+ * Set up event listeners for the element
85
+ * @returns {void}
86
+ * @private
87
+ */
56
88
  setupEventListeners() {
57
- this.handleFocusIn = this.handleFocusIn.bind(this);
58
- this.handleFocusOut = this.handleFocusOut.bind(this);
59
- this.handleInput = this.handleInput.bind(this);
60
- this.handleKeyDown = this.handleKeyDown.bind(this);
61
- this.handleDocumentClick = this.handleDocumentClick.bind(this);
62
-
63
- this.element.addEventListener("focusin", this.handleFocusIn);
64
- this.element.addEventListener("focusout", this.handleFocusOut);
65
- this.element.addEventListener("input", this.handleInput);
66
- this.element.addEventListener("keydown", this.handleKeyDown);
67
- document.addEventListener("click", this.handleDocumentClick);
68
- }
69
-
70
- handleFocusIn() {
71
- if (this.element.parentNode) {
72
- this.element.parentNode.classList.add("is-active");
73
- }
74
- }
75
-
89
+ // Handle focus events to set menu container
90
+ this.element.addEventListener("focusin", this.handleFocusIn.bind(this));
91
+ this.element.addEventListener("focusout", this.handleFocusOut.bind(this));
92
+ this.element.addEventListener("input", this.handleInput.bind(this));
93
+ }
94
+
95
+ /**
96
+ * Handle focus in event
97
+ * @param {Event} event - The focus in event
98
+ * @returns {void}
99
+ * @private
100
+ */
101
+ handleFocusIn(event) {
102
+ if (this.tribute) {
103
+ this.tribute.menuContainer = event.target.parentNode;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Handle focus out event
109
+ * @param {Event} event - The focus out event
110
+ * @returns {void}
111
+ * @private
112
+ */
76
113
  handleFocusOut(event) {
77
- if (this.suggestion && this.suggestion.contains(event.relatedTarget)) {
78
- return;
79
- }
80
-
81
- this.hideSuggestions();
82
114
  const parent = event.target.parentNode;
83
115
 
84
116
  if (parent && parent.classList.contains("is-active")) {
@@ -86,99 +118,63 @@ export default class extends Controller {
86
118
  }
87
119
  }
88
120
 
89
- handleInput() {
90
- const trigger = this.mentionTriggerAtCursor();
91
- if (!trigger) {
92
- this.hideSuggestions();
93
- return;
94
- }
95
-
96
- this.currentMentionStart = trigger.start;
97
- this.performRemoteSearch(trigger.query);
98
- }
99
-
100
- handleKeyDown(event) {
101
- if (!this.isActive) {
102
- return;
103
- }
104
-
105
- if (event.key === "Escape") {
106
- event.preventDefault();
107
- this.hideSuggestions();
108
- } else if (event.key === "ArrowDown") {
109
- event.preventDefault();
110
- this.updateSelectedIndex(1);
111
- this.renderSuggestions();
112
- } else if (event.key === "ArrowUp") {
113
- event.preventDefault();
114
- this.updateSelectedIndex(-1);
115
- this.renderSuggestions();
116
- } else if (event.key === "Enter") {
117
- event.preventDefault();
118
- this.selectSuggestion(this.selectedIndex);
119
- }
120
- }
121
+ /**
122
+ * Handle input event
123
+ * @param {Event} event - The input event
124
+ * @returns {void}
125
+ * @private
126
+ */
127
+ handleInput(event) {
128
+ const parent = event.target.parentNode;
121
129
 
122
- handleDocumentClick(event) {
123
- if (this.element === event.target || this.element.contains(event.target) || this.suggestion?.contains(event.target)) {
130
+ if (!parent) {
124
131
  return;
125
132
  }
126
133
 
127
- this.hideSuggestions();
128
- }
129
-
130
- mentionTriggerAtCursor() {
131
- const value = this.element.value || "";
132
- const caretPosition = this.element.selectionStart;
133
-
134
- if (typeof caretPosition !== "number") {
135
- return null;
136
- }
134
+ if (this.tribute && this.tribute.isActive) {
135
+ // Move the tribute container to the correct parent
136
+ const tributeContainer = document.querySelector(".tribute-container");
137
+ if (tributeContainer) {
138
+ parent.appendChild(tributeContainer);
139
+ }
137
140
 
138
- const textBeforeCursor = value.slice(0, caretPosition);
139
- const mentionMatch = textBeforeCursor.match(/(?:^|\s)@([\w.-]{2,})$/);
140
- if (!mentionMatch) {
141
- return null;
141
+ parent.classList.add("is-active");
142
+ } else {
143
+ parent.classList.remove("is-active");
142
144
  }
143
-
144
- return {
145
- query: mentionMatch[1],
146
- start: caretPosition - mentionMatch[1].length - 1
147
- };
148
145
  }
149
146
 
150
- performRemoteSearch(text) {
151
- const currentRequestId = this.requestId + 1;
152
- this.requestId = currentRequestId;
153
-
147
+ /**
148
+ * Perform remote search for users
149
+ * @param {string} text - The search text
150
+ * @param {Function} callback - The callback function to call with results
151
+ * @returns {void}
152
+ * @private
153
+ */
154
+ performRemoteSearch(text, callback) {
154
155
  const query = `{users(filter:{wildcard:"${text}"}){nickname,name,avatarUrl,__typename}}`;
155
156
  const apiPath = window.Decidim.config.get("api_path");
156
157
 
157
158
  this.makeRequest(apiPath, { query }).
158
159
  then((response) => {
159
- if (this.requestId !== currentRequestId) {
160
- return;
161
- }
162
-
163
160
  const data = response.data.users || [];
164
- const sortedData = data.sort((first, second) => first.nickname.localeCompare(second.nickname));
165
- this.suggestions = sortedData.slice(0, this.options.menuItemLimit);
166
- this.selectedIndex = this.suggestions.length > 0
167
- ? 0
168
- : -1;
169
- this.renderSuggestions({ showNoResults: true });
161
+ callback(data);
170
162
  }).
171
163
  catch(() => {
172
- if (this.requestId !== currentRequestId) {
173
- return;
174
- }
175
-
176
- this.suggestions = [];
177
- this.selectedIndex = -1;
178
- this.renderSuggestions({ showNoResults: true });
164
+ callback([]);
165
+ }).
166
+ finally(() => {
167
+ this.adjustTributeContainer();
179
168
  });
180
169
  }
181
170
 
171
+ /**
172
+ * Make an HTTP POST request
173
+ * @param {string} url - The request URL
174
+ * @param {Object} data - The request data
175
+ * @returns {Promise} The request promise
176
+ * @private
177
+ */
182
178
  makeRequest(url, data) {
183
179
  return fetch(url, {
184
180
  method: "POST",
@@ -195,191 +191,35 @@ export default class extends Controller {
195
191
  });
196
192
  }
197
193
 
198
- renderSuggestions({ showNoResults = false } = {}) {
199
- if (!this.suggestion) {
200
- return;
201
- }
202
-
203
- this.suggestion.innerHTML = "";
204
-
205
- if (this.suggestions.length < 1) {
206
- this.isActive = false;
207
-
208
- if (showNoResults && this.options.noDataFoundMessage) {
209
- const noResultsItem = document.createElement("button");
210
- noResultsItem.type = "button";
211
- noResultsItem.disabled = true;
212
- noResultsItem.classList.add("editor-suggestions-item", "editor-suggestions-item-disabled");
213
- noResultsItem.textContent = this.options.noDataFoundMessage;
214
- this.suggestion.append(noResultsItem);
215
- this.positionSuggestionMenu();
216
- this.suggestion.classList.remove("hidden", "hide");
217
- } else {
218
- this.suggestion.classList.add("hidden", "hide");
219
- }
220
-
194
+ /**
195
+ * Adjust the tribute container positioning and styling
196
+ * @returns {void}
197
+ * @private
198
+ */
199
+ adjustTributeContainer() {
200
+ if (!this.tribute || !this.tribute.current || !this.tribute.current.element) {
221
201
  return;
222
202
  }
223
203
 
224
- this.suggestions.forEach((item, index) => {
225
- const suggestionItem = document.createElement("button");
226
- suggestionItem.type = "button";
227
- suggestionItem.classList.add("editor-suggestions-item");
228
- suggestionItem.dataset.index = index;
229
-
230
- if (item.avatarUrl) {
231
- const avatar = document.createElement("img");
232
- avatar.classList.add("editor-suggestions-item-avatar");
233
- avatar.src = item.avatarUrl;
234
- avatar.alt = item.name || item.nickname;
235
- suggestionItem.append(avatar);
236
- }
237
-
238
- const label = document.createElement("span");
239
- label.classList.add("editor-suggestions-item-label");
240
- label.textContent = `${item.nickname} (${item.name})`;
241
- suggestionItem.append(label);
204
+ const parent = this.tribute.current.element.parentNode;
205
+ if (parent) {
206
+ parent.classList.add("is-active");
242
207
 
243
- if (index === this.selectedIndex) {
244
- suggestionItem.dataset.selected = "true";
208
+ const tributeContainer = parent.querySelector(".tribute-container");
209
+ if (tributeContainer) {
210
+ // Remove inline styles for absolute positioning
211
+ tributeContainer.removeAttribute("style");
245
212
  }
246
-
247
- suggestionItem.addEventListener("click", (event) => {
248
- event.preventDefault();
249
- this.selectSuggestion(index);
250
- });
251
-
252
- this.suggestion.append(suggestionItem);
253
- });
254
-
255
- this.positionSuggestionMenu();
256
- this.isActive = true;
257
- this.suggestion.classList.remove("hidden", "hide");
258
- }
259
-
260
- positionSuggestionMenu() {
261
- if (!this.suggestion || this.currentMentionStart === null) {
262
- return;
263
213
  }
264
-
265
- const coordinates = this.coordinatesForTextIndex(this.currentMentionStart);
266
- if (!coordinates) {
267
- return;
268
- }
269
-
270
- Object.assign(this.suggestion.style, {
271
- position: "absolute",
272
- top: `${coordinates.top}px`,
273
- left: `${coordinates.left}px`
274
- });
275
- }
276
-
277
- coordinatesForTextIndex(index) {
278
- const element = this.element;
279
- const styles = window.getComputedStyle(element);
280
- const elementRect = element.getBoundingClientRect();
281
- const borderTopWidth = parseFloat(styles.borderTopWidth) || 0;
282
- const borderLeftWidth = parseFloat(styles.borderLeftWidth) || 0;
283
- const lineHeight = parseFloat(styles.lineHeight) || 16;
284
-
285
- const mirror = document.createElement("div");
286
- const mirrorStyles = [
287
- "fontFamily",
288
- "fontSize",
289
- "fontWeight",
290
- "fontStyle",
291
- "letterSpacing",
292
- "textTransform",
293
- "wordSpacing",
294
- "textIndent",
295
- "boxSizing",
296
- "width",
297
- "paddingTop",
298
- "paddingRight",
299
- "paddingBottom",
300
- "paddingLeft",
301
- "borderTopWidth",
302
- "borderRightWidth",
303
- "borderBottomWidth",
304
- "borderLeftWidth",
305
- "borderStyle",
306
- "lineHeight"
307
- ];
308
-
309
- mirrorStyles.forEach((property) => {
310
- mirror.style[property] = styles[property];
311
- });
312
-
313
- mirror.style.position = "absolute";
314
- mirror.style.visibility = "hidden";
315
- mirror.style.whiteSpace = element.nodeName === "TEXTAREA"
316
- ? "pre-wrap"
317
- : "pre";
318
- mirror.style.overflowWrap = "break-word";
319
- mirror.style.top = "0";
320
- mirror.style.left = "-9999px";
321
-
322
- const before = document.createTextNode((element.value || "").slice(0, index));
323
- const marker = document.createElement("span");
324
- marker.textContent = "@";
325
-
326
- mirror.append(before);
327
- mirror.append(marker);
328
- document.body.append(mirror);
329
-
330
- const top = elementRect.top + window.scrollY + marker.offsetTop - element.scrollTop + borderTopWidth + lineHeight;
331
- const left = elementRect.left + window.scrollX + marker.offsetLeft - element.scrollLeft + borderLeftWidth;
332
-
333
- mirror.remove();
334
-
335
- return { top, left };
336
- }
337
-
338
- updateSelectedIndex(direction) {
339
- if (this.suggestions.length < 1) {
340
- this.selectedIndex = -1;
341
- return;
342
- }
343
-
344
- const maxIndex = this.suggestions.length - 1;
345
- const nextIndex = this.selectedIndex + direction;
346
-
347
- this.selectedIndex = Math.max(0, Math.min(nextIndex, maxIndex));
348
- }
349
-
350
- selectSuggestion(index) {
351
- const selectedItem = this.suggestions[index];
352
- if (!selectedItem || this.currentMentionStart === null) {
353
- return;
354
- }
355
-
356
- const cursorPosition = this.element.selectionStart;
357
- const value = this.element.value || "";
358
- const mentionValue = `${selectedItem.nickname} `;
359
- const newValue = `${value.slice(0, this.currentMentionStart)}${mentionValue}${value.slice(cursorPosition)}`;
360
-
361
- this.element.value = newValue;
362
-
363
- const newPosition = this.currentMentionStart + mentionValue.length;
364
- this.element.setSelectionRange(newPosition, newPosition);
365
-
366
- this.element.dispatchEvent(new Event("input", { bubbles: true }));
367
- this.hideSuggestions();
368
- }
369
-
370
- hideSuggestions() {
371
- if (!this.suggestion) {
372
- return;
373
- }
374
-
375
- this.isActive = false;
376
- this.currentMentionStart = null;
377
- this.selectedIndex = -1;
378
- this.suggestions = [];
379
- this.suggestion.classList.add("hidden", "hide");
380
- this.suggestion.innerHTML = "";
381
214
  }
382
215
 
216
+ /**
217
+ * Create a debounced version of a function
218
+ * @param {Function} callback - The function to debounce
219
+ * @param {number} wait - The debounce delay in milliseconds
220
+ * @returns {Function} The debounced function
221
+ * @private
222
+ */
383
223
  debounce(callback, wait) {
384
224
  let timeout = null;
385
225
  return (...args) => {
@@ -393,6 +233,10 @@ export default class extends Controller {
393
233
  };
394
234
  }
395
235
 
236
+ /**
237
+ * Check if the component is initialized
238
+ * @returns {boolean} True if initialized, false otherwise
239
+ */
396
240
  get initialized() {
397
241
  return this.isInitialized;
398
242
  }