decidim-core 0.28.1 → 0.28.2

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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/announcement/show.erb +2 -2
  3. data/app/cells/decidim/author/show.erb +5 -5
  4. data/app/cells/decidim/card/show.erb +1 -1
  5. data/app/cells/decidim/card_metadata/show.erb +2 -2
  6. data/app/cells/decidim/data_consent/category.erb +1 -1
  7. data/app/cells/decidim/nav_links/show.erb +2 -2
  8. data/app/cells/decidim/notification/moderated.erb +12 -0
  9. data/app/cells/decidim/notification_cell.rb +5 -1
  10. data/app/cells/decidim/profile/details.erb +1 -1
  11. data/app/cells/decidim/progress_bar/show.erb +1 -1
  12. data/app/cells/decidim/progress_bar_cell.rb +2 -0
  13. data/app/cells/decidim/report_button/flag_modal.erb +5 -1
  14. data/app/cells/decidim/resource_types_filter/show.erb +3 -3
  15. data/app/cells/decidim/statistic/show.erb +2 -2
  16. data/app/cells/decidim/upload_modal/modal.erb +3 -4
  17. data/app/controllers/concerns/decidim/force_authentication.rb +1 -1
  18. data/app/controllers/concerns/decidim/use_organization_time_zone.rb +1 -1
  19. data/app/controllers/decidim/gamification/badges_controller.rb +2 -0
  20. data/app/controllers/decidim/links_controller.rb +15 -2
  21. data/app/helpers/concerns/decidim/flash_helper_extensions.rb +2 -2
  22. data/app/helpers/decidim/check_boxes_tree_helper.rb +1 -2
  23. data/app/mailers/decidim/application_mailer.rb +40 -6
  24. data/app/packs/src/decidim/a11y.js +14 -0
  25. data/app/packs/src/decidim/abide_form_validator_fixer.js +44 -0
  26. data/app/packs/src/decidim/direct_uploads/upload_modal.js +2 -6
  27. data/app/packs/src/decidim/index.js +29 -1
  28. data/app/packs/stylesheets/decidim/_accordion.scss +2 -2
  29. data/app/packs/stylesheets/decidim/_cards.scss +2 -2
  30. data/app/packs/stylesheets/decidim/_layout.scss +3 -3
  31. data/app/packs/stylesheets/decidim/_modal_update.scss +1 -3
  32. data/app/presenters/decidim/admin_log/organization_presenter.rb +1 -1
  33. data/app/presenters/decidim/log/resource_presenter.rb +7 -1
  34. data/app/services/decidim/log/diff_changeset_calculator.rb +1 -1
  35. data/app/views/decidim/account/show.html.erb +2 -2
  36. data/app/views/decidim/application/_document.html.erb +2 -2
  37. data/app/views/decidim/endorsements/update_buttons_and_counters.js.erb +2 -1
  38. data/app/views/decidim/gamification/badges/index.html.erb +34 -33
  39. data/app/views/decidim/links/_modal.html.erb +1 -1
  40. data/app/views/decidim/links/new.html.erb +3 -1
  41. data/app/views/decidim/manifests/show.json.erb +1 -1
  42. data/app/views/decidim/messaging/conversations/create.js.erb +1 -1
  43. data/app/views/decidim/notifications_settings/show.html.erb +6 -6
  44. data/app/views/decidim/pages/_tabbed.html.erb +2 -2
  45. data/app/views/decidim/searches/_filters.html.erb +2 -2
  46. data/app/views/decidim/shared/_filters.html.erb +2 -2
  47. data/app/views/decidim/shared/_orders.html.erb +2 -2
  48. data/app/views/decidim/shared/filters/_collection.html.erb +5 -3
  49. data/app/views/decidim/shared/filters/_dropdown_label.html.erb +21 -19
  50. data/app/views/layouts/decidim/_wrapper.html.erb +1 -1
  51. data/app/views/layouts/decidim/footer/_main_links.html.erb +3 -1
  52. data/app/views/layouts/decidim/header/_main_links_desktop.html.erb +4 -2
  53. data/app/views/layouts/decidim/header/_menu_breadcrumb_items.html.erb +2 -0
  54. data/app/views/layouts/decidim/shared/_layout_user_profile.html.erb +2 -2
  55. data/config/locales/ar.yml +1 -5
  56. data/config/locales/bg.yml +878 -1
  57. data/config/locales/ca.yml +2 -0
  58. data/config/locales/cs.yml +1 -1
  59. data/config/locales/de.yml +3 -1
  60. data/config/locales/el.yml +8 -1
  61. data/config/locales/en.yml +3 -1
  62. data/config/locales/es-MX.yml +8 -6
  63. data/config/locales/es-PY.yml +8 -6
  64. data/config/locales/es.yml +32 -30
  65. data/config/locales/eu.yml +4 -2
  66. data/config/locales/fi-plain.yml +3 -1
  67. data/config/locales/fi.yml +4 -2
  68. data/config/locales/fr-CA.yml +2 -0
  69. data/config/locales/fr.yml +2 -0
  70. data/config/locales/ga-IE.yml +8 -0
  71. data/config/locales/gl.yml +1 -0
  72. data/config/locales/hu.yml +1 -2
  73. data/config/locales/it.yml +7 -1
  74. data/config/locales/ja.yml +3 -1
  75. data/config/locales/kaa.yml +5 -0
  76. data/config/locales/lb.yml +7 -1
  77. data/config/locales/lt.yml +8 -2
  78. data/config/locales/lv.yml +8 -1
  79. data/config/locales/nl.yml +7 -1
  80. data/config/locales/no.yml +7 -1
  81. data/config/locales/pl.yml +35 -0
  82. data/config/locales/pt-BR.yml +0 -1
  83. data/config/locales/pt.yml +7 -1
  84. data/config/locales/ro-RO.yml +8 -0
  85. data/config/locales/ru.yml +8 -0
  86. data/config/locales/sk.yml +8 -1
  87. data/config/locales/sl.yml +8 -0
  88. data/config/locales/sv.yml +8 -1
  89. data/config/locales/tr-TR.yml +21 -4
  90. data/config/locales/uk.yml +10 -0
  91. data/config/locales/zh-CN.yml +0 -1
  92. data/config/locales/zh-TW.yml +8 -1
  93. data/lib/decidim/core/seeds.rb +1 -1
  94. data/lib/decidim/core/test/shared_examples/comments_examples.rb +76 -6
  95. data/lib/decidim/core/test/shared_examples/logo_email.rb +2 -2
  96. data/lib/decidim/core/version.rb +1 -1
  97. data/lib/decidim/core.rb +6 -1
  98. data/lib/decidim/events/base_event.rb +4 -0
  99. data/lib/decidim/organization_settings.rb +10 -2
  100. metadata +12 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 800e096eb86537caa7de18236fe0becf73b1c30ee38e4631888025b52e831f3a
4
- data.tar.gz: 9f05dfffcd55fcc9d4e76163a4b98c5b7e851bbdbcfe317f9a9566cb73a142d9
3
+ metadata.gz: 524ec4d380f620c07119a6ccbee1b28408f6072799aa32cd74a1836f1ede5070
4
+ data.tar.gz: 4e1b60ce9a2cb9e340b0db3a22f66b326ade8f05cb8551352b39fe4198a06bbb
5
5
  SHA512:
6
- metadata.gz: 97568f03aab3b39a4b75e98421a5b9e43f30f8f1c71b548478f0ee2f73c6303981643948289f9483d9cfbe71af47b872bf3e3c49d39009270e9e3ef72e55bf70
7
- data.tar.gz: 8fa7ecb28d0d2e8097124330aced622ac52fc96d6cc8d6039e629c897d29af8f79366e8992107a9d8b89c88280c723b667b345ccbc643d2326303f376abd243a
6
+ metadata.gz: c8ff8135707ab3ae9aa1d483cd41d104385c079e8dcbd575c3c618ae05c524c345b340bd4b82c47eccf533dc98e187985ba76ddb2293332977449ed8af95b4b3
7
+ data.tar.gz: 9188473aa2d7c440f2aa773e9e08824b2c847f8a29a6d1d1ff75900e9a2fd325f860d80d8faad5d2498dc594f20b92ad9e8f7e6b98551ba9f68ffe24a879dcc1
@@ -5,7 +5,7 @@
5
5
  </div>
6
6
  <% end %>
7
7
 
8
- <span class="flash__message">
8
+ <div class="flash__message">
9
9
  <%= text %>
10
- </span>
10
+ </div>
11
11
  <% end %>
@@ -1,16 +1,16 @@
1
1
  <% data = has_tooltip? ? { tooltip: render(:profile_minicard).html_safe } : nil %>
2
- <div class="author" data-author>
3
- <%= content_tag :div, class: "author__container#{" is-compact" if layout == :compact}", data: do %>
2
+ <span class="author" data-author>
3
+ <%= content_tag :span, class: "author__container#{" is-compact" if layout == :compact}", data: do %>
4
4
  <% if layout == :compact %>
5
5
  <%= render :avatar %>
6
6
 
7
- <div>
7
+ <span>
8
8
  <%= render :name %>
9
9
 
10
10
  <% context_actions.each do |action| %>
11
11
  <%= render action %>
12
12
  <% end %>
13
- </div>
13
+ </span>
14
14
  <% elsif layout == :avatar %>
15
15
  <%= render :avatar %>
16
16
  <% else %>
@@ -24,4 +24,4 @@
24
24
  <%= render action %>
25
25
  <% end %>
26
26
  <% end %>
27
- </div>
27
+ </span>
@@ -1,5 +1,5 @@
1
1
  <div class="py-4 space-y-2">
2
- <a href="" class="text-lg text-secondary font-semibold hover:underline"><%= title %></a>
2
+ <a href="" class="text-lg text-secondary font-semibold hover:underline"><%= decidim_html_escape(title) %></a>
3
3
  <div class="flex items-center divide-x divide-gray-3">
4
4
  <% metadata.first(4).each do |item| %>
5
5
  <div class="flex items-center gap-1 px-4 lg:px-6 first:pl-0 last:pr-0 max-w-xs">
@@ -3,12 +3,12 @@
3
3
  <% hook_output = render_hook(item[:hook]) %>
4
4
  <% next if hook_output.blank? %>
5
5
 
6
- <%= content_tag :span, data: item[:data_attributes] do %>
6
+ <%= content_tag :div, data: item[:data_attributes] do %>
7
7
  <%= icon item[:icon] if item[:icon].present? %>
8
8
  <%= hook_output %>
9
9
  <% end %>
10
10
  <% else %>
11
- <%= content_tag :span, data: item[:data_attributes] do %>
11
+ <%= content_tag :div, data: item[:data_attributes] do %>
12
12
  <%= icon item[:icon] if item[:icon].present? %>
13
13
  <% if item[:text].present? %>
14
14
  <%= link_to_if enable_links? && item.has_key?(:url), item[:text], item[:url] %>
@@ -2,7 +2,7 @@
2
2
  <div class="cookies__category-trigger">
3
3
  <label for="dc-<%= category[:slug] %>" class="cookies__category-toggle">
4
4
  <input
5
- <%= %(checked="checked") if category[:mandatory] %>
5
+ <%== %(checked="checked") if category[:mandatory] %>
6
6
  id="dc-<%= category[:slug] %>"
7
7
  type="checkbox"
8
8
  name="<%= category[:slug] %>"
@@ -1,10 +1,10 @@
1
1
  <div class="participatory-space__nav-container">
2
- <button id="dropdown-trigger-participatory-space" data-component="dropdown" data-target="dropdown-menu-participatory-space" data-auto-close="true" data-disabled-md="true" data-scroll-to-menu="true">
2
+ <button id="dropdown-trigger-participatory-space" data-component="dropdown" data-target="dropdown-menu-participatory-space" data-auto-close="true" data-scroll-to-menu="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" aria-hidden="true">
8
8
  <% model.each do |item| %>
9
9
  <li>
10
10
  <%= link_to item[:url], class: "participatory-space__nav-item" do %>
@@ -0,0 +1,12 @@
1
+ <div class="notification" data-notification>
2
+ <div class="notification__wrapper">
3
+ <div class="notification__time" title="<%= l(notification.created_at) %>"> <%= notification.created_at_in_words %></div>
4
+ <div class="notification__snippet">
5
+ <span class="notification__snippet-title text-gray"><%= t("decidim.notifications.show.moderated") %></span>
6
+ </div>
7
+ </div>
8
+ <%= link_to model, remote: true, method: :delete, class: "notification__button", data: { "notification-read": "" } do %>
9
+ <span class="sr-only md:not-sr-only"><%= t("mark_as_read", scope: "layouts.decidim.notifications_dashboard") %></span>
10
+ <%= icon "check-line", class: "fill-current" %>
11
+ <% end %>
12
+ </div>
@@ -9,7 +9,11 @@ module Decidim
9
9
  include Decidim::SanitizeHelper
10
10
 
11
11
  def show
12
- render :show
12
+ if notification.event_class_instance.try(:hidden_resource?)
13
+ render :moderated
14
+ else
15
+ render :show
16
+ end
13
17
  end
14
18
 
15
19
  def notification_title
@@ -9,7 +9,7 @@
9
9
  <%= icon detail[:icon] %>
10
10
  <span>
11
11
  <% if detail[:url].present? %>
12
- <%= link_to(detail[:url], detail[:text]) %>
12
+ <%= link_to(detail[:url], detail[:text], rel: "nofollow noopener noreferrer") %>
13
13
  <% else %>
14
14
  <%= detail[:text] %>
15
15
  <% end %>
@@ -7,7 +7,7 @@
7
7
  <%= content_tag :div, units_name_text, class: "progress-bar__units" if units_name %>
8
8
 
9
9
  <% if total != 0 %>
10
- <div class="progress-bar" role="progressbar" tabindex="0" aria-label="<%= units_name.present? ? units_name_text : t("decidim.shared.progress") %>" aria-valuenow="<%= percentage %>" aria-valuemin="0" aria-valuetext="<%= percentage %> %" aria-valuemax="<%= total %>">
10
+ <div class="progress-bar" role="progressbar" aria-label="<%= units_name.present? ? units_name_text : t("decidim.shared.progress") %>" aria-valuenow="<%= number_with_precision(percentage, separator: ".", precision: 2) %>" aria-valuemin="0" aria-valuemax="100" aria-valuetext="<%= number_to_percentage(percentage, precision: 2) %>">
11
11
  <div style="width: <%= percentage %>%"></div>
12
12
  </div>
13
13
  <% end %>
@@ -29,6 +29,8 @@ module Decidim
29
29
  # total: 10,
30
30
  # )
31
31
  class ProgressBarCell < Decidim::ViewModel
32
+ include ActionView::Helpers::NumberHelper
33
+
32
34
  private
33
35
 
34
36
  def element_id
@@ -34,11 +34,15 @@
34
34
  <%= f.check_box :hide,
35
35
  label: t("decidim.shared.flag_modal.hide_content"),
36
36
  include_hidden: false,
37
+ id: hide_checkbox_id,
37
38
  data: {
38
39
  label_action: t("decidim.shared.flag_modal.hide"),
39
40
  label_report: t("decidim.shared.flag_modal.report"),
40
41
  hide: "true"
41
- }, id: hide_checkbox_id %>
42
+ },
43
+ label_options: {
44
+ for: hide_checkbox_id
45
+ } %>
42
46
  <% end %>
43
47
  <% end %>
44
48
 
@@ -1,5 +1,5 @@
1
1
  <div id="<%= id %>" class="filter-container">
2
- <button id="dropdown-trigger-resource" data-component="dropdown" data-target="dropdown-menu-resource" data-auto-close="true" data-disabled-md="true">
2
+ <button id="dropdown-trigger-resource" data-component="dropdown" data-target="dropdown-menu-resource" data-auto-close="true">
3
3
  <% resource_types.each do |resource_type| %>
4
4
  <span data-value="<%= resource_type[0] %>" class="<%= "is-active" if filter_param == resource_type[0] %>">
5
5
  <%= text_with_resource_icon(*resource_type) %>
@@ -8,7 +8,7 @@
8
8
  <%= icon "arrow-down-s-line" %>
9
9
  <%= icon "arrow-up-s-line" %>
10
10
  </button>
11
- <%= filter_form_for filter, form_path, class: "new_filter", id: "dropdown-menu-resource" do |form| %>
11
+ <%= filter_form_for filter, form_path, :class => "new_filter", :id => "dropdown-menu-resource", "aria-hidden" => true do |form| %>
12
12
  <%= form.collection_radio_buttons(
13
13
  filter_param_key,
14
14
  resource_types,
@@ -16,7 +16,7 @@
16
16
  :last,
17
17
  { checked: filter_param }
18
18
  ) do |builder|
19
- builder.label { builder.radio_button(class: "reset-defaults", hidden: true) + content_tag(:div, text_with_resource_icon(builder.value, builder.text), class: "filter") }
19
+ builder.label { builder.radio_button(class: "reset-defaults", hidden: true) + content_tag(:span, text_with_resource_icon(builder.value, builder.text), class: "filter") }
20
20
  end %>
21
21
  <% end %>
22
22
  </div>
@@ -1,7 +1,7 @@
1
1
  <div class="statistic <%= stat_dom_class %>" data-statistic>
2
- <h3 class="statistic__title" title="<%= stat_title %>">
2
+ <span class="statistic__title" title="<%= stat_title %>">
3
3
  <%= stat_title %>
4
- </h3>
4
+ </span>
5
5
  <span class="statistic__number">
6
6
  <%= stat_number %>
7
7
  </span>
@@ -37,10 +37,10 @@
37
37
  <%= icon "upload-cloud-2-line", class: "w-8 h-8 text-gray fill-current" %>
38
38
  <%= t("decidim.forms.upload_help.dropzone") %>
39
39
  </span>
40
- <button class="button button__sm button__secondary" data-select-file-button>
40
+ <label class="button button__sm button__secondary" for="files-<%= modal_id %>">
41
41
  <span><%= t("decidim.forms.upload.select_file") %></span>
42
42
  <%= icon "arrow-right-line", class: "fill-current" %>
43
- </button>
43
+ </label>
44
44
  </div>
45
45
  </div>
46
46
  </div>
@@ -60,8 +60,7 @@
60
60
  <%= t("decidim.shared.confirm_modal.cancel") %>
61
61
  </button>
62
62
  <button type="button" class="button button__sm md:button__lg button__secondary" data-dropzone-save data-dialog-close="<%= modal_id %>" disabled>
63
- <%= t("next", scope: "decidim.messaging.conversations.index") %>
64
- <%= icon "arrow-right-line", class: "fill-current" %>
63
+ <%= t("save", scope: "decidim.forms.upload.labels") %>
65
64
  </button>
66
65
  </div>
67
66
  <% end %>
@@ -17,7 +17,7 @@ module Decidim
17
17
  # Breaks the request lifecycle, if user is not authenticated.
18
18
  # Otherwise returns.
19
19
  def ensure_authenticated!
20
- return true unless current_organization.force_users_to_authenticate_before_access_organization
20
+ return true unless current_organization&.force_users_to_authenticate_before_access_organization
21
21
 
22
22
  # Next stop: Check whether auth is ok
23
23
  unless user_signed_in?
@@ -25,7 +25,7 @@ module Decidim
25
25
  #
26
26
  # Returns a String.
27
27
  def organization_time_zone
28
- @organization_time_zone ||= current_organization.time_zone
28
+ @organization_time_zone ||= current_organization&.time_zone
29
29
  end
30
30
  end
31
31
  end
@@ -9,6 +9,8 @@ module Decidim
9
9
  @badges = Decidim::Gamification.badges.sort_by(&:name)
10
10
  end
11
11
 
12
+ private
13
+
12
14
  def breadcrumb_item
13
15
  {
14
16
  label: t("decidim.gamification.badges.index.title"),
@@ -14,7 +14,8 @@ module Decidim
14
14
  rescue_from URI::InvalidURIError, with: :modal
15
15
 
16
16
  def new
17
- headers["X-Robots-Tag"] = "noindex"
17
+ headers["X-Robots-Tag"] = "none"
18
+ headers["Link"] = %(<#{url_for}>; rel="canonical")
18
19
  end
19
20
 
20
21
  private
@@ -36,7 +37,19 @@ module Decidim
36
37
  end
37
38
 
38
39
  def external_url
39
- @external_url ||= URI.parse(URI::Parser.new.escape(params[:external_url]))
40
+ @external_url ||= URI.parse(escape_url(params[:external_url]))
41
+ end
42
+
43
+ def escape_url(external_url)
44
+ before_fragment, fragment = external_url.split("#", 2)
45
+ escaped_before_fragment = URI::Parser.new.escape(before_fragment)
46
+
47
+ if fragment
48
+ escaped_fragment = URI::Parser.new.escape(fragment)
49
+ "#{escaped_before_fragment}##{escaped_fragment}"
50
+ else
51
+ escaped_before_fragment
52
+ end
40
53
  end
41
54
  end
42
55
  end
@@ -106,9 +106,9 @@ module Decidim
106
106
  end
107
107
 
108
108
  def message(value)
109
- return content_tag(:span, value, class: "flash__message flex items-center") unless value.is_a?(Hash)
109
+ return content_tag(:div, value, class: "flash__message flex items-center") unless value.is_a?(Hash)
110
110
 
111
- content_tag(:span, class: "flash__message") do
111
+ content_tag(:div, class: "flash__message") do
112
112
  concat value[:title]
113
113
  concat content_tag(:span, value[:body], class: "flash__message-body")
114
114
  end
@@ -16,8 +16,7 @@ module Decidim
16
16
  include_hidden: false,
17
17
  label_options: {
18
18
  "data-children-checkbox": parent_id,
19
- value:,
20
- class: "filter"
19
+ value:
21
20
  }
22
21
  }
23
22
  options.merge!(checkbox_options)
@@ -7,23 +7,57 @@ module Decidim
7
7
  include LocalisedMailer
8
8
  include MultitenantAssetHost
9
9
  after_action :set_smtp
10
+ after_action :set_from
10
11
 
11
12
  default from: Decidim.config.mailer_sender
12
13
  layout "decidim/mailer"
13
14
 
14
15
  private
15
16
 
17
+ attr_reader :organization
18
+
16
19
  def set_smtp
17
- return if @organization.nil? || @organization.smtp_settings.blank?
20
+ return if organization.nil? || organization.smtp_settings.blank? || organization.smtp_settings.except("from", "from_label", "from_email").all?(&:blank?)
18
21
 
19
- mail.from = @organization.smtp_settings["from"].presence || mail.from
20
22
  mail.reply_to = mail.reply_to || Decidim.config.mailer_reply
21
23
  mail.delivery_method.settings.merge!(
22
- address: @organization.smtp_settings["address"],
23
- port: @organization.smtp_settings["port"],
24
- user_name: @organization.smtp_settings["user_name"],
25
- password: Decidim::AttributeEncryptor.decrypt(@organization.smtp_settings["encrypted_password"])
24
+ address: organization.smtp_settings["address"],
25
+ port: organization.smtp_settings["port"],
26
+ user_name: organization.smtp_settings["user_name"],
27
+ password: Decidim::AttributeEncryptor.decrypt(organization.smtp_settings["encrypted_password"])
26
28
  ) { |_k, o, v| v.presence || o }.compact_blank!
27
29
  end
30
+
31
+ def set_from
32
+ return if organization.nil?
33
+ return if already_defined_name_in_mail?(mail.from.first)
34
+
35
+ mail.from = sender
36
+ end
37
+
38
+ def sender
39
+ return Decidim.config.mailer_sender if return_mailer_sender?
40
+ return default_sender if organization.smtp_settings.blank?
41
+ return default_sender if organization.smtp_settings["from"].nil?
42
+ return default_sender if organization.smtp_settings["from"].empty?
43
+
44
+ smtp_settings_from = organization.smtp_settings["from"]
45
+ return smtp_settings_from if already_defined_name_in_mail?(smtp_settings_from)
46
+
47
+ email_address_with_name(smtp_settings_from, organization.name)
48
+ end
49
+
50
+ def default_sender
51
+ email_address_with_name(Decidim.config.mailer_sender, organization.name)
52
+ end
53
+
54
+ def already_defined_name_in_mail?(mail_address)
55
+ # if there is an space, there is already a name in the address
56
+ mail_address.match?(/ /)
57
+ end
58
+
59
+ def return_mailer_sender?
60
+ already_defined_name_in_mail?(Decidim.config.mailer_sender) && organization.smtp_settings.present?
61
+ end
28
62
  end
29
63
  end
@@ -104,6 +104,20 @@ const createDropdown = (component) => {
104
104
  });
105
105
  }
106
106
 
107
+ // Disable focus on children elements so we can pass the AXE accessibility tests
108
+ const dropdownMenu = document.getElementById(dropdownOptions.dropdown);
109
+ if (dropdownMenu.getAttribute("aria-hidden") === "true") {
110
+ dropdownMenu.
111
+ querySelectorAll("a, input, button").
112
+ forEach((element) => { element.tabIndex = -1 })
113
+ }
114
+
115
+ component.addEventListener("click", () => {
116
+ dropdownMenu.
117
+ querySelectorAll("a, input, button").
118
+ forEach((element) => { element.tabIndex = 0 })
119
+ })
120
+
107
121
  Dropdowns.render(component.id, dropdownOptions);
108
122
  }
109
123
 
@@ -0,0 +1,44 @@
1
+ /**
2
+ * This script modifies the behavior of Abide form validation to address the issue of form validation errors
3
+ * appearing prematurely in input fields.
4
+ *
5
+ * The primary goal is to hide error messages until the input field loses focus.
6
+ */
7
+
8
+ class AbideFormValidatorFixer {
9
+ initialize() {
10
+ const forms = document.querySelectorAll("main [data-live-validate='true']");
11
+
12
+ forms.forEach((form) => {
13
+ if (this.isElementVisible(form)) {
14
+ this.setupForm(form);
15
+ }
16
+ });
17
+ }
18
+
19
+ isElementVisible(element) {
20
+ return element.offsetParent !== null && getComputedStyle(element).display !== "none";
21
+ }
22
+
23
+ setupForm(form) {
24
+ const inputs = form.querySelectorAll("input");
25
+
26
+ inputs.forEach((input) => {
27
+ const errorElement = input.closest("label")?.querySelector(".form-error") || input.parentElement.querySelector(".form-error");
28
+ if (!errorElement) {
29
+ return;
30
+ }
31
+ form.removeAttribute("data-live-validate");
32
+ input.addEventListener("input", this.hideErrorElement.bind(this, errorElement));
33
+ });
34
+ }
35
+
36
+ hideErrorElement(errorElement) {
37
+ errorElement.classList.remove("is-visible");
38
+ }
39
+ }
40
+
41
+ document.addEventListener("DOMContentLoaded", () => {
42
+ const validatorFixer = new AbideFormValidatorFixer();
43
+ validatorFixer.initialize();
44
+ });
@@ -154,20 +154,16 @@ export default class UploadModal {
154
154
  // Disabled save button when any children have data-state="error"
155
155
  this.saveButton.disabled = Array.from(files).filter(({ dataset: { state } }) => state === STATUS.ERROR).length > 0;
156
156
 
157
- const dataSelectFileButton = this.emptyItems.querySelector("[data-select-file-button]");
158
-
159
157
  // Only allow to continue the upload when the multiple option is true (default: false)
160
158
  const continueUpload = !files.length || this.options.multiple
161
159
  this.input.disabled = !continueUpload
162
160
  if (continueUpload) {
163
161
  this.emptyItems.classList.remove("is-disabled");
164
- dataSelectFileButton.removeAttribute("disabled");
162
+ this.emptyItems.querySelector("label").removeAttribute("disabled");
165
163
  } else {
166
164
  this.emptyItems.classList.add("is-disabled");
167
- dataSelectFileButton.disabled = true;
165
+ this.emptyItems.querySelector("label").disabled = true;
168
166
  }
169
-
170
- dataSelectFileButton.addEventListener("click", () => this.input.click());
171
167
  }
172
168
 
173
169
  createUploadItem(file, errors, opts = {}) {
@@ -50,6 +50,7 @@ import "src/decidim/impersonation"
50
50
  import "src/decidim/gallery"
51
51
  import "src/decidim/direct_uploads/upload_field"
52
52
  import "src/decidim/data_consent"
53
+ import "src/decidim/abide_form_validator_fixer"
53
54
  import "src/decidim/sw"
54
55
 
55
56
  // local deps that require initialization
@@ -90,6 +91,33 @@ window.Decidim = window.Decidim || {
90
91
 
91
92
  window.morphdom = morphdom
92
93
 
94
+ // REDESIGN_PENDING: deprecated
95
+ window.initFoundation = (element) => {
96
+ $(element).foundation();
97
+
98
+ // Fix compatibility issue with the `a11y-accordion-component` package that
99
+ // uses the `data-open` attribute to indicate the open state for the accordion
100
+ // trigger.
101
+ //
102
+ // In Foundation, these listeners are initiated on the document node always,
103
+ // regardless of the element for which foundation is initiated. Therefore, we
104
+ // need the document node here instead of the `element` passed to this
105
+ // function.
106
+ const $document = $(document);
107
+
108
+ $document.off("click.zf.trigger", window.Foundation.Triggers.Listeners.Basic.openListener);
109
+ $document.on("click.zf.trigger", "[data-open]", (ev, ...restArgs) => {
110
+ // Do not apply for the accordion triggers.
111
+ const accordion = ev.currentTarget?.closest("[data-component='accordion']");
112
+ if (accordion) {
113
+ return;
114
+ }
115
+
116
+ // Otherwise call the original implementation
117
+ Reflect.apply(window.Foundation.Triggers.Listeners.Basic.openListener, ev.currentTarget, [ev, ...restArgs]);
118
+ });
119
+ };
120
+
93
121
  Rails.start()
94
122
 
95
123
  /**
@@ -104,7 +132,7 @@ const initializer = (element = document) => {
104
132
  window.focusGuard = window.focusGuard || new FocusGuard(document.body);
105
133
 
106
134
  // REDESIGN_PENDING: deprecated
107
- $(element).foundation();
135
+ window.initFoundation(element);
108
136
 
109
137
  svg4everybody();
110
138
 
@@ -4,10 +4,10 @@
4
4
 
5
5
  [data-component="accordion"]
6
6
  [id*="comment"][class="comment-reply"][aria-hidden="true"] {
7
- display: block;
7
+ display: none;
8
8
  }
9
9
 
10
10
  [data-component="accordion"]
11
11
  [id*="comment"][class="comment-reply"][aria-hidden="false"] {
12
- display: none;
12
+ display: block;
13
13
  }
@@ -134,7 +134,7 @@
134
134
  &-month,
135
135
  &-day,
136
136
  &-year {
137
- @apply inline-flex items-center justify-evenly empty:[&>span]:hidden;
137
+ @apply inline-flex items-center justify-evenly empty:[&>div]:hidden;
138
138
  }
139
139
  }
140
140
 
@@ -143,7 +143,7 @@
143
143
  &__list-metadata {
144
144
  @apply mt-auto inline-flex flex-wrap gap-x-4 md:gap-0;
145
145
 
146
- & > span {
146
+ & > div {
147
147
  @apply inline-flex items-center gap-1 px-0 md:px-6 border-gray-3 border-0 md:border-r first:pl-0 last:pr-0 last:border-r-0 text-sm text-gray-2;
148
148
 
149
149
  & > svg {
@@ -6,7 +6,7 @@
6
6
  }
7
7
 
8
8
  [data-content] {
9
- @apply min-h-[60vh] relative flex flex-col;
9
+ @apply relative flex flex-col;
10
10
  }
11
11
  }
12
12
 
@@ -31,14 +31,14 @@
31
31
  }
32
32
 
33
33
  .layout-2col {
34
- @apply md:grid grid-cols-12 container grow min-h-[60vh];
34
+ @apply md:grid grid-cols-12 container grow auto-rows-max;
35
35
 
36
36
  &__aside {
37
37
  @apply col-span-4 lg:col-span-3 md:pr-16 py-6 md:py-12 gap-6 md:gap-12 flex flex-col justify-between items-start md:justify-start before:content-[''] before:absolute before:top-0 before:left-0 before:h-full before:w-1/2 before:-z-10 md:before:bg-background;
38
38
  }
39
39
 
40
40
  &__main {
41
- @apply col-span-8 lg:col-span-9 bg-white md:pl-16 py-6 md:py-12;
41
+ @apply col-span-8 lg:col-span-9 bg-white md:pl-16 py-6 md:py-12 min-h-[60vh];
42
42
  }
43
43
 
44
44
  &__reverse &__aside {
@@ -28,9 +28,7 @@
28
28
  @apply w-24 flex-none flex justify-center;
29
29
  }
30
30
 
31
- img:not([src^="data:image"]):not(
32
- [src^="data:application/octet-stream"]
33
- ) {
31
+ img[src="data:,"] {
34
32
  @apply hidden;
35
33
  }
36
34
 
@@ -26,7 +26,7 @@ module Decidim
26
26
 
27
27
  def settings_attributes_mapping
28
28
  {
29
- name: :string,
29
+ name: :i18n,
30
30
  default_locale: :locale,
31
31
  reference_prefix: :string,
32
32
  twitter_handler: :string,
@@ -10,6 +10,8 @@ module Decidim
10
10
  # overwrite `BasePresenter#resource_presenter` to return your custom resource presenter.
11
11
  # The only requirement for custom renderers is that they should respond to `present`.
12
12
  class ResourcePresenter
13
+ include Decidim::SanitizeHelper
14
+
13
15
  # Public: Initializes the presenter.
14
16
  #
15
17
  # resource - An instance of a model that can be located by
@@ -65,7 +67,11 @@ module Decidim
65
67
  #
66
68
  # Returns an HTML-safe String.
67
69
  def present_resource_name
68
- h.translated_attribute extra["title"]
70
+ if resource.present? && resource.respond_to?(:presenter) && resource.presenter.respond_to?(:title)
71
+ resource.presenter.title(html_escape: true)
72
+ else
73
+ decidim_escape_translated(extra["title"]).html_safe
74
+ end
69
75
  end
70
76
  end
71
77
  end
@@ -86,7 +86,7 @@ module Decidim
86
86
  locales.flat_map do |locale|
87
87
  previous_value = values.first.try(:[], locale)
88
88
  new_value = values.last.try(:[], locale)
89
- if previous_value == new_value
89
+ if previous_value == new_value || (previous_value.nil? && new_value.blank?)
90
90
  nil
91
91
  else
92
92
  label = generate_label(attribute, locale)