decidim-core 0.31.2 → 0.31.4
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.
- checksums.yaml +4 -4
- data/app/cells/decidim/content_blocks/highlighted_elements_with_cell_for_list_cell.rb +5 -1
- data/app/cells/decidim/nav_links/show.erb +3 -3
- data/app/cells/decidim/participatory_space_private_user/show.erb +6 -6
- data/app/cells/decidim/participatory_space_private_user_cell.rb +0 -4
- data/app/cells/decidim/report_button/already_reported_modal.erb +1 -1
- data/app/cells/decidim/report_button/flag_modal.erb +1 -1
- data/app/cells/decidim/report_user_button/already_reported_modal.erb +1 -1
- data/app/cells/decidim/report_user_button/flag_modal.erb +1 -1
- data/app/cells/decidim/share_text_widget/modal.erb +1 -1
- data/app/cells/decidim/upload_modal/files.erb +5 -1
- data/app/cells/decidim/upload_modal_cell.rb +10 -1
- data/app/commands/decidim/multiple_attachments_methods.rb +20 -3
- data/app/helpers/decidim/mailer_helper.rb +36 -0
- data/app/helpers/decidim/menu_helper.rb +2 -1
- data/app/helpers/decidim/newsletters_helper.rb +4 -22
- data/app/jobs/decidim/find_and_update_descendants_job.rb +8 -2
- data/app/jobs/decidim/update_search_indexes_job.rb +2 -2
- data/app/mailers/decidim/application_mailer.rb +4 -0
- data/app/packs/src/decidim/a11y.js +29 -0
- data/app/packs/src/decidim/a11y.test.js +81 -0
- data/app/packs/src/decidim/confirm.js +8 -1
- data/app/packs/src/decidim/confirm.test.js +225 -0
- data/app/packs/src/decidim/controllers/accordion/accordion.test.js +118 -0
- data/app/packs/src/decidim/controllers/accordion/controller.js +24 -0
- data/app/packs/src/decidim/controllers/dropdown/controller.js +26 -0
- data/app/packs/src/decidim/controllers/dropdown/dropdown.test.js +187 -0
- data/app/packs/src/decidim/controllers/form_validator/form_validator.js +3 -2
- data/app/packs/src/decidim/controllers/form_validator/form_validator.test.js +5 -0
- data/app/packs/src/decidim/controllers/language_change/controller.js +1 -0
- data/app/packs/src/decidim/controllers/language_change/language_change.test.js +13 -0
- data/app/packs/src/decidim/datepicker/datepicker_functions.js +26 -0
- data/app/packs/src/decidim/datepicker/generate_datepicker.js +2 -1
- data/app/packs/src/decidim/datepicker/generate_timepicker.js +3 -2
- data/app/packs/src/decidim/datepicker/test/datepicker_functions_adjust_picker_position.test.js +234 -0
- data/app/packs/src/decidim/editor/extensions/image/index.js +49 -11
- data/app/packs/src/decidim/editor/extensions/image/node_view.js +9 -1
- data/app/packs/src/decidim/editor/extensions/link/bubble_menu.js +34 -6
- data/app/packs/src/decidim/editor/extensions/link/index.js +45 -12
- data/app/packs/src/decidim/editor/test/extensions/image_links.test.js +161 -0
- data/app/packs/src/decidim/refactor/moved/focus_guard.js +4 -4
- data/app/packs/stylesheets/decidim/_cards.scss +12 -4
- data/app/packs/stylesheets/decidim/_flash.scss +1 -1
- data/app/packs/stylesheets/decidim/_rich_text.scss +17 -0
- data/app/packs/stylesheets/decidim/editor.scss +10 -0
- data/app/presenters/decidim/menu_item_presenter.rb +7 -1
- data/app/views/decidim/devise/invitations/edit.html.erb +3 -3
- data/app/views/decidim/devise/registrations/new.html.erb +1 -0
- data/app/views/decidim/devise/shared/_tos_fields.html.erb +3 -3
- data/app/views/decidim/notification_mailer/event_received.html.erb +3 -3
- data/app/views/decidim/pages/_tabbed.html.erb +3 -3
- data/app/views/decidim/shared/_filters.html.erb +5 -5
- data/app/views/decidim/shared/filters/_check_boxes_tree.html.erb +1 -1
- data/app/views/decidim/shared/filters/_collection.html.erb +1 -1
- data/config/initializers/devise.rb +6 -0
- data/config/locales/ar.yml +3 -3
- data/config/locales/bg.yml +0 -4
- data/config/locales/ca-IT.yml +7 -6
- data/config/locales/ca.yml +7 -6
- data/config/locales/cs.yml +5 -8
- data/config/locales/de.yml +31 -8
- data/config/locales/el.yml +0 -2
- data/config/locales/en.yml +5 -4
- data/config/locales/es-MX.yml +10 -9
- data/config/locales/es-PY.yml +10 -9
- data/config/locales/es.yml +12 -11
- data/config/locales/eu.yml +7 -5
- data/config/locales/fi-plain.yml +10 -4
- data/config/locales/fi.yml +11 -5
- data/config/locales/fr-CA.yml +7 -5
- data/config/locales/fr.yml +8 -7
- data/config/locales/gl.yml +0 -2
- data/config/locales/hu.yml +4 -8
- data/config/locales/id-ID.yml +0 -2
- data/config/locales/it.yml +1 -3
- data/config/locales/ja.yml +7 -8
- data/config/locales/lb.yml +0 -2
- data/config/locales/lt.yml +1 -3
- data/config/locales/lv.yml +0 -2
- data/config/locales/nl.yml +0 -2
- data/config/locales/no.yml +0 -2
- data/config/locales/pl.yml +0 -4
- data/config/locales/pt-BR.yml +4 -5
- data/config/locales/pt.yml +0 -2
- data/config/locales/ro-RO.yml +1 -5
- data/config/locales/ru.yml +0 -2
- data/config/locales/sk.yml +0 -4
- data/config/locales/sv.yml +8 -7
- data/config/locales/tr-TR.yml +17 -5
- data/config/locales/zh-CN.yml +0 -2
- data/config/locales/zh-TW.yml +1 -3
- data/lib/decidim/assets/tailwind/tailwind.config.js.erb +1 -1
- data/lib/decidim/content_parsers/blob_parser.rb +3 -3
- data/lib/decidim/content_renderers/blob_renderer.rb +2 -2
- data/lib/decidim/core/test/shared_examples/participatory_space_members_shared_examples.rb +121 -0
- data/lib/decidim/core/version.rb +1 -1
- data/lib/decidim/form_builder.rb +58 -36
- data/lib/decidim/maintenance/taxonomy_importer.rb +1 -1
- data/lib/decidim/participatory_space_user.rb +1 -1
- data/lib/decidim/searchable.rb +4 -4
- metadata +14 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 513dc9560cc17e12439bcef89efbb034edc6300b791810f2037bbb64191805a6
|
|
4
|
+
data.tar.gz: 99d7f076a772765592d34f5164659c5f4d0826edb4d146d5917fff28e02dc922
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d8928571dc994667af951c63981ce2472ce1be5db0c477beb27cadfc2ac43b6cfa87f1a8ab6854483cd00ce46fb1856adacf593ad4e159e7e1a46dc3ae099b87
|
|
7
|
+
data.tar.gz: e03706f2aadaac2165c6a513a5b1284011bda181ab5a29683fe5983e616c880fd9c7a6ff15cbf1619fb6d6f658eb30df40e4d151cb81f12b0bdbd16c7848b224
|
|
@@ -19,10 +19,14 @@ module Decidim
|
|
|
19
19
|
@list_cell ||= cell(
|
|
20
20
|
list_cell_path,
|
|
21
21
|
published_components.one? ? published_components.first : published_components,
|
|
22
|
-
**model.settings.to_h, see_all_path:
|
|
22
|
+
**model.settings.to_h, **extra_list_cell_options, see_all_path:
|
|
23
23
|
)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
def extra_list_cell_options
|
|
27
|
+
{}
|
|
28
|
+
end
|
|
29
|
+
|
|
26
30
|
def see_all_path; end
|
|
27
31
|
end
|
|
28
32
|
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<div class="participatory-space__nav-container">
|
|
2
|
-
<button id="dropdown-trigger-participatory-space" data-controller="dropdown" data-target="dropdown-menu-participatory-space" data-auto-close="true" data-scroll-to-menu="true">
|
|
2
|
+
<button id="dropdown-trigger-participatory-space" data-controller="dropdown" data-target="dropdown-menu-participatory-space" data-auto-close="true" data-scroll-to-menu="true" data-add-aria-roles="false" data-open-md="true">
|
|
3
3
|
<span><%= t("decidim.searches.filters.jump_to") %></span>
|
|
4
4
|
<%= icon "arrow-down-s-line" %>
|
|
5
5
|
<%= icon "arrow-up-s-line" %>
|
|
6
6
|
</button>
|
|
7
|
-
<ul id="dropdown-menu-participatory-space" class="participatory-space__nav"
|
|
7
|
+
<ul id="dropdown-menu-participatory-space" class="participatory-space__nav">
|
|
8
8
|
<% model.each do |item| %>
|
|
9
|
-
<li
|
|
9
|
+
<li>
|
|
10
10
|
<%= link_to item[:url], class: "participatory-space__nav-item" do %>
|
|
11
11
|
<%= decidim_escape_translated(item[:name]) %>
|
|
12
12
|
<%= icon "arrow-right-line" %>
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
<%= link_to profile_url, class: "profile__user" do %>
|
|
2
2
|
<div class="profile__user-avatar-container">
|
|
3
|
-
<div class="
|
|
4
|
-
<%= image_tag(
|
|
3
|
+
<div class="profile__user-avatar">
|
|
4
|
+
<%= image_tag(model.avatar_url(:big), alt: "member-avatar") %>
|
|
5
5
|
</div>
|
|
6
6
|
</div>
|
|
7
7
|
<div>
|
|
8
|
-
<
|
|
8
|
+
<span class="profile__user-name">
|
|
9
9
|
<%= name %>
|
|
10
|
-
</
|
|
10
|
+
</span>
|
|
11
11
|
<% if nickname.present? %>
|
|
12
12
|
<span class="profile__user-nick block">
|
|
13
13
|
<%= nickname %>
|
|
@@ -20,4 +20,4 @@
|
|
|
20
20
|
</span>
|
|
21
21
|
</div>
|
|
22
22
|
</div>
|
|
23
|
-
|
|
23
|
+
<% end %>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<%= decidim_modal id: modal_id, class: "flag-modal" do %>
|
|
2
2
|
<div data-dialog-container>
|
|
3
3
|
<%= icon "flag-line" %>
|
|
4
|
-
<h2 tabindex="-1" data-dialog-title><%= t("decidim.shared.flag_modal.title") %></h2>
|
|
4
|
+
<h2 id="dialog-title-<%= modal_id %>" tabindex="-1" data-dialog-title><%= t("decidim.shared.flag_modal.title") %></h2>
|
|
5
5
|
<div>
|
|
6
6
|
<div class="form__wrapper flag-modal__form">
|
|
7
7
|
<p class="flag-modal__form-description"><%= t("decidim.shared.flag_modal.already_reported") %></p>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<%= decidim_form_for report_form, builder:, url: report_path, method: :post, html: { id: nil, data: { controller: "report-form" } } do |f| %>
|
|
3
3
|
<div data-dialog-container>
|
|
4
4
|
<%= icon "flag-line" %>
|
|
5
|
-
<h2 tabindex="-1" data-dialog-title><%= t("decidim.shared.flag_modal.title") %></h2>
|
|
5
|
+
<h2 id="dialog-title-<%= modal_id %>" tabindex="-1" data-dialog-title><%= t("decidim.shared.flag_modal.title") %></h2>
|
|
6
6
|
<div>
|
|
7
7
|
<div class="form__wrapper flag-modal__form">
|
|
8
8
|
<p id="dialog-desc-<%= modal_id %>" class="flag-modal__form-description"><%= t("decidim.shared.flag_modal.description") %></p>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<%= decidim_modal id: modal_id, class: "flag-user-modal" do %>
|
|
2
2
|
<div data-dialog-container>
|
|
3
3
|
<%= icon "flag-line" %>
|
|
4
|
-
<h2 tabindex="-1" data-dialog-title><%= t("decidim.shared.flag_user_modal.title") %></h2>
|
|
4
|
+
<h2 id="dialog-title-<%= modal_id %>" tabindex="-1" data-dialog-title><%= t("decidim.shared.flag_user_modal.title") %></h2>
|
|
5
5
|
<div>
|
|
6
6
|
<div class="form__wrapper flag-modal__form">
|
|
7
7
|
<p class="flag-modal__form-description"><%= t("decidim.shared.flag_user_modal.already_reported") %></p>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<%= decidim_form_for report_form, builder:, url: report_path, method: :post, html: { id: nil, data: { controller: "report-form" } } do |f| %>
|
|
3
3
|
<div data-dialog-container>
|
|
4
4
|
<%= icon "flag-line" %>
|
|
5
|
-
<h2 tabindex="-1" data-dialog-title><%= t("decidim.shared.flag_user_modal.title") %></h2>
|
|
5
|
+
<h2 id="dialog-title-<%= modal_id %>" tabindex="-1" data-dialog-title><%= t("decidim.shared.flag_user_modal.title") %></h2>
|
|
6
6
|
<div>
|
|
7
7
|
<div class="form__wrapper flag-modal__form">
|
|
8
8
|
<p class="flag-modal__form-description"><%= t("decidim.shared.flag_user_modal.description") %></p>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<%= decidim_modal id: "socialShare", class: "share-modal" do %>
|
|
2
2
|
<div data-dialog-container>
|
|
3
|
-
<h2 tabindex="-1" data-dialog-title><%= t("share", scope: "decidim.shared.share_modal") %></h2>
|
|
3
|
+
<h2 id="dialog-title-socialShare" tabindex="-1" data-dialog-title><%= t("share", scope: "decidim.shared.share_modal") %></h2>
|
|
4
4
|
|
|
5
5
|
<div>
|
|
6
6
|
|
|
@@ -42,7 +42,11 @@
|
|
|
42
42
|
<% end %>
|
|
43
43
|
<% end %>
|
|
44
44
|
<% if attachment_blob.present? %>
|
|
45
|
-
|
|
45
|
+
<% if is_persisted_attachment %>
|
|
46
|
+
<%= form.hidden_field attribute, value: attachment.id, id: "hidden_#{attribute}_#{attachment.id}" %>
|
|
47
|
+
<% else %>
|
|
48
|
+
<%= form.hidden_field attribute, value: attachment_blob.signed_id, id: "hidden_#{attribute}_#{attachment_blob.id}" %>
|
|
49
|
+
<% end %>
|
|
46
50
|
<% end %>
|
|
47
51
|
</div>
|
|
48
52
|
<% end %>
|
|
@@ -128,7 +128,16 @@ module Decidim
|
|
|
128
128
|
@attachments = begin
|
|
129
129
|
attachments = options[:attachments] || form.object.send(attribute)
|
|
130
130
|
attachments = Array(attachments).compact_blank
|
|
131
|
-
attachments.map
|
|
131
|
+
attachments.map do |attachment|
|
|
132
|
+
case attachment
|
|
133
|
+
when String
|
|
134
|
+
ActiveStorage::Blob.find_signed(attachment)
|
|
135
|
+
when Integer
|
|
136
|
+
Decidim::Attachment.find_by(id: attachment)
|
|
137
|
+
else
|
|
138
|
+
attachment
|
|
139
|
+
end
|
|
140
|
+
end.compact
|
|
132
141
|
end
|
|
133
142
|
end
|
|
134
143
|
|
|
@@ -41,8 +41,9 @@ module Decidim
|
|
|
41
41
|
|
|
42
42
|
def create_attachments(first_weight: 0)
|
|
43
43
|
weight = first_weight
|
|
44
|
-
# Add the weights first to the old
|
|
45
|
-
|
|
44
|
+
# Add the weights first to the old documents
|
|
45
|
+
document_ids = keep_ids
|
|
46
|
+
Decidim::Attachment.where(id: document_ids).each do |document|
|
|
46
47
|
document.update!(weight:)
|
|
47
48
|
weight += 1
|
|
48
49
|
end
|
|
@@ -59,7 +60,7 @@ module Decidim
|
|
|
59
60
|
documents = include_all_attachments ? documents_attached_to.attachments.with_attached_file : documents_attached_to.documents
|
|
60
61
|
|
|
61
62
|
documents.each do |document|
|
|
62
|
-
document.destroy!
|
|
63
|
+
document.destroy! unless keep_ids.include?(document.id)
|
|
63
64
|
end
|
|
64
65
|
|
|
65
66
|
documents_attached_to.reload
|
|
@@ -98,5 +99,21 @@ module Decidim
|
|
|
98
99
|
def blob(signed_id)
|
|
99
100
|
ActiveStorage::Blob.find_signed(signed_id)
|
|
100
101
|
end
|
|
102
|
+
|
|
103
|
+
def keep_ids
|
|
104
|
+
documents_array = Array(@form.documents)
|
|
105
|
+
documents_array.map do |doc|
|
|
106
|
+
case doc
|
|
107
|
+
when Decidim::Attachment
|
|
108
|
+
doc.id
|
|
109
|
+
when Integer
|
|
110
|
+
doc
|
|
111
|
+
when String
|
|
112
|
+
doc.match?(/\A\d+\z/) ? doc.to_i : nil
|
|
113
|
+
when Hash
|
|
114
|
+
(doc[:id] || doc["id"]).to_i
|
|
115
|
+
end
|
|
116
|
+
end.compact
|
|
117
|
+
end
|
|
101
118
|
end
|
|
102
119
|
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
# Helper that provides methods to render order selector and links
|
|
5
|
+
module MailerHelper
|
|
6
|
+
# Transforms relative image URLs in HTML content to absolute URLs using the provided host.
|
|
7
|
+
# This is used in emails (newsletters and notifications) to ensure images display correctly
|
|
8
|
+
# in email clients.
|
|
9
|
+
#
|
|
10
|
+
# @param content [String] - HTML content with img tags
|
|
11
|
+
# @param host [String] - the Decidim::Organization host to use for the root URL
|
|
12
|
+
#
|
|
13
|
+
# @return [String] - the content with transformed image URLs
|
|
14
|
+
def decidim_transform_image_urls(content, host)
|
|
15
|
+
return content if host.blank? || content.blank?
|
|
16
|
+
|
|
17
|
+
root_url = if Decidim.storage_cdn_host.present?
|
|
18
|
+
Decidim.storage_cdn_host.chomp("/")
|
|
19
|
+
else
|
|
20
|
+
Decidim::EngineRouter.new("decidim", {}).root_url(host:).chomp("/")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
content.gsub(/src\s*=\s*(['"])([^'"]*)\1/) do
|
|
24
|
+
quote = Regexp.last_match(1)
|
|
25
|
+
src_value = Regexp.last_match(2)
|
|
26
|
+
|
|
27
|
+
if src_value.blank? || src_value.start_with?("http://", "https://", "data:", "//", "cid:")
|
|
28
|
+
%(src=#{quote}#{src_value}#{quote})
|
|
29
|
+
else
|
|
30
|
+
normalized_src = src_value.start_with?("/") ? src_value : "/#{src_value}"
|
|
31
|
+
%(src=#{quote}#{root_url}#{normalized_src}#{quote})
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -57,7 +57,8 @@ module Decidim
|
|
|
57
57
|
self,
|
|
58
58
|
element_class: "font-semibold underline",
|
|
59
59
|
active_class: "is-active",
|
|
60
|
-
|
|
60
|
+
role: false,
|
|
61
|
+
container_options: { class: "space-y-4 break-inside-avoid" },
|
|
61
62
|
label: t("layouts.decidim.footer.decidim_title")
|
|
62
63
|
)
|
|
63
64
|
end
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
module Decidim
|
|
4
4
|
# Helper that provides methods to render links with utm codes, and replaced name
|
|
5
5
|
module NewslettersHelper
|
|
6
|
+
include Decidim::SanitizeHelper
|
|
7
|
+
include Decidim::MailerHelper
|
|
8
|
+
|
|
6
9
|
# If the newsletter body there are some links and the Decidim.track_newsletter_links = true
|
|
7
10
|
# it will be replaced with the utm_codes method described below.
|
|
8
11
|
# for example transform "https://es.lipsum.com/" to "https://es.lipsum.com/?utm_source=localhost&utm_campaign=newsletter_11"
|
|
@@ -19,7 +22,7 @@ module Decidim
|
|
|
19
22
|
|
|
20
23
|
content = interpret_name(content, user)
|
|
21
24
|
content = track_newsletter_links(content, id, host)
|
|
22
|
-
|
|
25
|
+
decidim_transform_image_urls(content, host)
|
|
23
26
|
end
|
|
24
27
|
|
|
25
28
|
# this method is used to generate the root link on mail with the utm_codes
|
|
@@ -67,27 +70,6 @@ module Decidim
|
|
|
67
70
|
content.gsub("%{name}", user.name)
|
|
68
71
|
end
|
|
69
72
|
|
|
70
|
-
# Find each img HTML tag with relative path in src attribute
|
|
71
|
-
# For each URL, prepends the decidim.root_url
|
|
72
|
-
# If host is not defined it returns full content
|
|
73
|
-
#
|
|
74
|
-
# @param content [String] - the string to convert
|
|
75
|
-
# @param host [String] - the Decidim::Organization host to replace
|
|
76
|
-
#
|
|
77
|
-
# @return [String] - the content converted
|
|
78
|
-
#
|
|
79
|
-
def transform_image_urls(content, host)
|
|
80
|
-
return content if host.blank?
|
|
81
|
-
|
|
82
|
-
content.scan(/src\s*=\s*"([^"]*)"/).each do |src|
|
|
83
|
-
root_url = decidim.root_url(host:)[0..-2]
|
|
84
|
-
src_replaced = "#{root_url}#{src.first}"
|
|
85
|
-
content = content.gsub(/src\s*=\s*"([^"]*#{src.first})"/, %(src="#{src_replaced}"))
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
content
|
|
89
|
-
end
|
|
90
|
-
|
|
91
73
|
# Add tracking query params to each links
|
|
92
74
|
#
|
|
93
75
|
# @param content [String] - the string to convert
|
|
@@ -4,8 +4,14 @@ module Decidim
|
|
|
4
4
|
# Update search indexes for each descendants of a given element
|
|
5
5
|
class FindAndUpdateDescendantsJob < ApplicationJob
|
|
6
6
|
queue_as :default
|
|
7
|
+
MAX_DEPTH = 5
|
|
8
|
+
|
|
9
|
+
def perform(element, current_depth = 0)
|
|
10
|
+
if current_depth >= MAX_DEPTH
|
|
11
|
+
Rails.logger.warn "Max depth of #{MAX_DEPTH} reached for element #{element.class.name} with id #{element.id}. Stopping recursion."
|
|
12
|
+
return
|
|
13
|
+
end
|
|
7
14
|
|
|
8
|
-
def perform(element)
|
|
9
15
|
descendants_collector = components_for(element)
|
|
10
16
|
descendants_collector << element.comments.to_a if element.respond_to?(:comments)
|
|
11
17
|
|
|
@@ -14,7 +20,7 @@ module Decidim
|
|
|
14
20
|
descendants_collector.each do |descendants|
|
|
15
21
|
next if descendants.blank?
|
|
16
22
|
|
|
17
|
-
Decidim::UpdateSearchIndexesJob.perform_later(descendants)
|
|
23
|
+
Decidim::UpdateSearchIndexesJob.perform_later(descendants, current_depth + 1)
|
|
18
24
|
end
|
|
19
25
|
end
|
|
20
26
|
|
|
@@ -4,8 +4,8 @@ module Decidim
|
|
|
4
4
|
class UpdateSearchIndexesJob < ApplicationJob
|
|
5
5
|
queue_as :default
|
|
6
6
|
|
|
7
|
-
def perform(elements)
|
|
8
|
-
elements.each { |element| element.try(:try_update_index_for_search_resource) }
|
|
7
|
+
def perform(elements, current_depth = 0)
|
|
8
|
+
elements.each { |element| element.try(:try_update_index_for_search_resource, current_depth) }
|
|
9
9
|
end
|
|
10
10
|
end
|
|
11
11
|
end
|
|
@@ -7,9 +7,13 @@ module Decidim
|
|
|
7
7
|
include LocalisedMailer
|
|
8
8
|
include MultitenantAssetHost
|
|
9
9
|
include Decidim::SanitizeHelper
|
|
10
|
+
include Decidim::MailerHelper
|
|
10
11
|
include Decidim::OrganizationHelper
|
|
11
12
|
helper_method :organization_name, :decidim_escape_translated, :decidim_sanitize_translated, :translated_attribute, :decidim_sanitize, :decidim_sanitize_newsletter
|
|
12
13
|
|
|
14
|
+
helper Decidim::SanitizeHelper
|
|
15
|
+
helper Decidim::MailerHelper
|
|
16
|
+
|
|
13
17
|
after_action :set_smtp
|
|
14
18
|
after_action :set_from
|
|
15
19
|
|
|
@@ -8,6 +8,13 @@ import Dialogs from "a11y-dialog-component";
|
|
|
8
8
|
* @return {void}
|
|
9
9
|
*/
|
|
10
10
|
const createDialog = (component) => {
|
|
11
|
+
const getFocusableElements = (container) => {
|
|
12
|
+
const selectors = "a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex='-1'])";
|
|
13
|
+
return Array.from(container.querySelectorAll(selectors)).filter(
|
|
14
|
+
(el) => el.offsetParent !== null
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
11
18
|
const {
|
|
12
19
|
dataset: { dialog, ...attrs }
|
|
13
20
|
} = component;
|
|
@@ -29,11 +36,33 @@ const createDialog = (component) => {
|
|
|
29
36
|
backdropSelector: `[data-dialog="${dialog}"]`,
|
|
30
37
|
enableAutoFocus: false,
|
|
31
38
|
onOpen: (params, trigger) => {
|
|
39
|
+
const keyHandler = (event) => {
|
|
40
|
+
if (event.key !== "Tab") {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const focusable = getFocusableElements(params);
|
|
44
|
+
if (focusable.length === 0) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (event.shiftKey && document.activeElement === focusable[0]) {
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
focusable[focusable.length - 1].focus({ preventScroll: true });
|
|
50
|
+
} else if (!event.shiftKey && document.activeElement === focusable[focusable.length - 1]) {
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
focusable[0].focus({ preventScroll: true });
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
params._focusTrapHandler = keyHandler;
|
|
56
|
+
params.addEventListener("keydown", keyHandler);
|
|
32
57
|
setFocusOnTitle(params);
|
|
33
58
|
window.focusGuard.trap(params, trigger);
|
|
34
59
|
params.dispatchEvent(new CustomEvent("open.dialog"));
|
|
35
60
|
},
|
|
36
61
|
onClose: (params) => {
|
|
62
|
+
if (params._focusTrapHandler) {
|
|
63
|
+
params.removeEventListener("keydown", params._focusTrapHandler);
|
|
64
|
+
Reflect.deleteProperty(params, "_focusTrapHandler");
|
|
65
|
+
}
|
|
37
66
|
window.focusGuard.disable();
|
|
38
67
|
params.dispatchEvent(new CustomEvent("close.dialog"));
|
|
39
68
|
},
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/* global jest */
|
|
2
|
+
|
|
3
|
+
import { createDialog } from "src/decidim/a11y"
|
|
4
|
+
|
|
5
|
+
describe("a11y dialog focus trap", () => {
|
|
6
|
+
const dialogHtml = `
|
|
7
|
+
<button id="open-btn" data-dialog-open="testDialog">Open</button>
|
|
8
|
+
<div id="test-dialog" data-dialog="testDialog">
|
|
9
|
+
<div data-dialog-container>
|
|
10
|
+
<h2 id="dialog-title-testDialog" tabindex="-1" data-dialog-title>Test Dialog</h2>
|
|
11
|
+
<a href="#">Link 1</a>
|
|
12
|
+
<button>Button 1</button>
|
|
13
|
+
<input type="text" />
|
|
14
|
+
<button>Button 2</button>
|
|
15
|
+
<a href="#">Link 2</a>
|
|
16
|
+
</div>
|
|
17
|
+
<div data-dialog-actions>
|
|
18
|
+
<button data-dialog-close="testDialog">Close</button>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
document.body.innerHTML = dialogHtml;
|
|
25
|
+
window.Decidim = {
|
|
26
|
+
currentDialogs: {}
|
|
27
|
+
};
|
|
28
|
+
window.focusGuard = {
|
|
29
|
+
trap: jest.fn(),
|
|
30
|
+
disable: jest.fn()
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("keydown handler", () => {
|
|
35
|
+
let dialogEl = null;
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
const component = document.querySelector("[data-dialog]");
|
|
39
|
+
createDialog(component);
|
|
40
|
+
dialogEl = document.querySelector("[data-dialog='testDialog']");
|
|
41
|
+
// Get the dialog from Decidim.currentDialogs and open it
|
|
42
|
+
const dialog = window.Decidim.currentDialogs.testDialog;
|
|
43
|
+
dialog.open();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("adds keydown handler on open", () => {
|
|
47
|
+
expect(dialogEl._focusTrapHandler).toBeDefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("handles Tab key", () => {
|
|
51
|
+
const selectors = "a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex='-1'])";
|
|
52
|
+
const tabbableElements = Array.from(dialogEl.querySelectorAll(selectors)).filter(
|
|
53
|
+
(el) => el.offsetParent || el.offsetParent === null
|
|
54
|
+
);
|
|
55
|
+
tabbableElements[tabbableElements.length - 1].focus();
|
|
56
|
+
|
|
57
|
+
const event = new KeyboardEvent("keydown", { key: "Tab", bubbles: true });
|
|
58
|
+
const preventDefault = jest.fn();
|
|
59
|
+
event.preventDefault = preventDefault;
|
|
60
|
+
|
|
61
|
+
dialogEl.dispatchEvent(event);
|
|
62
|
+
|
|
63
|
+
expect(preventDefault).toHaveBeenCalled();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("onClose cleanup", () => {
|
|
68
|
+
it("removes the keydown handler on close", () => {
|
|
69
|
+
const component = document.querySelector("[data-dialog]");
|
|
70
|
+
createDialog(component);
|
|
71
|
+
const dialogEl = document.querySelector("[data-dialog='testDialog']");
|
|
72
|
+
|
|
73
|
+
const dialog = window.Decidim.currentDialogs.testDialog;
|
|
74
|
+
dialog.open();
|
|
75
|
+
expect(dialogEl._focusTrapHandler).toBeDefined();
|
|
76
|
+
|
|
77
|
+
dialog.close();
|
|
78
|
+
expect(dialogEl._focusTrapHandler).toBeUndefined();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -169,7 +169,9 @@ export const initializeConfirm = () => {
|
|
|
169
169
|
return handleDocumentEvent(ev, [
|
|
170
170
|
Rails.linkClickSelector,
|
|
171
171
|
Rails.buttonClickSelector,
|
|
172
|
-
Rails.formInputClickSelector
|
|
172
|
+
Rails.formInputClickSelector,
|
|
173
|
+
'button[data-confirm][type="button"]',
|
|
174
|
+
"form button[data-confirm]"
|
|
173
175
|
]);
|
|
174
176
|
});
|
|
175
177
|
document.addEventListener("change", (ev) => {
|
|
@@ -187,6 +189,11 @@ export const initializeConfirm = () => {
|
|
|
187
189
|
$(Rails.formInputClickSelector).on("click.confirm", (ev) => {
|
|
188
190
|
handleConfirm(ev, getMatchingEventTarget(ev, Rails.formInputClickSelector));
|
|
189
191
|
});
|
|
192
|
+
|
|
193
|
+
// Handle button[type="button"] with data-confirm inside forms
|
|
194
|
+
$('button[data-confirm][type="button"]').on("click.confirm", (ev) => {
|
|
195
|
+
handleConfirm(ev, ev.currentTarget);
|
|
196
|
+
});
|
|
190
197
|
});
|
|
191
198
|
};
|
|
192
199
|
|