decidim-core 0.31.5 → 0.32.0.rc2

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 (361) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -13
  3. data/app/cells/decidim/amendable/amend_button_card_cell.rb +1 -1
  4. data/app/cells/decidim/content_blocks/announcement_settings_form/show.erb +5 -0
  5. data/app/cells/decidim/content_blocks/announcement_settings_form_cell.rb +17 -0
  6. data/app/cells/decidim/content_blocks/how_to_participate/show.erb +1 -1
  7. data/app/cells/decidim/content_blocks/participatory_space_announcement_cell.rb +1 -3
  8. data/app/cells/decidim/content_blocks/participatory_space_hero_cell.rb +1 -5
  9. data/app/cells/decidim/{participatory_space_private_user_cell.rb → member_cell.rb} +1 -1
  10. data/app/cells/decidim/onboarding_action_message_cell.rb +1 -0
  11. data/app/cells/decidim/profile/details.erb +1 -1
  12. data/app/cells/decidim/profile_cell.rb +1 -0
  13. data/app/cells/decidim/profile_member_of/show.erb +13 -0
  14. data/app/cells/decidim/profile_member_of_cell.rb +35 -0
  15. data/app/cells/decidim/tags_cell.rb +1 -1
  16. data/app/cells/decidim/versions_list/show.erb +25 -0
  17. data/app/commands/decidim/create_follow.rb +3 -5
  18. data/app/commands/decidim/create_user_report.rb +1 -1
  19. data/app/commands/decidim/destroy_account.rb +3 -3
  20. data/app/commands/decidim/search.rb +5 -5
  21. data/app/controllers/concerns/decidim/ajax_permission_handler.rb +1 -1
  22. data/app/controllers/concerns/decidim/direct_upload.rb +3 -2
  23. data/app/controllers/concerns/decidim/ephemeral_session_checker.rb +1 -1
  24. data/app/controllers/concerns/decidim/force_authentication.rb +10 -1
  25. data/app/controllers/concerns/decidim/impersonate_users.rb +4 -0
  26. data/app/controllers/concerns/decidim/locale_switcher.rb +52 -2
  27. data/app/controllers/concerns/decidim/needs_tos_accepted.rb +1 -1
  28. data/app/controllers/concerns/decidim/participatory_space/has_members_page.rb +27 -0
  29. data/app/controllers/concerns/decidim/participatory_space_context.rb +1 -5
  30. data/app/controllers/decidim/account_controller.rb +1 -1
  31. data/app/controllers/decidim/amendments_controller.rb +5 -4
  32. data/app/controllers/decidim/components/base_controller.rb +20 -3
  33. data/app/controllers/decidim/devise/omniauth_registrations_controller.rb +1 -1
  34. data/app/controllers/decidim/devise/passwords_controller.rb +1 -1
  35. data/app/controllers/decidim/devise/registrations_controller.rb +1 -1
  36. data/app/controllers/decidim/devise/sessions_controller.rb +1 -1
  37. data/app/controllers/decidim/download_your_data_controller.rb +1 -1
  38. data/app/controllers/decidim/editor_images_controller.rb +1 -1
  39. data/app/controllers/decidim/follows_controller.rb +4 -3
  40. data/app/controllers/decidim/geolocation_controller.rb +1 -1
  41. data/app/controllers/decidim/likes_controller.rb +2 -1
  42. data/app/controllers/decidim/locales_controller.rb +3 -11
  43. data/app/controllers/decidim/messaging/conversations_controller.rb +3 -3
  44. data/app/controllers/decidim/newsletters_controller.rb +1 -1
  45. data/app/controllers/decidim/notifications_settings_controller.rb +1 -1
  46. data/app/controllers/decidim/notifications_subscriptions_controller.rb +1 -1
  47. data/app/controllers/decidim/open_data_controller.rb +1 -1
  48. data/app/controllers/decidim/report_users_controller.rb +2 -2
  49. data/app/controllers/decidim/reports_controller.rb +1 -1
  50. data/app/controllers/decidim/user_activities_controller.rb +1 -2
  51. data/app/events/decidim/welcome_notification_event.rb +1 -1
  52. data/app/forms/decidim/upload_validation_form.rb +1 -1
  53. data/app/helpers/concerns/decidim/flash_helper_extensions.rb +1 -0
  54. data/app/helpers/decidim/active_link_to_helper.rb +136 -0
  55. data/app/helpers/decidim/amendments_helper.rb +3 -3
  56. data/app/helpers/decidim/check_boxes_tree_helper.rb +2 -2
  57. data/app/helpers/decidim/component_path_helper.rb +9 -4
  58. data/app/helpers/decidim/filters_helper.rb +0 -2
  59. data/app/helpers/decidim/menu_helper.rb +3 -3
  60. data/app/helpers/decidim/meta_tags_helper.rb +0 -2
  61. data/app/helpers/decidim/paginate_helper.rb +14 -1
  62. data/app/helpers/decidim/sanitize_helper.rb +1 -0
  63. data/app/helpers/decidim/short_link_helper.rb +5 -5
  64. data/app/helpers/decidim/translations_helper.rb +4 -4
  65. data/app/jobs/decidim/delete_inactive_participants_job.rb +2 -2
  66. data/app/mailers/decidim/application_mailer.rb +7 -2
  67. data/app/mailers/decidim/decidim_devise_mailer.rb +1 -0
  68. data/app/mailers/decidim/reported_mailer.rb +4 -1
  69. data/app/models/decidim/action_log.rb +1 -1
  70. data/app/models/decidim/attachment.rb +2 -2
  71. data/app/models/decidim/authorization_transfer.rb +1 -1
  72. data/app/models/decidim/component.rb +6 -9
  73. data/app/models/decidim/content_block.rb +3 -3
  74. data/app/models/decidim/newsletter.rb +2 -2
  75. data/app/models/decidim/participatory_space/member.rb +55 -0
  76. data/app/models/decidim/resource_link.rb +1 -1
  77. data/app/models/decidim/short_link.rb +1 -1
  78. data/app/packs/src/decidim/controllers/assign_role/assign_role.test.js +80 -0
  79. data/app/packs/src/decidim/controllers/assign_role/controller.js +27 -0
  80. data/app/packs/src/decidim/controllers/main_menu/controller.js +115 -0
  81. data/app/packs/src/decidim/controllers/main_menu/main_menu.test.js +185 -0
  82. data/app/packs/src/decidim/datepicker/generate_datepicker.js +1 -13
  83. data/app/packs/src/decidim/editor/common/suggestion.js +1 -1
  84. data/app/packs/src/decidim/editor/extensions/decidim_kit/index.js +4 -1
  85. data/app/packs/src/decidim/editor/extensions/image/index.js +8 -5
  86. data/app/packs/src/decidim/editor/extensions/link/index.js +8 -0
  87. data/app/packs/src/decidim/editor/extensions/mention/index.js +18 -5
  88. data/app/packs/src/decidim/editor/extensions/mention_resource/index.js +16 -7
  89. data/app/packs/src/decidim/editor/test/extensions/decidim_kit.test.js +13 -4
  90. data/app/packs/src/decidim/editor/test/extensions/heading.test.js +1 -1
  91. data/app/packs/src/decidim/editor/test/extensions/image.test.js +26 -28
  92. data/app/packs/src/decidim/editor/test/extensions/mention.test.js +6 -2
  93. data/app/packs/src/decidim/editor/test/extensions/video_embed.test.js +27 -15
  94. data/app/packs/src/decidim/editor/test/helpers.js +8 -10
  95. data/app/packs/src/decidim/editor/test/toolbar/full.test.js +15 -4
  96. data/app/packs/src/decidim/editor/test/toolbar/shared/behaves_like_basic_block.js +8 -4
  97. data/app/packs/src/decidim/editor/test/toolbar/shared/behaves_like_basic_formatting.js +5 -1
  98. data/app/packs/src/decidim/editor/test/toolbar/shared/behaves_like_basic_indent.js +9 -5
  99. data/app/packs/src/decidim/editor/test/toolbar/shared/behaves_like_basic_link.js +5 -1
  100. data/app/packs/src/decidim/editor/test/toolbar/shared/behaves_like_basic_list.js +8 -4
  101. data/app/packs/src/decidim/editor/test/toolbar/shared/behaves_like_basic_styling.js +13 -9
  102. data/app/packs/src/decidim/editor/test/toolbar/shared/behaves_like_content_styling.js +6 -2
  103. data/app/packs/src/decidim/index.js +0 -1
  104. data/app/packs/stylesheets/decidim/_cards.scss +2 -1
  105. data/app/packs/stylesheets/decidim/_datepicker.scss +0 -8
  106. data/app/packs/stylesheets/decidim/_dropdown.scss +21 -31
  107. data/app/packs/stylesheets/decidim/_footer.scss +0 -36
  108. data/app/packs/stylesheets/decidim/_forms.scss +5 -1
  109. data/app/packs/stylesheets/decidim/_header.scss +148 -46
  110. data/app/packs/stylesheets/decidim/_language_chooser.scss +79 -0
  111. data/app/packs/stylesheets/decidim/application.scss +1 -0
  112. data/app/presenters/decidim/admin_log/participatory_space/member_presenter.rb +40 -0
  113. data/app/presenters/decidim/admin_log/static_page_resource_presenter.rb +1 -1
  114. data/app/presenters/decidim/authorization_transfer_presenter.rb +1 -1
  115. data/app/presenters/decidim/breadcrumb_root_menu_item_presenter.rb +3 -3
  116. data/app/presenters/decidim/log/user_presenter.rb +1 -0
  117. data/app/presenters/decidim/log/value_types/access_mode_presenter.rb +45 -0
  118. data/app/presenters/decidim/participatory_space/member_presenter.rb +48 -0
  119. data/app/presenters/decidim/resource_locator_presenter.rb +22 -3
  120. data/app/queries/decidim/last_activity.rb +1 -1
  121. data/app/serializers/decidim/exporters/participatory_space_serializer.rb +15 -1
  122. data/app/services/decidim/email_notification_generator.rb +2 -0
  123. data/app/services/decidim/notification_generator.rb +4 -0
  124. data/app/uploaders/decidim/organization_favicon_uploader.rb +4 -9
  125. data/app/validators/uploader_image_dimensions_validator.rb +13 -11
  126. data/app/views/decidim/homepage/show.html.erb +1 -1
  127. data/app/views/decidim/messaging/conversations/_new_conversation_button.html.erb +1 -1
  128. data/app/views/decidim/messaging/conversations/_reply.html.erb +1 -1
  129. data/app/views/decidim/messaging/conversations/new.html.erb +1 -1
  130. data/app/views/decidim/messaging/conversations/show.html.erb +2 -2
  131. data/app/views/decidim/participatory_space/members/_member.html.erb +1 -0
  132. data/app/views/decidim/shared/_results_per_page.html.erb +1 -1
  133. data/app/views/decidim/shared/filters/_check_boxes_tree.html.erb +1 -1
  134. data/app/views/decidim/shared/filters/_dropdown_label.html.erb +4 -3
  135. data/app/views/devise/mailer/{invite_private_user.html.erb → invite_member.html.erb} +2 -2
  136. data/app/views/devise/mailer/{invite_private_user.text.erb → invite_member.text.erb} +2 -2
  137. data/app/views/layouts/decidim/_head.html.erb +2 -2
  138. data/app/views/layouts/decidim/_wrapper.html.erb +1 -1
  139. data/app/views/layouts/decidim/footer/_focus_mode_main.html.erb +0 -1
  140. data/app/views/layouts/decidim/footer/_main.html.erb +0 -1
  141. data/app/views/layouts/decidim/header/_focus_mode_back_button.html.erb +4 -1
  142. data/app/views/layouts/decidim/header/_main.html.erb +17 -8
  143. data/app/views/layouts/decidim/header/_main_language_chooser.html.erb +24 -0
  144. data/app/views/layouts/decidim/header/_main_links_desktop.html.erb +56 -56
  145. data/app/views/layouts/decidim/header/_main_links_mobile_account.html.erb +2 -2
  146. data/app/views/layouts/decidim/header/_main_links_mobile_item_account.html.erb +3 -1
  147. data/app/views/layouts/decidim/header/_main_menu_mobile.html.erb +18 -5
  148. data/app/views/layouts/decidim/header/_main_search.html.erb +3 -3
  149. data/app/views/layouts/decidim/header/_menu.html.erb +5 -5
  150. data/app/views/layouts/decidim/header/_menu_breadcrumb_desktop.html.erb +13 -12
  151. data/app/views/layouts/decidim/header/_menu_breadcrumb_mobile.html.erb +28 -0
  152. data/config/initializers/invisible_captcha.rb +1 -0
  153. data/config/locales/ar.yml +3 -10
  154. data/config/locales/bg.yml +3 -10
  155. data/config/locales/ca-IT.yml +33 -19
  156. data/config/locales/ca.yml +33 -19
  157. data/config/locales/cs.yml +22 -19
  158. data/config/locales/de.yml +13 -18
  159. data/config/locales/el.yml +3 -7
  160. data/config/locales/en.yml +33 -19
  161. data/config/locales/es-MX.yml +33 -19
  162. data/config/locales/es-PY.yml +33 -19
  163. data/config/locales/es.yml +33 -19
  164. data/config/locales/eu.yml +33 -19
  165. data/config/locales/fi-plain.yml +33 -19
  166. data/config/locales/fi.yml +33 -19
  167. data/config/locales/fr-CA.yml +33 -19
  168. data/config/locales/fr.yml +33 -19
  169. data/config/locales/ga-IE.yml +3 -1
  170. data/config/locales/gl.yml +3 -7
  171. data/config/locales/hu.yml +3 -10
  172. data/config/locales/id-ID.yml +3 -7
  173. data/config/locales/it.yml +3 -10
  174. data/config/locales/ja.yml +18 -19
  175. data/config/locales/lb.yml +3 -8
  176. data/config/locales/lt.yml +3 -10
  177. data/config/locales/lv.yml +3 -7
  178. data/config/locales/nl.yml +3 -8
  179. data/config/locales/no.yml +3 -8
  180. data/config/locales/pl.yml +3 -10
  181. data/config/locales/pt-BR.yml +17 -19
  182. data/config/locales/pt.yml +3 -8
  183. data/config/locales/ro-RO.yml +10 -18
  184. data/config/locales/ru.yml +0 -6
  185. data/config/locales/sk.yml +33 -19
  186. data/config/locales/sv.yml +33 -19
  187. data/config/locales/tr-TR.yml +3 -8
  188. data/config/locales/uk.yml +0 -3
  189. data/config/locales/zh-CN.yml +3 -7
  190. data/config/locales/zh-TW.yml +3 -8
  191. data/config/routes.rb +139 -76
  192. data/db/data/20251213075429_rename_members_in_action_log.rb +22 -0
  193. data/db/data/20260319145808_rename_send_to_members_in_newsletter.rb +27 -0
  194. data/db/migrate/20200730142511_add_file_upload_settings_to_decidim_organizations.rb +13 -15
  195. data/db/migrate/20250523104311_move_cta_to_hero_content_block.rb +1 -1
  196. data/db/migrate/20250527122040_move_highlighted_content_banner_settings_to_content_block.rb +1 -1
  197. data/db/migrate/20250819110800_convert_private_exports_id_to_uuid.rb +2 -2
  198. data/db/migrate/20251112132305_remove_legacy_images_from_core_module.rb +27 -0
  199. data/db/migrate/20251205122428_rename_participatory_space_private_users_to_members.rb +9 -0
  200. data/db/migrate/20251216185133_rename_privatable_to_to_participatory_space_in_members.rb +10 -0
  201. data/db/migrate/20260208201405_remove_user_group_core.rb +21 -0
  202. data/db/migrate/20260208201406_remove_user_group_memberships.rb +30 -0
  203. data/db/migrate/20260208201407_remove_user_group_organizations.rb +11 -0
  204. data/db/migrate/20260217152425_remove_enable_participatory_space_filters_from_decidim_organizations.rb +7 -0
  205. data/db/migrate/20260314081619_add_index_on_decidim_members.rb +25 -0
  206. data/decidim-core.gemspec +25 -28
  207. data/lib/decidim/acts_as_tree.rb +12 -12
  208. data/lib/decidim/admin_filters_registry.rb +1 -1
  209. data/lib/decidim/amendable.rb +5 -1
  210. data/lib/decidim/api/functions/category_list.rb +1 -0
  211. data/lib/decidim/api/functions/component_finder_base.rb +1 -1
  212. data/lib/decidim/api/functions/component_list.rb +3 -1
  213. data/lib/decidim/api/functions/component_list_base.rb +1 -0
  214. data/lib/decidim/api/functions/participatory_space_finder_base.rb +1 -1
  215. data/lib/decidim/api/functions/participatory_space_list_base.rb +1 -0
  216. data/lib/decidim/api/functions/user_entity_list.rb +0 -1
  217. data/lib/decidim/api/interfaces/amendable_entity_interface.rb +1 -0
  218. data/lib/decidim/api/interfaces/amendable_interface.rb +1 -0
  219. data/lib/decidim/api/interfaces/attachable_collection_interface.rb +1 -0
  220. data/lib/decidim/api/interfaces/attachable_interface.rb +1 -0
  221. data/lib/decidim/api/interfaces/author_interface.rb +2 -1
  222. data/lib/decidim/api/interfaces/authorable_interface.rb +1 -0
  223. data/lib/decidim/api/interfaces/categories_container_interface.rb +1 -0
  224. data/lib/decidim/api/interfaces/categorizable_interface.rb +1 -0
  225. data/lib/decidim/api/interfaces/coauthorable_interface.rb +1 -0
  226. data/lib/decidim/api/interfaces/component_interface.rb +9 -8
  227. data/lib/decidim/api/interfaces/fingerprint_interface.rb +1 -0
  228. data/lib/decidim/api/interfaces/followable_interface.rb +2 -0
  229. data/lib/decidim/api/interfaces/likeable_interface.rb +2 -2
  230. data/lib/decidim/api/interfaces/localizable_interface.rb +1 -0
  231. data/lib/decidim/api/interfaces/participatory_space_interface.rb +1 -0
  232. data/lib/decidim/api/interfaces/referable_interface.rb +1 -0
  233. data/lib/decidim/api/interfaces/scopable_interface.rb +1 -0
  234. data/lib/decidim/api/interfaces/taxonomizable_interface.rb +1 -0
  235. data/lib/decidim/api/interfaces/timestamps_interface.rb +1 -0
  236. data/lib/decidim/api/interfaces/traceable_interface.rb +1 -0
  237. data/lib/decidim/api/types/attachment_collection_type.rb +5 -3
  238. data/lib/decidim/api/types/attachment_type.rb +11 -4
  239. data/lib/decidim/api/types/badge_score_type.rb +28 -0
  240. data/lib/decidim/api/types/moderation_type.rb +25 -0
  241. data/lib/decidim/api/types/participant_details_type.rb +17 -0
  242. data/lib/decidim/api/types/reportable_type.rb +18 -0
  243. data/lib/decidim/api/types/reportable_user_type.rb +17 -0
  244. data/lib/decidim/api/types/static_page_topic_type.rb +16 -0
  245. data/lib/decidim/api/types/static_page_type.rb +22 -0
  246. data/lib/decidim/api/types/taxonomy_type.rb +2 -0
  247. data/lib/decidim/api/types/user_moderation_type.rb +36 -0
  248. data/lib/decidim/api/types/user_type.rb +13 -0
  249. data/lib/decidim/asset_router/pipeline.rb +1 -1
  250. data/lib/decidim/asset_router/storage.rb +9 -9
  251. data/lib/decidim/assets/tailwind/tailwind.config.js.erb +3 -0
  252. data/lib/decidim/attribute_encryptor.rb +51 -10
  253. data/lib/decidim/attribute_object/model.rb +0 -5
  254. data/lib/decidim/block_registry.rb +1 -1
  255. data/lib/decidim/coauthorable.rb +2 -1
  256. data/lib/decidim/command.rb +1 -0
  257. data/lib/decidim/content_processor.rb +1 -1
  258. data/lib/decidim/core/api.rb +8 -0
  259. data/lib/decidim/core/content_blocks/registry_manager.rb +0 -6
  260. data/lib/decidim/core/engine.rb +2 -34
  261. data/lib/decidim/core/menu.rb +0 -26
  262. data/lib/decidim/core/test/factories.rb +29 -18
  263. data/lib/decidim/core/test/shared_examples/access_mode_restricted_participatory_spaces.rb +172 -0
  264. data/lib/decidim/core/test/shared_examples/access_mode_transparent_participatory_spaces.rb +241 -0
  265. data/lib/decidim/core/test/shared_examples/comments_examples.rb +115 -111
  266. data/lib/decidim/core/test/shared_examples/fingerprint_examples.rb +2 -0
  267. data/lib/decidim/core/test/shared_examples/has_members.rb +63 -0
  268. data/lib/decidim/core/test/shared_examples/manage_share_tokens_examples.rb +1 -1
  269. data/lib/decidim/core/test/shared_examples/map_examples.rb +1 -1
  270. data/lib/decidim/core/test/shared_examples/participant_details_examples.rb +16 -0
  271. data/lib/decidim/core/test/shared_examples/participatory_space_members_page_examples.rb +10 -10
  272. data/lib/decidim/core/test/shared_examples/resource_liked_event_examples.rb +1 -1
  273. data/lib/decidim/core/test/shared_examples/resource_locator_presenter_examples.rb +7 -7
  274. data/lib/decidim/core/test/shared_examples/searchable_participatory_space_examples.rb +3 -3
  275. data/lib/decidim/core/test/shared_examples/searchable_results_examples.rb +2 -2
  276. data/lib/decidim/core/test/shared_examples/simple_event.rb +1 -0
  277. data/lib/decidim/core/test/shared_examples/translated_event_examples.rb +2 -2
  278. data/lib/decidim/core/test/shared_examples/versions_controller_examples.rb +1 -1
  279. data/lib/decidim/core/test.rb +2 -1
  280. data/lib/decidim/core/version.rb +1 -1
  281. data/lib/decidim/core.rb +222 -335
  282. data/lib/decidim/download_your_data_serializers/{download_your_data_participatory_space_private_user_serializer.rb → download_your_data_member_serializer.rb} +6 -6
  283. data/lib/decidim/download_your_data_serializers.rb +2 -2
  284. data/lib/decidim/engine_router.rb +31 -6
  285. data/lib/decidim/exporters/csv.rb +1 -1
  286. data/lib/decidim/filter_form_builder.rb +1 -1
  287. data/{app/controllers/concerns → lib}/decidim/form_factory.rb +11 -1
  288. data/lib/decidim/has_settings.rb +2 -2
  289. data/lib/decidim/legacy_form_builder.rb +1 -0
  290. data/lib/decidim/locale_router_detector.rb +41 -0
  291. data/lib/decidim/maintenance/import_models/scope.rb +1 -1
  292. data/lib/decidim/maintenance/import_models.rb +0 -2
  293. data/lib/decidim/map/frontend.rb +1 -1
  294. data/lib/decidim/map/geocoding.rb +2 -2
  295. data/lib/decidim/map/static_map.rb +10 -22
  296. data/lib/decidim/map/utility.rb +3 -6
  297. data/lib/decidim/map.rb +2 -2
  298. data/lib/decidim/menu.rb +1 -1
  299. data/lib/decidim/menu_item.rb +1 -0
  300. data/lib/decidim/menu_registry.rb +1 -1
  301. data/lib/decidim/moderation_tools.rb +1 -1
  302. data/lib/decidim/newsletter_encryptor.rb +5 -8
  303. data/lib/decidim/organization_settings.rb +2 -2
  304. data/lib/decidim/participable.rb +1 -0
  305. data/lib/decidim/participatory_space/has_members.rb +61 -0
  306. data/lib/decidim/participatory_space_manifest.rb +0 -4
  307. data/lib/decidim/participatory_space_resourceable.rb +10 -1
  308. data/lib/decidim/routes/locale_redirects.rb +47 -0
  309. data/lib/decidim/routes.rb +7 -0
  310. data/lib/decidim/search_resource_fields_mapper.rb +3 -4
  311. data/lib/decidim/seeds.rb +17 -0
  312. data/lib/decidim/shakapacker/runner.rb +4 -4
  313. data/lib/decidim/shakapacker/shakapacker.yml +28 -3
  314. data/lib/decidim/taxonomizable.rb +1 -1
  315. data/lib/decidim/translatable_attributes.rb +9 -3
  316. data/lib/decidim/view_model.rb +4 -0
  317. data/lib/devise/models/decidim_newsletterable.rb +1 -1
  318. data/lib/tasks/decidim_procfile.rake +1 -1
  319. data/lib/tasks/decidim_tasks.rake +0 -1
  320. data/lib/tasks/decidim_taxonomies.rake +1 -1
  321. data/lib/tasks/upgrade/decidim_active_storage_migration_tasks.rake +0 -1
  322. data/lib/tasks/upgrade/decidim_encryption.rake +12 -0
  323. data/lib/tasks/upgrade/decidim_migrate_wysiwyg_content.rake +0 -1
  324. data/lib/tasks/upgrade/decidim_remove_deleted_users_left_data_tasks.rake +1 -1
  325. data/lib/tasks/upgrade/{fix_deleted_private_follows.rake → fix_deleted_members_follows.rake} +5 -5
  326. data/lib/tasks/upgrade/migrations.rake +0 -1
  327. data/lib/tasks/upgrade/user_groups_migration.rake +0 -1
  328. metadata +206 -130
  329. data/app/cells/decidim/content_blocks/global_menu/show.erb +0 -11
  330. data/app/cells/decidim/content_blocks/global_menu_cell.rb +0 -35
  331. data/app/cells/decidim/content_blocks/menu_breadcrumb_last_activity/show.erb +0 -10
  332. data/app/cells/decidim/content_blocks/menu_breadcrumb_last_activity_cell.rb +0 -49
  333. data/app/cells/decidim/participatory_space_dropdown_metadata/links.erb +0 -9
  334. data/app/cells/decidim/participatory_space_dropdown_metadata/metadata.erb +0 -1
  335. data/app/cells/decidim/participatory_space_dropdown_metadata/show.erb +0 -11
  336. data/app/cells/decidim/participatory_space_dropdown_metadata_cell.rb +0 -26
  337. data/app/controllers/concerns/decidim/has_members_page.rb +0 -25
  338. data/app/jobs/decidim/newsletters_opt_in_job.rb +0 -11
  339. data/app/jobs/decidim/user_report_job.rb +0 -11
  340. data/app/models/decidim/participatory_space_private_user.rb +0 -51
  341. data/app/packs/src/decidim/controllers/sticky_header/controller.js +0 -116
  342. data/app/packs/src/decidim/controllers/sticky_header/sticky_header.test.js +0 -320
  343. data/app/packs/src/decidim/dropdown_menu.js +0 -18
  344. data/app/packs/src/decidim/identity_selector_dialog.js +0 -33
  345. data/app/presenters/decidim/admin_log/participatory_space_private_user_presenter.rb +0 -38
  346. data/app/presenters/decidim/participatory_space_private_user_presenter.rb +0 -46
  347. data/app/views/decidim/participatory_space_private_users/_participatory_space_private_user.html.erb +0 -1
  348. data/app/views/layouts/decidim/footer/_main_language_chooser.html.erb +0 -26
  349. data/app/views/layouts/decidim/header/_menu_breadcrumb_items.html.erb +0 -34
  350. data/app/views/layouts/decidim/header/_menu_breadcrumb_main_dropdown_desktop.html.erb +0 -10
  351. data/app/views/layouts/decidim/header/_menu_breadcrumb_main_dropdown_mobile.html.erb +0 -17
  352. data/app/views/layouts/decidim/header/_menu_breadcrumb_main_dropdown_top_left.html.erb +0 -2
  353. data/app/views/layouts/decidim/header/_menu_breadcrumb_mobile_tablet.html.erb +0 -33
  354. data/app/views/layouts/decidim/header/_mobile_language_choose.html.erb +0 -25
  355. data/lib/decidim/core/test/shared_examples/has_private_users.rb +0 -63
  356. data/lib/decidim/core/test/shared_examples/participatory_space_dropdown_metadata_cell_examples.rb +0 -81
  357. data/lib/decidim/has_private_users.rb +0 -57
  358. data/lib/decidim/maintenance/import_models/assembly_type.rb +0 -49
  359. data/lib/decidim/maintenance/import_models/participatory_process_type.rb +0 -21
  360. data/lib/decidim/query_extensions.rb +0 -64
  361. /data/app/cells/decidim/{participatory_space_private_user → member}/show.erb +0 -0
@@ -33,6 +33,14 @@ export default Link.extend({
33
33
  }
34
34
  },
35
35
 
36
+ renderHTML({ HTMLAttributes }) {
37
+ const attrs = { ...HTMLAttributes };
38
+ if (attrs.target === "") {
39
+ Reflect.deleteProperty(attrs, "target");
40
+ }
41
+ return ["a", attrs, 0];
42
+ },
43
+
36
44
  addCommands() {
37
45
  const i18n = getDictionary("editor.extensions.link");
38
46
 
@@ -32,11 +32,10 @@ export default Mention.extend({
32
32
 
33
33
  return {
34
34
  ...parentOptions,
35
- renderLabel({ node }) {
36
- // The labels are formed based on the nicknames returned by the API
37
- // which already contain the suggestion character, so there is no need
38
- // to display it twice.
39
- return `${node.attrs.label ?? node.attrs.id}`
35
+ renderText({ node }) {
36
+ // renderText is used to create the DOM representation
37
+ const label = node.attrs.label ?? node.attrs.id;
38
+ return label;
40
39
  },
41
40
  suggestion: {
42
41
  ...parentOptions?.suggestion,
@@ -59,6 +58,20 @@ export default Mention.extend({
59
58
  };
60
59
  },
61
60
 
61
+ renderHTML({ node }) {
62
+ // renderHTML is used for visual rendering getHTML()
63
+ const label = node.attrs.label ?? node.attrs.id;
64
+ return [
65
+ "span",
66
+ {
67
+ "data-type": "mention",
68
+ "data-id": node.attrs.id,
69
+ "data-label": node.attrs.label
70
+ },
71
+ label
72
+ ];
73
+ },
74
+
62
75
  addNodeView() {
63
76
  return createNodeView(this);
64
77
  }
@@ -27,18 +27,14 @@ const searchResources = async (queryText) => {
27
27
 
28
28
  export default Mention.extend({
29
29
  name: "mentionResource",
30
-
31
30
  addOptions() {
32
31
  const options = this.parent?.();
33
32
  const suggestion = options?.suggestion;
34
-
35
33
  return {
36
34
  ...options,
37
- renderLabel({ node }) {
38
- // The labels are formed based on the titles returned by the API
39
- // which already contain the suggestion character, so there is no need
40
- // to display it twice.
41
- return `${node.attrs.label ?? node.attrs.id}`
35
+ renderText({ node }) {
36
+ // renderText is used to create the DOM representation
37
+ return node.attrs.label ?? node.attrs.id;
42
38
  },
43
39
  suggestion: {
44
40
  ...suggestion,
@@ -63,6 +59,19 @@ export default Mention.extend({
63
59
  };
64
60
  },
65
61
 
62
+ renderHTML({ node }) {
63
+ // renderHTML is used for visual rendering getHTML()
64
+ return [
65
+ "span",
66
+ {
67
+ "data-type": "mentionResource",
68
+ "data-id": node.attrs.id,
69
+ "data-label": node.attrs.label
70
+ },
71
+ node.attrs.label ?? node.attrs.id
72
+ ];
73
+ },
74
+
66
75
  addNodeView() {
67
76
  return createNodeView(this);
68
77
  }
@@ -32,15 +32,24 @@ describe("DecidimKit", () => {
32
32
  "code",
33
33
  "doc",
34
34
  "dropCursor",
35
- "gapCursor",
36
35
  "hardBreak",
37
- "history",
36
+ "paragraph",
37
+ "link",
38
+ "decidimKit",
39
+ "starterKit",
38
40
  "horizontalRule",
39
41
  "italic",
40
42
  "listItem",
41
- "paragraph",
42
43
  "strike",
43
- "text"
44
+ "text",
45
+ "characterCount",
46
+ "bold",
47
+ "dialog",
48
+ "indent",
49
+ "orderedList",
50
+ "codeBlock",
51
+ "underline",
52
+ "heading"
44
53
  ].forEach((name) => expect(extensions).toContain(name));
45
54
  });
46
55
 
@@ -51,6 +51,6 @@ describe("Heading", () => {
51
51
  editorElement.focus();
52
52
  await updateContent(editorElement, "# ");
53
53
 
54
- expect(editor.getHTML()).toMatchHtml("<h2></h2>");
54
+ expect(editor.getHTML()).toMatchHtml("<p># </p>");
55
55
  });
56
56
  });
@@ -28,24 +28,7 @@ describe("Image", () => {
28
28
  let uploadFilePath = "/path/to/image.jpg";
29
29
  let uploadDialogElement = null;
30
30
  let editorInnerHTML = (dim, src, alt) => {
31
- return `
32
- <div data-image-resizer="" class="ProseMirror-selectednode" draggable="true">
33
- <div data-image-resizer-wrapper="">
34
- <button type="button" data-image-resizer-control="top-left"></button>
35
- <button type="button" data-image-resizer-control="top-right"></button>
36
- <button type="button" data-image-resizer-control="bottom-left"></button>
37
- <button type="button" data-image-resizer-control="bottom-right"></button>
38
- <div data-image-resizer-dimensions="">
39
- <span data-image-resizer-dimension="width" data-image-resizer-dimension-value="${dim}"></span>
40
- ×
41
- <span data-image-resizer-dimension="height" data-image-resizer-dimension-value="${dim}"></span>
42
- </div>
43
- <div class="editor-content-image" data-image="">
44
- <img src="${src}" alt="${alt}">
45
- </div>
46
- </div>
47
- </div>
48
- `
31
+ return `<div data-image-resizer="" class="ProseMirror-selectednode" draggable="true"><div data-image-resizer-wrapper=""><button type="button" data-image-resizer-control="top-left"></button><button type="button" data-image-resizer-control="top-right"></button><button type="button" data-image-resizer-control="bottom-left"></button><button type="button" data-image-resizer-control="bottom-right"></button><div data-image-resizer-dimensions=""><span data-image-resizer-dimension="width" data-image-resizer-dimension-value="${dim}"></span>×<span data-image-resizer-dimension="height" data-image-resizer-dimension-value="${dim}"></span></div><div class="editor-content-image" data-image=""><img src="${src}" alt="${alt}"></div></div></div>`;
49
32
  }
50
33
 
51
34
  const updateFile = async (path, alt) => {
@@ -63,6 +46,10 @@ describe("Image", () => {
63
46
  await sleep(0);
64
47
  }
65
48
 
49
+ const normalizeHTML = (html) => {
50
+ return html.replace(/<p><br class="ProseMirror-trailingBreak"><\/p>/g, "").replace(/<p><\/p>/g, "");
51
+ };
52
+
66
53
  beforeEach(() => {
67
54
  document.body.innerHTML = "";
68
55
 
@@ -109,7 +96,8 @@ describe("Image", () => {
109
96
  it("editing setting the image through the dialog", async () => {
110
97
  editorElement.focus();
111
98
  await updateContent(editorElement,
112
- '<div class="editor-content-image" data-image=""><img src="/path/to/image.jpg" alt="Test text"></div>'
99
+ '<div class="editor-content-image" data-image=""><img src="/path/to/image.jpg" alt="Test text"></div>',
100
+ editor
113
101
  );
114
102
 
115
103
  editor.commands.imageDialog();
@@ -117,7 +105,7 @@ describe("Image", () => {
117
105
  await updateFile("/path/to/image_updated.jpg", "Updated text")
118
106
  expect(editorElement.classList.contains("dialog-open")).toBe(false);
119
107
 
120
- expect(editorElement.innerHTML).toMatchHtml(editorInnerHTML("null", "/path/to/image_updated.jpg", "Updated text"));
108
+ expect(normalizeHTML(editorElement.innerHTML)).toMatchHtml(editorInnerHTML("null", "/path/to/image_updated.jpg", "Updated text"));
121
109
  expect(editor.getHTML()).toMatchHtml(`
122
110
  <div class="editor-content-image" data-image=""><img src="/path/to/image_updated.jpg" alt="Updated text"></div>
123
111
  `);
@@ -126,16 +114,26 @@ describe("Image", () => {
126
114
  it("allows double clicking the image", async () => {
127
115
  editorElement.focus();
128
116
  await updateContent(editorElement,
129
- '<div class="editor-content-image" data-image=""><img src="/path/to/image.jpg" alt="Test text"></div>'
117
+ '<div class="editor-content-image" data-image=""><img src="/path/to/image.jpg" alt="Test text"></div>',
118
+ editor
130
119
  );
131
120
 
132
121
  jest.spyOn(uploadDialogElement.dialog, "open");
133
122
 
134
123
  // Position calculations do not work with JSDom / Jest
135
- editor.view.posAtCoords = jest.fn().mockReturnValue({ pos: 1, inside: -1 });
136
- editorElement.dispatchEvent(new MouseEvent("mousedown", { button: 0, clientX: 10, clientY: 10 }));
137
- editorElement.dispatchEvent(new MouseEvent("mousedown", { button: 0, clientX: 10, clientY: 10 }));
138
- await updateFile("/path/to/image_updated.jpg", "Updated text")
124
+ editor.view.posAtCoords = jest.fn().mockReturnValue({ pos: 0, inside: 0 });
125
+
126
+ const mockEvent = new MouseEvent("dblclick", {
127
+ button: 0,
128
+ clientX: 10,
129
+ clientY: 10
130
+ });
131
+
132
+ editor.view.someProp("handleDoubleClick", (click) => click(editor.view, 0, mockEvent));
133
+
134
+ await sleep(0);
135
+
136
+ await updateFile("/path/to/image_updated.jpg", "Updated text");
139
137
 
140
138
  expect(uploadDialogElement.dialog.open).toHaveBeenCalled();
141
139
  expect(editor.getHTML()).toMatchHtml(`
@@ -283,9 +281,9 @@ describe("Image", () => {
283
281
  });
284
282
 
285
283
  editorElement.focus();
286
- await updateContent(editorElement,
287
- '<div class="editor-content-image" data-image=""><img src="/path/to/image.jpg" alt="Test text"></div>'
288
- );
284
+
285
+ editor.commands.setImage({ src: "/path/to/image.jpg", alt: "Test text" });
286
+ await sleep(0);
289
287
  });
290
288
  describe("with mouse", () => behavesLikeImageResizer("mouse"));
291
289
 
@@ -64,6 +64,10 @@ describe("Mention", () => {
64
64
  let editor = null;
65
65
  let editorElement = null;
66
66
 
67
+ const normalizeHTML = (html) => {
68
+ return html.replace(/\s*data-mention-suggestion-char="[^"]*"/g, "");
69
+ };
70
+
67
71
  beforeEach(() => {
68
72
  document.body.innerHTML = "";
69
73
 
@@ -108,7 +112,7 @@ describe("Mention", () => {
108
112
  expect(editorElement.innerHTML).toEqual(
109
113
  '<p><span data-suggestion="mention" data-id="@johndoe" data-label="@johndoe (John Doe)">@johndoe (John Doe)</span> </p>'
110
114
  );
111
- expect(editor.getHTML()).toEqual(
115
+ expect(normalizeHTML(editor.getHTML())).toEqual(
112
116
  '<p><span data-type="mention" data-id="@johndoe" data-label="@johndoe (John Doe)">@johndoe (John Doe)</span> </p>'
113
117
  );
114
118
  });
@@ -122,7 +126,7 @@ describe("Mention", () => {
122
126
  expect(editorElement.innerHTML).toEqual(
123
127
  '<p><span data-suggestion="mention" data-id="@johndoe" data-label="@johndoe (John Doe)">@johndoe (John Doe)</span> </p>'
124
128
  );
125
- expect(editor.getHTML()).toEqual(
129
+ expect(normalizeHTML(editor.getHTML())).toEqual(
126
130
  '<p><span data-type="mention" data-id="@johndoe" data-label="@johndoe (John Doe)">@johndoe (John Doe)</span> </p>'
127
131
  );
128
132
  });
@@ -9,6 +9,10 @@ describe("VideoEmbed", () => {
9
9
  let editor = null;
10
10
  let editorElement = null;
11
11
 
12
+ const normalizeHTML = (html) => {
13
+ return html.replace(/<p><br class="ProseMirror-trailingBreak"><\/p>/g, "").replace(/<p><\/p>/g, "");
14
+ };
15
+
12
16
  beforeEach(() => {
13
17
  document.body.innerHTML = "";
14
18
 
@@ -35,7 +39,7 @@ describe("VideoEmbed", () => {
35
39
  </div>
36
40
  </div>
37
41
  `);
38
- expect(editor.getHTML()).toMatchHtml(`
42
+ expect(normalizeHTML(editor.getHTML())).toMatchHtml(`
39
43
  <div class="editor-content-videoEmbed" data-video-embed="https://www.youtube.com/watch?v=f6JMgJAQ2tc">
40
44
  <div>
41
45
  <iframe src="https://www.youtube-nocookie.com/embed/f6JMgJAQ2tc?cc_load_policy=1&amp;modestbranding=1" title="Decidim" frameborder="0" allowfullscreen="true"></iframe>
@@ -52,21 +56,21 @@ describe("VideoEmbed", () => {
52
56
  <iframe src="https://www.youtube-nocookie.com/embed/f6JMgJAQ2tc?cc_load_policy=1&amp;modestbranding=1" title="Decidim" frameborder="0" allowfullscreen="true"></iframe>
53
57
  </div>
54
58
  </div>
55
- `);
59
+ `, editor);
56
60
 
57
61
  editor.commands.setVideo({
58
62
  src: "https://www.youtube.com/watch?v=zhMMW0TENNA",
59
63
  title: "Free Open-Source"
60
64
  });
61
65
 
62
- expect(editorElement.innerHTML).toMatchHtml(`
66
+ expect(normalizeHTML(editorElement.innerHTML)).toMatchHtml(`
63
67
  <div class="editor-content-videoEmbed ProseMirror-selectednode" data-video-embed="https://www.youtube.com/watch?v=zhMMW0TENNA" draggable="true">
64
68
  <div>
65
69
  <iframe src="https://www.youtube-nocookie.com/embed/zhMMW0TENNA?cc_load_policy=1&amp;modestbranding=1" title="Free Open-Source" frameborder="0" allowfullscreen="true"></iframe>
66
70
  </div>
67
71
  </div>
68
72
  `);
69
- expect(editor.getHTML()).toMatchHtml(`
73
+ expect(normalizeHTML(editor.getHTML())).toMatchHtml(`
70
74
  <div class="editor-content-videoEmbed" data-video-embed="https://www.youtube.com/watch?v=zhMMW0TENNA">
71
75
  <div>
72
76
  <iframe src="https://www.youtube-nocookie.com/embed/zhMMW0TENNA?cc_load_policy=1&amp;modestbranding=1" title="Free Open-Source" frameborder="0" allowfullscreen="true"></iframe>
@@ -83,7 +87,7 @@ describe("VideoEmbed", () => {
83
87
  <iframe src="https://www.youtube-nocookie.com/embed/f6JMgJAQ2tc?cc_load_policy=1&amp;modestbranding=1" title="Decidim" frameborder="0" allowfullscreen="true"></iframe>
84
88
  </div>
85
89
  </div>
86
- `);
90
+ `, editor);
87
91
 
88
92
  editor.commands.videoEmbedDialog();
89
93
  expect(editorElement.classList.contains("dialog-open")).toBe(true);
@@ -94,7 +98,7 @@ describe("VideoEmbed", () => {
94
98
  dialog.querySelector("[data-dialog-actions] button[data-action='save']").click();
95
99
  await sleep(50);
96
100
 
97
- expect(editor.getHTML()).toMatchHtml(`
101
+ expect(normalizeHTML(editor.getHTML())).toMatchHtml(`
98
102
  <div class="editor-content-videoEmbed" data-video-embed="https://www.youtube.com/watch?v=f6JMgJAQ2tc">
99
103
  <div>
100
104
  <iframe src="https://www.youtube-nocookie.com/embed/f6JMgJAQ2tc?cc_load_policy=1&amp;modestbranding=1" title="Decidim" frameborder="0" allowfullscreen="true"></iframe>
@@ -113,7 +117,7 @@ describe("VideoEmbed", () => {
113
117
  dialog.querySelector("[data-dialog-actions] button[data-action='save']").click();
114
118
  await sleep(50);
115
119
 
116
- expect(editor.getHTML()).toMatchHtml(`
120
+ expect(normalizeHTML(editor.getHTML())).toMatchHtml(`
117
121
  <div class="editor-content-videoEmbed" data-video-embed="https://www.youtube.com/watch?v=zhMMW0TENNA">
118
122
  <div>
119
123
  <iframe src="https://www.youtube-nocookie.com/embed/zhMMW0TENNA?cc_load_policy=1&amp;modestbranding=1" title="Free Open-Source" frameborder="0" allowfullscreen="true"></iframe>
@@ -125,7 +129,7 @@ describe("VideoEmbed", () => {
125
129
  it("allows pasting a YouTube video", async () => {
126
130
  await pasteContent(editorElement, "https://www.youtube.com/watch?v=f6JMgJAQ2tc");
127
131
 
128
- expect(editor.getHTML()).toMatchHtml(`
132
+ expect(normalizeHTML(editor.getHTML())).toMatchHtml(`
129
133
  <div class="editor-content-videoEmbed" data-video-embed="https://www.youtube.com/watch?v=f6JMgJAQ2tc">
130
134
  <div>
131
135
  <iframe src="https://www.youtube-nocookie.com/embed/f6JMgJAQ2tc?cc_load_policy=1&amp;modestbranding=1" title="" frameborder="0" allowfullscreen="true"></iframe>
@@ -137,7 +141,7 @@ describe("VideoEmbed", () => {
137
141
  it("allows pasting a Vimeo video", async () => {
138
142
  await pasteContent(editorElement, "https://vimeo.com/312909656");
139
143
 
140
- expect(editor.getHTML()).toMatchHtml(`
144
+ expect(normalizeHTML(editor.getHTML())).toMatchHtml(`
141
145
  <div class="editor-content-videoEmbed" data-video-embed="https://vimeo.com/312909656">
142
146
  <div>
143
147
  <iframe src="https://player.vimeo.com/video/312909656" title="" frameborder="0" allowfullscreen="true"></iframe>
@@ -154,13 +158,21 @@ describe("VideoEmbed", () => {
154
158
  <iframe src="https://www.youtube-nocookie.com/embed/f6JMgJAQ2tc?cc_load_policy=1&amp;modestbranding=1" title="Decidim" frameborder="0" allowfullscreen="true"></iframe>
155
159
  </div>
156
160
  </div>
157
- `);
161
+ `, editor);
162
+
163
+ // Position calculations do not work with JSDom / Jest.
164
+ editor.view.posAtCoords = jest.fn().mockReturnValue({ pos: 0, inside: 0 });
165
+
166
+ // Manually trigger the handleDoubleClick handler directly.
167
+ const mockEvent = new MouseEvent("dblclick", {
168
+ bubbles: true,
169
+ cancelable: true,
170
+ clientX: 10,
171
+ clientY: 10
172
+ });
158
173
 
159
- // Position calculations do not work with JSDom / Jest
160
- editor.view.posAtCoords = jest.fn().mockReturnValue({ pos: 1, inside: -1 });
174
+ editor.view.someProp("handleDoubleClick", (click) => click(editor.view, 0, mockEvent));
161
175
 
162
- editorElement.dispatchEvent(new MouseEvent("mousedown", { clientX: 10, clientY: 10 }));
163
- editorElement.dispatchEvent(new MouseEvent("mousedown", { clientX: 10, clientY: 10 }));
164
176
  await sleep(0);
165
177
 
166
178
  expect(editorElement.classList.contains("dialog-open")).toBe(true);
@@ -172,7 +184,7 @@ describe("VideoEmbed", () => {
172
184
  dialog.querySelector("[data-dialog-actions] button[data-action='save']").click();
173
185
  await sleep(50);
174
186
 
175
- expect(editor.getHTML()).toMatchHtml(`
187
+ expect(normalizeHTML(editor.getHTML())).toMatchHtml(`
176
188
  <div class="editor-content-videoEmbed" data-video-embed="https://www.youtube.com/watch?v=zhMMW0TENNA">
177
189
  <div>
178
190
  <iframe src="https://www.youtube-nocookie.com/embed/zhMMW0TENNA?cc_load_policy=1&amp;modestbranding=1" title="Decidim" frameborder="0" allowfullscreen="true"></iframe>
@@ -76,16 +76,14 @@ Object.assign(Range.prototype, {
76
76
 
77
77
  export const sleep = async (time) => new Promise((resolve) => setTimeout(resolve, time));
78
78
 
79
- export const updateContent = async (editable, content) => {
80
- // ProseMirror listens has a mutation observer which listens to the changes on
81
- // the contenteditable element. This forces us to manually change the
82
- // `innerHTML` of the element to cause a change event to be triggered for the
83
- // editor.
84
- editable.innerHTML = content;
85
-
86
- // We need to wait for the mutation observer to complete its logic. It has a
87
- // timeout of 20 milliseconds by default which is the minimum amount we need
88
- // to wait.
79
+ export const updateContent = async (editable, content, editor) => {
80
+ if (editor) {
81
+ // Allows the proper use of TipTap's setContent method
82
+ editor.commands.setContent(content);
83
+ } else {
84
+ // Fallback for cases where editor is not available
85
+ editable.innerHTML = content;
86
+ }
89
87
  await sleep(50);
90
88
  };
91
89
 
@@ -53,6 +53,12 @@ describe("full toolbar", () => {
53
53
  });
54
54
 
55
55
  describe("videoEmbed", () => {
56
+
57
+ const normalizeHTML = (html) => {
58
+ return html.replace(/\s*data-mention-suggestion-char="[^"]*"/g, "").
59
+ replace(/<p><br class="ProseMirror-trailingBreak"><\/p>/g, "");
60
+ };
61
+
56
62
  it("creates a new video embed with the provided details at the end of the selection", async () => {
57
63
  await setContent("Hello, world!");
58
64
  prosemirror.focus();
@@ -69,7 +75,7 @@ describe("full toolbar", () => {
69
75
  // handled
70
76
  await sleep(0);
71
77
 
72
- expect(prosemirror.innerHTML).toMatchHtml(
78
+ expect(normalizeHTML(prosemirror.innerHTML)).toMatchHtml(
73
79
  `
74
80
  <p>Hello, world!</p>
75
81
  <div class="editor-content-videoEmbed ProseMirror-selectednode" data-video-embed="https://www.youtube.com/watch?v=f6JMgJAQ2tc" draggable="true">
@@ -112,7 +118,7 @@ describe("full toolbar", () => {
112
118
  // handled
113
119
  await sleep(0);
114
120
 
115
- expect(prosemirror.innerHTML).toMatchHtml(
121
+ expect(normalizeHTML(prosemirror.innerHTML)).toMatchHtml(
116
122
  `
117
123
  <p>Hello, world!</p>
118
124
  <div class="editor-content-videoEmbed ProseMirror-selectednode" data-video-embed="https://www.youtube.com/watch?v=zhMMW0TENNA" draggable="true">
@@ -148,6 +154,11 @@ describe("full toolbar", () => {
148
154
  window.Decidim.currentDialogs[dialog] = dialogEl.dialog;
149
155
  }
150
156
 
157
+ const normalizeHTML = (html) => {
158
+ return html.replace(/\s*data-mention-suggestion-char="[^"]*"/g, "").
159
+ replace(/<p><br class="ProseMirror-trailingBreak"><\/p>/g, "");
160
+ };
161
+
151
162
  beforeEach(() => {
152
163
  const csrf = document.createElement("meta");
153
164
  csrf.setAttribute("name", "csrf-token")
@@ -174,7 +185,7 @@ describe("full toolbar", () => {
174
185
 
175
186
  await sleep(0);
176
187
 
177
- expect(prosemirror.innerHTML).toMatchHtml(
188
+ expect(normalizeHTML(prosemirror.innerHTML)).toMatchHtml(
178
189
  `
179
190
  <p>Hello, world!</p>
180
191
  <div data-image-resizer="" class="ProseMirror-selectednode" draggable="true">
@@ -218,7 +229,7 @@ describe("full toolbar", () => {
218
229
 
219
230
  await sleep(0);
220
231
 
221
- expect(prosemirror.innerHTML).toMatchHtml(
232
+ expect(normalizeHTML(prosemirror.innerHTML)).toMatchHtml(
222
233
  `
223
234
  <p>Hello, world!</p>
224
235
  <div data-image-resizer="" class="ProseMirror-selectednode" draggable="true">
@@ -5,13 +5,17 @@ import contextHelpers from "src/decidim/editor/test/toolbar/shared/context";
5
5
  export default (ctx) => {
6
6
  const { getControl, setContent } = contextHelpers(ctx);
7
7
 
8
+ const normalizeHTML = (html) => {
9
+ return html.replace(/<p><br class="ProseMirror-trailingBreak"><\/p>$/g, "").trim();
10
+ };
11
+
8
12
  describe("codeBlock", () => {
9
13
  it("creates a new code block", async () => {
10
14
  await setContent("Hello, world!");
11
15
  selectContent(ctx.prosemirror);
12
16
  getControl("codeBlock").click();
13
17
 
14
- expect(ctx.prosemirror.innerHTML).toEqual("<pre><code>Hello, world!</code></pre>");
18
+ expect(normalizeHTML(ctx.prosemirror.innerHTML)).toEqual("<pre><code>Hello, world!</code></pre>");
15
19
  });
16
20
 
17
21
  it("makes existing code block content as normal text", async () => {
@@ -20,7 +24,7 @@ export default (ctx) => {
20
24
  selectContent(ctx.prosemirror, "pre code");
21
25
  getControl("codeBlock").click();
22
26
 
23
- expect(ctx.prosemirror.innerHTML).toEqual("<p>Hello, world!</p>");
27
+ expect(normalizeHTML(ctx.prosemirror.innerHTML)).toEqual("<p>Hello, world!</p>");
24
28
  });
25
29
  });
26
30
 
@@ -30,7 +34,7 @@ export default (ctx) => {
30
34
  selectContent(ctx.prosemirror);
31
35
  getControl("blockquote").click();
32
36
 
33
- expect(ctx.prosemirror.innerHTML).toEqual("<blockquote><p>Hello, world!</p></blockquote>");
37
+ expect(normalizeHTML(ctx.prosemirror.innerHTML)).toEqual("<blockquote><p>Hello, world!</p></blockquote>");
34
38
  });
35
39
 
36
40
  it("makes existing blockquote content as normal text", async () => {
@@ -39,7 +43,7 @@ export default (ctx) => {
39
43
  selectContent(ctx.prosemirror, "blockquote p");
40
44
  getControl("blockquote").click();
41
45
 
42
- expect(ctx.prosemirror.innerHTML).toEqual("<p>Hello, world!</p>");
46
+ expect(normalizeHTML(ctx.prosemirror.innerHTML)).toEqual("<p>Hello, world!</p>");
43
47
  });
44
48
  });
45
49
  };
@@ -3,6 +3,10 @@ import contextHelpers from "src/decidim/editor/test/toolbar/shared/context";
3
3
  export default (ctx) => {
4
4
  const { getControl, setContent } = contextHelpers(ctx);
5
5
 
6
+ const normalizeHTML = (html) => {
7
+ return html.replace(/<p><br class="ProseMirror-trailingBreak"><\/p>$/g, "").trim();
8
+ };
9
+
6
10
  describe("hardBreak", () => {
7
11
  it("creates a new line break at the cursor position", async () => {
8
12
  await setContent("Hello, world!");
@@ -11,7 +15,7 @@ export default (ctx) => {
11
15
 
12
16
  // Note that the "tailingBreak" is only ProseMirror's internal element
13
17
  // to place the cursor at the correct location.
14
- expect(ctx.prosemirror.innerHTML).toEqual('<p>Hello, world!<br><br class="ProseMirror-trailingBreak"></p>');
18
+ expect(normalizeHTML(ctx.prosemirror.innerHTML)).toEqual('<p>Hello, world!<br><br class="ProseMirror-trailingBreak"></p>');
15
19
  });
16
20
  });
17
21
  };
@@ -5,6 +5,10 @@ import contextHelpers from "src/decidim/editor/test/toolbar/shared/context";
5
5
  export default (ctx) => {
6
6
  const { getControl, setContent } = contextHelpers(ctx);
7
7
 
8
+ const normalizeHTML = (html) => {
9
+ return html.replace(/<p><br class="ProseMirror-trailingBreak"><\/p>$/g, "").trim();
10
+ };
11
+
8
12
  describe("indent:indent", () => {
9
13
  it("indents the existing content", async () => {
10
14
  await setContent("Hello, world!");
@@ -13,7 +17,7 @@ export default (ctx) => {
13
17
  ctrl.click();
14
18
  ctrl.click();
15
19
 
16
- expect(ctx.prosemirror.innerHTML).toEqual('<p class="editor-indent-2">Hello, world!</p>');
20
+ expect(normalizeHTML(ctx.prosemirror.innerHTML)).toEqual('<p class="editor-indent-2">Hello, world!</p>');
17
21
  });
18
22
 
19
23
  it("indents a list item correctly", async () => {
@@ -21,7 +25,7 @@ export default (ctx) => {
21
25
  selectContent(ctx.prosemirror, "ul li:nth-child(2) p");
22
26
  getControl("indent:indent").click();
23
27
 
24
- expect(ctx.prosemirror.innerHTML).toEqual(
28
+ expect(normalizeHTML(ctx.prosemirror.innerHTML)).toEqual(
25
29
  "<ul><li><p>First item</p><ul><li><p>Second item</p></li></ul></li></ul>"
26
30
  );
27
31
  });
@@ -35,7 +39,7 @@ export default (ctx) => {
35
39
  ctrl.click();
36
40
  ctrl.click();
37
41
 
38
- expect(ctx.prosemirror.innerHTML).toEqual("<p>Hello, world!</p>");
42
+ expect(normalizeHTML(ctx.prosemirror.innerHTML)).toEqual("<p>Hello, world!</p>");
39
43
  });
40
44
 
41
45
  it("outdents a list item correctly", async () => {
@@ -43,7 +47,7 @@ export default (ctx) => {
43
47
  selectContent(ctx.prosemirror, "ul li ul li p");
44
48
  getControl("indent:outdent").click();
45
49
 
46
- expect(ctx.prosemirror.innerHTML).toEqual(
50
+ expect(normalizeHTML(ctx.prosemirror.innerHTML)).toEqual(
47
51
  "<ul><li><p>First item</p></li><li><p>Second item</p></li></ul>"
48
52
  );
49
53
  });
@@ -53,7 +57,7 @@ export default (ctx) => {
53
57
  selectContent(ctx.prosemirror, "ul li:nth-child(2) p");
54
58
  getControl("indent:outdent").click();
55
59
 
56
- expect(ctx.prosemirror.innerHTML).toEqual(
60
+ expect(normalizeHTML(ctx.prosemirror.innerHTML)).toEqual(
57
61
  "<ul><li><p>First item</p></li><li><p>Second item</p></li></ul>"
58
62
  );
59
63
  });
@@ -5,6 +5,10 @@ import contextHelpers from "src/decidim/editor/test/toolbar/shared/context";
5
5
  export default (ctx) => {
6
6
  const { getControl, setContent } = contextHelpers(ctx);
7
7
 
8
+ const normalizeHTML = (html) => {
9
+ return html.replace(/<p><br class="ProseMirror-trailingBreak"><\/p>$/g, "").trim();
10
+ };
11
+
8
12
  describe("link", () => {
9
13
  it("creates a link for the selected text", async () => {
10
14
  await setContent("Hello, world!");
@@ -25,7 +29,7 @@ export default (ctx) => {
25
29
  // handled
26
30
  await sleep(0);
27
31
 
28
- expect(ctx.prosemirror.innerHTML).toEqual(
32
+ expect(normalizeHTML(ctx.prosemirror.innerHTML)).toEqual(
29
33
  '<p>Hello, <a href="https://decidim.org" target="_blank">world</a>!</p>'
30
34
  );
31
35
  });