decidim-core 0.29.4 → 0.29.5

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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/activity/show.erb +6 -6
  3. data/app/cells/decidim/content_blocks/participatory_space_main_data/title.erb +11 -2
  4. data/app/cells/decidim/footer_topics/show.erb +2 -2
  5. data/app/cells/decidim/group_admins/show.erb +3 -1
  6. data/app/cells/decidim/group_members/show.erb +6 -2
  7. data/app/cells/decidim/images_panel/show.erb +5 -2
  8. data/app/cells/decidim/report_button/flag_modal.erb +11 -9
  9. data/app/cells/decidim/report_user_button/flag_modal.erb +11 -10
  10. data/app/cells/decidim/upload_modal/files.erb +4 -4
  11. data/app/cells/decidim/upload_modal_cell.rb +5 -3
  12. data/app/commands/decidim/amendable/accept.rb +2 -1
  13. data/app/controllers/concerns/decidim/headers/browser_feature_permissions.rb +50 -0
  14. data/app/controllers/decidim/amendments_controller.rb +3 -3
  15. data/app/controllers/decidim/application_controller.rb +1 -0
  16. data/app/helpers/decidim/amendments_helper.rb +2 -1
  17. data/app/helpers/decidim/filters_helper.rb +25 -0
  18. data/app/helpers/decidim/layout_helper.rb +6 -0
  19. data/app/packs/images/decidim/default-avatar.svg +1 -1
  20. data/app/packs/src/decidim/callout.js +13 -8
  21. data/app/packs/src/decidim/confirm.js +79 -59
  22. data/app/packs/src/decidim/datepicker/generate_datepicker.js +2 -0
  23. data/app/packs/src/decidim/datepicker/generate_timepicker.js +2 -0
  24. data/app/packs/src/decidim/direct_uploads/upload_field.js +3 -4
  25. data/app/packs/src/decidim/direct_uploads/upload_modal.js +8 -9
  26. data/app/packs/src/decidim/dropdown_menu.js +18 -0
  27. data/app/packs/src/decidim/editor/common/suggestion.js +11 -1
  28. data/app/packs/src/decidim/form_remote.js +1 -1
  29. data/app/packs/src/decidim/impersonation.js +1 -1
  30. data/app/packs/src/decidim/index.js +5 -1
  31. data/app/packs/src/decidim/session_timeouter.js +1 -1
  32. data/app/packs/src/decidim/utilities/dom.js +148 -0
  33. data/app/packs/stylesheets/decidim/_activity.scss +4 -4
  34. data/app/packs/stylesheets/decidim/_cards.scss +5 -1
  35. data/app/packs/stylesheets/decidim/_filters.scss +1 -1
  36. data/app/packs/stylesheets/decidim/_header.scss +11 -3
  37. data/app/packs/stylesheets/decidim/_layout.scss +2 -2
  38. data/app/packs/stylesheets/decidim/_modal.scss +1 -5
  39. data/app/packs/stylesheets/decidim/_modal_update.scss +5 -1
  40. data/app/permissions/decidim/permissions.rb +13 -1
  41. data/app/views/decidim/errors/internal_server_error.html.erb +1 -1
  42. data/app/views/decidim/errors/not_found.html.erb +1 -1
  43. data/app/views/decidim/newsletters/unsubscribe.html.erb +16 -4
  44. data/app/views/decidim/searches/_filters.html.erb +48 -13
  45. data/app/views/decidim/shared/_component_announcement.html.erb +1 -1
  46. data/app/views/decidim/shared/_confirm_modal.html.erb +3 -5
  47. data/app/views/decidim/shared/_filters.html.erb +6 -4
  48. data/app/views/layouts/decidim/_js_configuration.html.erb +1 -0
  49. data/app/views/layouts/decidim/footer/_main.html.erb +1 -1
  50. data/app/views/layouts/decidim/footer/_main_intro.html.erb +1 -1
  51. data/app/views/layouts/decidim/footer/_mini.html.erb +2 -2
  52. data/app/views/layouts/decidim/header/_main.html.erb +2 -2
  53. data/app/views/layouts/decidim/header/_main_links_desktop.html.erb +6 -0
  54. data/app/views/layouts/decidim/header/_main_links_dropdown.html.erb +2 -0
  55. data/app/views/layouts/decidim/header/_main_links_mobile_account.html.erb +1 -1
  56. data/app/views/layouts/decidim/header/_mobile_language_choose.html.erb +1 -1
  57. data/config/locales/ar.yml +0 -3
  58. data/config/locales/bg-BG.yml +2 -2
  59. data/config/locales/bg.yml +0 -5
  60. data/config/locales/ca-IT.yml +29 -5
  61. data/config/locales/ca.yml +29 -5
  62. data/config/locales/cs.yml +15 -6
  63. data/config/locales/de.yml +47 -23
  64. data/config/locales/el.yml +0 -4
  65. data/config/locales/en.yml +29 -5
  66. data/config/locales/es-MX.yml +29 -5
  67. data/config/locales/es-PY.yml +29 -5
  68. data/config/locales/es.yml +29 -5
  69. data/config/locales/eu.yml +63 -39
  70. data/config/locales/fi-plain.yml +58 -5
  71. data/config/locales/fi.yml +59 -6
  72. data/config/locales/fr-CA.yml +29 -5
  73. data/config/locales/fr-LU.yml +3 -3
  74. data/config/locales/fr.yml +29 -5
  75. data/config/locales/gl.yml +0 -3
  76. data/config/locales/hu.yml +0 -5
  77. data/config/locales/id-ID.yml +0 -3
  78. data/config/locales/is-IS.yml +0 -1
  79. data/config/locales/it.yml +124 -3
  80. data/config/locales/ja.yml +29 -5
  81. data/config/locales/lb-LU.yml +2 -2
  82. data/config/locales/lb.yml +0 -3
  83. data/config/locales/lt.yml +0 -5
  84. data/config/locales/lv.yml +0 -3
  85. data/config/locales/nl.yml +0 -3
  86. data/config/locales/no.yml +0 -3
  87. data/config/locales/pl.yml +0 -5
  88. data/config/locales/pt-BR.yml +1 -4
  89. data/config/locales/pt.yml +0 -3
  90. data/config/locales/ro-RO.yml +0 -4
  91. data/config/locales/ru.yml +0 -3
  92. data/config/locales/sk-SK.yml +3 -3
  93. data/config/locales/sk.yml +2 -3
  94. data/config/locales/sv.yml +28 -4
  95. data/config/locales/tr-TR.yml +0 -3
  96. data/config/locales/uk.yml +0 -2
  97. data/config/locales/zh-CN.yml +0 -3
  98. data/config/locales/zh-TW.yml +0 -5
  99. data/lib/decidim/assets/tailwind/tailwind.config.js.erb +2 -1
  100. data/lib/decidim/core/test/factories.rb +2 -2
  101. data/lib/decidim/core/test/shared_examples/announcements_examples.rb +4 -0
  102. data/lib/decidim/core/version.rb +1 -1
  103. data/lib/decidim/form_builder.rb +14 -0
  104. metadata +9 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c870efc9a61fb35a4d203542d3c151236fa734857034cd0218962c0d8a5889a2
4
- data.tar.gz: 870b0671cef65194a32e2fc33e430b92bab41f347c8b1068fe954dcc8e5b2990
3
+ metadata.gz: a294fcf54a5fa31a1d5e44af9a717e30ab8eab2df35ae131b9873e0d600dc630
4
+ data.tar.gz: 41703d211fa0d4d30c760cf00629bfebda559481e039298ac6a30679a46fe77c
5
5
  SHA512:
6
- metadata.gz: 3370c446ba0ebc647b221e031500993fd56719e96908a38832d62497103893295a46a33da7eadb42f110f119a4f4abf634cfd8c5a8ae707009c9a186ca31352b
7
- data.tar.gz: babce000ab8d7cd3fa51f6c8f81a2e5d6791817dda5e3336369af31316d8d7ba061e85cb38fd5f97885100b74f06f61e05b109d91fc9d1b67c2f49dc77e96c34
6
+ metadata.gz: 28e137934317b858031cda83491fb067b5ee54349fb50f037c1c0e4aa6f223afeb13899e28df90492ee53b5097a09a3d7c1add57e4d0aa0b080997a706646c89
7
+ data.tar.gz: 2e153e8e591bd91f1c6fb0ec14b5a7de5ce126191aa89ba698bf64a9a4389d4fb5e8bceb84cc5d43dc77f669b2549971c7d4e2cdfaed4cca6b8dce17172a3829
@@ -1,19 +1,19 @@
1
1
  <div class="activity" data-activity>
2
- <div class="activity__time">
2
+ <p class="activity__time">
3
3
  <%= created_at %>
4
- </div>
4
+ </p>
5
5
  <div class="activity__content">
6
- <span>
6
+ <div>
7
7
  <% if title.present? %>
8
- <span>
8
+ <p>
9
9
  <%= title_icon %>
10
10
  <%= title %>
11
- </span>
11
+ </p>
12
12
  <% end %>
13
13
  <a href="<%= resource_link_path %>">
14
14
  <%= html_truncate decidim_sanitize(resource_link_text, strip_tags: true), length: 80 %>
15
15
  </a>
16
- </span>
16
+ </div>
17
17
  <% unless hide_participatory_space? %>
18
18
  <span>
19
19
  <%= participatory_space_link %>
@@ -11,11 +11,11 @@
11
11
  <div class="content-block__description editor-content" <%= "data-component='accordion'" if should_truncate %>>
12
12
  <% if should_truncate %>
13
13
  <% seed = SecureRandom.hex(4) %>
14
- <div id="panel-view-more-<%= seed %>" aria-hidden="true">
14
+ <div id="panel-view-more-<%= seed %>" aria-hidden="true" inert>
15
15
  <%= description_text %>
16
16
  </div>
17
17
 
18
- <button class="button button__sm button__text-secondary mt-2" data-controls="panel-view-more-<%= seed %>" aria-expanded="false">
18
+ <button class="button button__sm button__text-secondary mt-2" data-controls="panel-view-more-<%= seed %>" aria-expanded="false" onclick="document.querySelector('div[id^=panel-view-more]').toggleAttribute('inert')">
19
19
  <span>
20
20
  <%= t("view_more", scope: "layouts.decidim.announcements") %>
21
21
  </span>
@@ -30,3 +30,12 @@
30
30
  <% end %>
31
31
  </div>
32
32
  <% end %>
33
+ <script>
34
+ const button = document.querySelector('button[data-controls^="panel-view-more"]')
35
+ button.addEventListener('keydown', function(e){
36
+ // press space or enter
37
+ if (e.keyCode === 32 || e.keyCode === 13){
38
+ document.querySelector('div[id^=panel-view-more]').toggleAttribute('inert')
39
+ }
40
+ })
41
+ </script>
@@ -1,5 +1,5 @@
1
- <nav role="navigation" aria-label="Help">
2
- <h2 class="h4 mb-4">Help</h2>
1
+ <nav role="navigation" aria-label="<%= t("layouts.decidim.footer.help") %>">
2
+ <h2 class="h4 mb-4"><%= t("layouts.decidim.footer.help") %></h2>
3
3
  <ul class="space-y-4 break-inside-avoid">
4
4
  <% topics.each do |topic| %>
5
5
  <%= topic_item(topic, class: "font-semibold underline") %>
@@ -21,7 +21,9 @@
21
21
  <%= link_to(
22
22
  decidim.demote_group_manage_user_path(model.nickname, membership),
23
23
  method: :post,
24
- data: { confirm: t("decidim.group_admins.actions.are_you_sure") },
24
+ data: { confirm: t("decidim.group_admins.actions.confirm_remove_from_admin"),
25
+ confirm_title: t("decidim.group_admins.actions.confirm_modal.title_remove"),
26
+ confirm_button: t("decidim.group_admins.actions.confirm_modal.ok_remove") },
25
27
  class: "button button__sm button__transparent-secondary"
26
28
  ) do %>
27
29
  <span><%= t("decidim.group_admins.actions.demote_admin") %></span>
@@ -24,14 +24,18 @@
24
24
  t("decidim.group_members.actions.promote_to_admin"),
25
25
  decidim.promote_group_manage_user_path(model.nickname, membership),
26
26
  method: :post,
27
- data: { confirm: t("decidim.group_members.actions.are_you_sure") },
27
+ data: { confirm: t("decidim.group_members.actions.confirm_promote_to_admin"),
28
+ confirm_title: t("decidim.group_admins.actions.confirm_modal.title_add"),
29
+ confirm_button: t("decidim.group_admins.actions.confirm_modal.ok_add") },
28
30
  class: "button button__sm button__transparent-secondary"
29
31
  ) %>
30
32
 
31
33
  <%= link_to(
32
34
  decidim.group_manage_user_path(model.nickname, membership),
33
35
  method: :delete,
34
- data: { confirm: t("decidim.group_members.actions.are_you_sure") },
36
+ data: { confirm: t("decidim.group_members.actions.confirm_remove_from_group"),
37
+ confirm_title: t("decidim.group_members.actions.confirm_modal.title_remove"),
38
+ confirm_button: t("decidim.group_members.actions.confirm_modal.ok_remove") },
35
39
  class: "button button__sm button__transparent-secondary"
36
40
  ) do %>
37
41
  <span><%= t("decidim.group_members.actions.remove_from_group") %></span>
@@ -1,7 +1,10 @@
1
1
  <div class="grid grid-cols-1 md:grid-cols-4 gap-6">
2
- <% photos.each_with_index do |photo, index| %>
2
+ <% photos.each_with_index do |photo| %>
3
3
  <%= link_to photo.big_url, target: "_blank", rel: "noopener", class: "overflow-hidden rounded aspect-video" do %>
4
- <%= image_tag photo.thumbnail_url, class:"w-full h-full object-cover", alt: strip_tags(translated_attribute(photo.title)) %>
4
+ <%= image_tag photo.thumbnail_url,
5
+ class: "w-full h-full object-cover",
6
+ role: "presentation",
7
+ "data-filename": strip_tags(translated_attribute(photo.title)) %>
5
8
  <% end %>
6
9
  <% end %>
7
10
  </div>
@@ -6,16 +6,18 @@
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>
9
- <p class="flag-modal__form-reason"><%= t("decidim.shared.flag_modal.reason") %>:</p>
10
- <%= f.collection_radio_buttons :reason, [
11
- [:spam, t("decidim.shared.flag_modal.spam")],
12
- [:offensive, t("decidim.shared.flag_modal.offensive")],
13
- [:does_not_belong, t("decidim.shared.flag_modal.does_not_belong", organization_name: current_organization_name)]
14
- ], :first, :last do |builder|
15
- builder.label(for: nil, class: "form__wrapper-checkbox-label") { builder.radio_button(id: nil) + builder.text }
16
- end %>
9
+ <fieldset class="mt-6">
10
+ <legend class="flag-modal__form-reason"><%= t("decidim.shared.flag_modal.reason") %>:</legend>
11
+ <%= f.collection_radio_buttons :reason, [
12
+ [:spam, t("decidim.shared.flag_modal.spam")],
13
+ [:offensive, t("decidim.shared.flag_modal.offensive")],
14
+ [:does_not_belong, t("decidim.shared.flag_modal.does_not_belong", organization_name: current_organization_name)]
15
+ ], :first, :last do |builder|
16
+ builder.label(for: "#{builder.value.to_s}-#{modal_id}", class: "form__wrapper-checkbox-label") { builder.radio_button(id: "#{builder.value.to_s}-#{modal_id}") + builder.text }
17
+ end %>
18
+ </fieldset>
17
19
 
18
- <%= f.text_area :details, rows: 4, label_options: { class: "flag-modal__form-textarea-label", for: nil }, id: nil %>
20
+ <%= f.text_area :details, rows: 4, label_options: { class: "flag-modal__form-textarea-label", for: "additional-comments-#{modal_id}" }, id: "additional-comments-#{modal_id}" %>
19
21
 
20
22
  <% if frontend_administrable? %>
21
23
  <%= f.check_box :hide,
@@ -6,16 +6,17 @@
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>
9
- <p class="flag-modal__form-reason"><%= t("decidim.shared.flag_modal.reason") %>:</p>
10
- <%= f.collection_radio_buttons :reason, [
11
- [:spam, t("decidim.shared.flag_user_modal.spam")],
12
- [:offensive, t("decidim.shared.flag_user_modal.offensive")],
13
- [:does_not_belong, t("decidim.shared.flag_user_modal.does_not_belong", organization_name: current_organization_name)]
14
- ], :first, :last do |builder|
15
- builder.label(for: nil, class: "form__wrapper-checkbox-label") { builder.radio_button(id: nil) + builder.text }
16
- end %>
17
-
18
- <%= f.text_area :details, rows: 4, label_options: { class: "flag-modal__form-textarea-label", for: nil }, id: nil %>
9
+ <fieldset class="mt-6">
10
+ <legend class="flag-modal__form-reason"><%= t("decidim.shared.flag_modal.reason") %>:</legend>
11
+ <%= f.collection_radio_buttons :reason, [
12
+ [:spam, t("decidim.shared.flag_user_modal.spam")],
13
+ [:offensive, t("decidim.shared.flag_user_modal.offensive")],
14
+ [:does_not_belong, t("decidim.shared.flag_user_modal.does_not_belong", organization_name: current_organization_name)]
15
+ ], :first, :last do |builder|
16
+ builder.label(for: "#{builder.value.to_s}-#{modal_id}", class: "form__wrapper-checkbox-label") { builder.radio_button(id: "#{builder.value.to_s}-#{modal_id}") + builder.text }
17
+ end %>
18
+ </fieldset>
19
+ <%= f.text_area :details, rows: 4, label_options: { class: "flag-modal__form-textarea-label", for: "additional-comments-#{modal_id}" }, id: "additional-comments-#{modal_id}" %>
19
20
 
20
21
  <% if frontend_administrable? %>
21
22
  <%= f.check_box :block,
@@ -25,19 +25,19 @@
25
25
 
26
26
  <div class="attachment-details" data-attachment-id="<%= attachment.id %>" data-title="<%= title_for(attachment) %>" data-filename="<%= file_name_for(attachment) %>" data-state="uploaded">
27
27
  <% if file_attachment_path(attachment) && blob(attachment).image? %>
28
- <div><%= image_tag(file_attachment_path(attachment), alt: title_for(attachment) || file_name_for(attachment)) %></div>
28
+ <div><%= image_tag(file_attachment_path(attachment), alt: "") %></div>
29
29
  <% elsif uploader_default_image_path(attribute).present? %>
30
30
  <div><%= image_tag uploader_default_image_path(attribute) %></div>
31
31
  <% end %>
32
32
 
33
33
  <% if has_title? %>
34
- <span><%= title_for(attachment) %> (<%= truncated_file_name_for(attachment) %>)</span>
34
+ <span><%= title_for(attachment) %></span>
35
35
  <%= form.hidden_field attribute, multiple: true, value: attachment.id, id: attachment.id %>
36
36
  <% else %>
37
37
  <% if blob(attachment).image? %>
38
- <span><%= truncated_file_name_for(attachment, 15) %></span>
38
+ <span><%= title_for(attachment) %></span>
39
39
  <% else %>
40
- <%= link_to truncated_file_name_for(attachment), file_attachment_path(attachment) %>
40
+ <%= link_to title_for(attachment), file_attachment_path(attachment), class: "w-full break-all mb-2" %>
41
41
  <% end %>
42
42
  <% end %>
43
43
  </div>
@@ -132,9 +132,11 @@ module Decidim
132
132
  end
133
133
 
134
134
  def title_for(attachment)
135
- return unless has_title?
136
-
137
- decidim_html_escape(decidim_sanitize(translated_attribute(attachment.title)))
135
+ if has_title?
136
+ decidim_html_escape(decidim_sanitize(translated_attribute(attachment.title)))
137
+ else
138
+ decidim_html_escape(decidim_sanitize(determine_filename(attachment)))
139
+ end
138
140
  end
139
141
 
140
142
  def truncated_file_name_for(attachment, max_length = 31)
@@ -62,7 +62,8 @@ module Decidim
62
62
  @amendable.save!
63
63
  @amendable
64
64
  end
65
- @amendable.add_coauthor(@amender, user_group: @user_group)
65
+
66
+ @amendable.add_coauthor(@amender, user_group: @user_group) if @amendable.is_a?(Decidim::Coauthorable)
66
67
  end
67
68
 
68
69
  def notify_emendation_state_change!
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Decidim
6
+ module Headers
7
+ # This module controls the "Permissions-Policy" header to define the
8
+ # specific sets of browser features that the website is able to use.
9
+ module BrowserFeaturePermissions
10
+ extend ActiveSupport::Concern
11
+
12
+ included do
13
+ after_action :define_permissions_policy
14
+ end
15
+
16
+ private
17
+
18
+ def define_permissions_policy
19
+ return if response.media_type != "text/html"
20
+ return if response.headers["Permissions-Policy"].present?
21
+
22
+ # Allow the "unload" and "onbeforeunload" events to be used at the
23
+ # current domain to prevent the user unintentionally changing the page
24
+ # when they have something important to do on the page, such as an
25
+ # unsaved form.
26
+ #
27
+ # This header is required because Chrome is phasing this event out due
28
+ # to some performance issues with the back/forward cache feature of the
29
+ # browser. However, currently there are no alternative events that would
30
+ # allow preventing accidental page reloads, tab closing or window
31
+ # closing.
32
+ #
33
+ # For further information, see:
34
+ # https://developer.chrome.com/docs/web-platform/deprecating-unload
35
+ # https://github.com/fergald/docs/blob/master/explainers/permissions-policy-unload.md
36
+ #
37
+ # Note that even Google suggests using the "beforeunload" for this
38
+ # particular use case:
39
+ # https://developer.chrome.com/docs/web-platform/page-lifecycle-api#events
40
+ #
41
+ # beforeunload
42
+ # Important: the beforeunload event should only be used to alert the
43
+ # user of unsaved changes. Once those changes are saved, the event
44
+ # should be removed. It should never be added unconditionally to the
45
+ # page, as doing so can hurt performance in some cases.
46
+ response.headers["Permissions-Policy"] = "unload=(self)"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -107,7 +107,7 @@ module Decidim
107
107
  end
108
108
 
109
109
  def reject
110
- enforce_permission_to :reject, :amendment, current_component: amendable.component
110
+ enforce_permission_to :reject, :amendment, amendable:, current_component: amendable.component
111
111
 
112
112
  @form = form(Decidim::Amendable::RejectForm).from_model(amendment)
113
113
 
@@ -143,13 +143,13 @@ module Decidim
143
143
  end
144
144
 
145
145
  def review
146
- enforce_permission_to :accept, :amendment, current_component: amendable.component
146
+ enforce_permission_to :accept, :amendment, amendable:, current_component: amendable.component
147
147
 
148
148
  @form = form(Decidim::Amendable::ReviewForm).from_params(params)
149
149
  end
150
150
 
151
151
  def accept
152
- enforce_permission_to :accept, :amendment, current_component: amendable.component
152
+ enforce_permission_to :accept, :amendment, amendable:, current_component: amendable.component
153
153
 
154
154
  @form = form(Decidim::Amendable::ReviewForm).from_params(params)
155
155
 
@@ -16,6 +16,7 @@ module Decidim
16
16
  include NeedsTosAccepted
17
17
  include Headers::HttpCachingDisabler
18
18
  include Headers::ContentSecurityPolicy
19
+ include Headers::BrowserFeaturePermissions
19
20
  include ActionAuthorization
20
21
  include ForceAuthentication
21
22
  include SafeRedirect
@@ -70,8 +70,9 @@ module Decidim
70
70
  # Checks if the user can accept and reject the emendation
71
71
  def allowed_to_accept_and_reject?(emendation)
72
72
  return unless emendation.amendment.evaluating?
73
+ return current_user.admin? if emendation.amendable.respond_to?(:official?) && emendation.amendable.official?
73
74
 
74
- emendation.amendable.created_by?(current_user) || current_user.admin?
75
+ emendation.amendable.created_by?(current_user)
75
76
  end
76
77
 
77
78
  # Checks if the user can promote the emendation
@@ -32,6 +32,31 @@ module Decidim
32
32
  end
33
33
  end
34
34
 
35
+ def filter_search_label(label, id)
36
+ I18n.t("decidim.searches.filters.resource", label:, collection: filter_for_resource(id))
37
+ end
38
+
39
+ def filter_for_resource(skip_to_id)
40
+ case skip_to_id
41
+ when "proposals"
42
+ I18n.t("decidim/proposals/proposal.other", scope: "activerecord.models")
43
+ when "meetings"
44
+ I18n.t("decidim/meetings/meeting.other", scope: "activerecord.models")
45
+ when "debates"
46
+ I18n.t("decidim/debates/debate.other", scope: "activerecord.models")
47
+ when "sortitions"
48
+ I18n.t("decidim/sortitions/sortition.other", scope: "activerecord.models")
49
+ when "surveys"
50
+ I18n.t("decidim/surveys/survey.other", scope: "activerecord.models")
51
+ when "projects"
52
+ I18n.t("decidim/budgets/project.other", scope: "activerecord.models")
53
+ when "initiatives"
54
+ I18n.t("decidim/initiative.other", scope: "activerecord.models")
55
+ else
56
+ ""
57
+ end
58
+ end
59
+
35
60
  private
36
61
 
37
62
  # Creates a unique namespace for a filter form to prevent duplicate IDs in
@@ -60,6 +60,12 @@ module Decidim
60
60
  html_properties = options.with_indifferent_access.transform_keys(&:dasherize).slice("width", "height", "aria-label", "role", "aria-hidden", "class", "style")
61
61
  html_properties = default_html_properties.merge(html_properties)
62
62
 
63
+ if name == "wechat-line"
64
+ html_properties = html_properties.merge({ "aria-label" => I18n.t("decidim.author.comments.other") }).except("aria-hidden")
65
+ elsif name == "heart-line"
66
+ html_properties = html_properties.merge({ "aria-label" => I18n.t("decidim.author.endorsements.other") }).except("aria-hidden")
67
+ end
68
+
63
69
  href = Decidim.cors_enabled ? "" : asset_pack_path("media/images/remixicon.symbol.svg")
64
70
 
65
71
  content_tag :svg, html_properties do
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="Layer_1" width="200" height="200" x="0" y="0" enable-background="new 0 0 200 200" version="1.1" viewBox="0 0 200 200" xml:space="preserve"><g><path fill="#C5C5C5" d="M200-0.5H0v200h26.099c2.885-35.293,24.07-64.381,52.419-74.319C64.372,117.596,54.75,102.674,54.75,85.5 c0-24.853,20.146-45,45-45c24.853,0,45,20.147,45,45c0,17.095-9.533,31.963-23.572,39.58c28.5,9.837,49.828,39.002,52.724,74.42 H200V-0.5z"/><path fill="#FFF" d="M121.178,125.08c14.039-7.617,23.572-22.485,23.572-39.58c0-24.853-20.147-45-45-45 c-24.854,0-45,20.147-45,45c0,17.174,9.622,32.096,23.768,39.681c-28.349,9.938-49.534,39.026-52.419,74.319h147.803 C171.006,164.082,149.678,134.917,121.178,125.08z"/></g></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="Layer_1" width="200" height="200" x="0" y="0" enable-background="new 0 0 200 200" version="1.1" viewBox="0 0 200 200" xml:space="preserve"><g><path fill="#908e8e" d="M200-0.5H0v200h26.099c2.885-35.293,24.07-64.381,52.419-74.319C64.372,117.596,54.75,102.674,54.75,85.5 c0-24.853,20.146-45,45-45c24.853,0,45,20.147,45,45c0,17.095-9.533,31.963-23.572,39.58c28.5,9.837,49.828,39.002,52.724,74.42 H200V-0.5z"/><path fill="#FFF" d="M121.178,125.08c14.039-7.617,23.572-22.485,23.572-39.58c0-24.853-20.147-45-45-45 c-24.854,0-45,20.147-45,45c0,17.174,9.622,32.096,23.768,39.681c-28.349,9.938-49.534,39.026-52.419,74.319h147.803 C171.006,164.082,149.678,134.917,121.178,125.08z"/></g></svg>
@@ -4,13 +4,18 @@
4
4
  * reload without this.
5
5
  */
6
6
 
7
- $(() => {
8
- const $callout = $('.callout[role="alert"]:first');
9
- if ($callout.length > 0) {
10
- setTimeout(() => {
11
- // The content insertion is to try to hint some of the screen readers
12
- // that the alert content has changed and needs to be announced.
13
- $callout.attr("tabindex", "0").focus().html(`${$callout.html()}&nbsp;`);
14
- }, 500);
7
+ document.addEventListener("turbo:load", () => {
8
+ const callout = document.querySelector(".flash[role='alert']");
9
+ if (!callout) {
10
+ return;
15
11
  }
12
+
13
+ setTimeout(() => {
14
+ callout.setAttribute("tabindex", "0");
15
+ callout.focus();
16
+
17
+ // The content insertion is to try to hint some of the screen readers
18
+ // that the alert content has changed and needs to be announced.
19
+ callout.innerHTML += "&nbsp;";
20
+ }, 500);
16
21
  });
@@ -5,12 +5,14 @@
5
5
  * it to gain control over the confirm events BEFORE rails-ujs is loaded.
6
6
  */
7
7
 
8
- import Rails from "@rails/ujs"
8
+ const { Rails } = window;
9
9
 
10
10
  class ConfirmDialog {
11
11
  constructor(sourceElement) {
12
12
  this.$modal = $("#confirm-modal");
13
- this.$source = sourceElement;
13
+ if (sourceElement) {
14
+ this.$source = $(sourceElement);
15
+ }
14
16
  this.$content = $("[data-confirm-modal-content]", this.$modal);
15
17
  this.$buttonConfirm = $("[data-confirm-ok]", this.$modal);
16
18
  this.$buttonCancel = $("[data-confirm-cancel]", this.$modal);
@@ -29,22 +31,37 @@ class ConfirmDialog {
29
31
  this.$buttonConfirm.on("click", (ev) => {
30
32
  ev.preventDefault();
31
33
 
32
- window.Decidim.currentDialogs["confirm-modal"].close()
33
- resolve(true);
34
- this.$source.focus();
34
+ this.close(() => resolve(true));
35
35
  });
36
36
 
37
37
  this.$buttonCancel.on("click", (ev) => {
38
38
  ev.preventDefault();
39
39
 
40
- window.Decidim.currentDialogs["confirm-modal"].close()
41
- resolve(false);
42
- this.$source.focus();
40
+ this.close(() => resolve(false));
43
41
  });
44
42
  });
45
43
  }
44
+
45
+ close(afterClose) {
46
+ window.Decidim.currentDialogs["confirm-modal"].close()
47
+ afterClose();
48
+ if (this.$source) {
49
+ this.$source.focus();
50
+ }
51
+ }
46
52
  }
47
53
 
54
+ const runConfirm = (message, sourceElement = null) => new Promise((resolve) => {
55
+ const dialog = new ConfirmDialog(sourceElement);
56
+ dialog.confirm(message).then((answer) => {
57
+ let completed = true;
58
+ if (sourceElement) {
59
+ completed = Rails.fire(sourceElement, "confirm:complete", [answer]);
60
+ }
61
+ resolve(answer && completed);
62
+ });
63
+ });
64
+
48
65
  // Override the default confirm dialog by Rails
49
66
  // See:
50
67
  // https://github.com/rails/rails/blob/fba1064153d8e2f4654df7762a7d3664b93e9fc8/actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee
@@ -64,37 +81,35 @@ const allowAction = (ev, element) => {
64
81
  return false;
65
82
  }
66
83
 
67
- const dialog = new ConfirmDialog(
68
- $(element)
69
- );
70
- dialog.confirm(message).then((answer) => {
71
- const completed = Rails.fire(element, "confirm:complete", [answer]);
72
- if (answer && completed) {
73
- // Allow the event to propagate normally and re-dispatch it without
74
- // the confirm data attribute which the Rails internal method is
75
- // checking.
76
- $(element).data("confirm", null);
77
- $(element).removeAttr("data-confirm");
78
-
79
- // The submit button click events will not do anything if they are
80
- // dispatched as is. In these cases, just submit the underlying form.
81
- if (ev.type === "click" &&
82
- (
83
- $(element).is('button[type="submit"]') ||
84
- $(element).is('input[type="submit"]')
85
- )
86
- ) {
87
- $(element).parents("form").submit();
88
- } else {
89
- let origEv = ev.originalEvent || ev;
90
- let newEv = origEv;
91
- if (typeof Event === "function") {
92
- // Clone the event because otherwise some click events may not
93
- // work properly when re-dispatched.
94
- newEv = new origEv.constructor(origEv.type, origEv);
95
- }
96
- ev.target.dispatchEvent(newEv);
84
+ runConfirm(message, element).then((answer) => {
85
+ if (!answer) {
86
+ return;
87
+ }
88
+
89
+ // Allow the event to propagate normally and re-dispatch it without
90
+ // the confirm data attribute which the Rails internal method is
91
+ // checking.
92
+ $(element).data("confirm", null);
93
+ $(element).removeAttr("data-confirm");
94
+
95
+ // The submit button click events will not do anything if they are
96
+ // dispatched as is. In these cases, just submit the underlying form.
97
+ if (ev.type === "click" &&
98
+ (
99
+ $(element).is('button[type="submit"]') ||
100
+ $(element).is('input[type="submit"]')
101
+ )
102
+ ) {
103
+ $(element).parents("form").submit();
104
+ } else {
105
+ let origEv = ev.originalEvent || ev;
106
+ let newEv = origEv;
107
+ if (typeof Event === "function") {
108
+ // Clone the event because otherwise some click events may not
109
+ // work properly when re-dispatched.
110
+ newEv = new origEv.constructor(origEv.type, origEv);
97
111
  }
112
+ ev.target.dispatchEvent(newEv);
98
113
  }
99
114
  });
100
115
 
@@ -129,26 +144,31 @@ const handleDocumentEvent = (ev, matchSelectors) => {
129
144
  });
130
145
  };
131
146
 
132
- document.addEventListener("click", (ev) => {
133
- return handleDocumentEvent(ev, [
134
- Rails.linkClickSelector,
135
- Rails.buttonClickSelector,
136
- Rails.formInputClickSelector
137
- ]);
138
- });
139
- document.addEventListener("change", (ev) => {
140
- return handleDocumentEvent(ev, [Rails.inputChangeSelector]);
141
- });
142
- document.addEventListener("submit", (ev) => {
143
- return handleDocumentEvent(ev, [Rails.formSubmitSelector]);
144
- });
147
+ // Note that this needs to be run **before** Rails.start()
148
+ export const initializeConfirm = () => {
149
+ document.addEventListener("click", (ev) => {
150
+ return handleDocumentEvent(ev, [
151
+ Rails.linkClickSelector,
152
+ Rails.buttonClickSelector,
153
+ Rails.formInputClickSelector
154
+ ]);
155
+ });
156
+ document.addEventListener("change", (ev) => {
157
+ return handleDocumentEvent(ev, [Rails.inputChangeSelector]);
158
+ });
159
+ document.addEventListener("submit", (ev) => {
160
+ return handleDocumentEvent(ev, [Rails.formSubmitSelector]);
161
+ });
145
162
 
146
- // This is needed for the confirm dialog to work with Foundation Abide.
147
- // Abide registers its own submit click listeners since Foundation 5.6.x
148
- // which will be handled before the document listeners above. This would
149
- // break the custom confirm functionality when used with Foundation Abide.
150
- document.addEventListener("DOMContentLoaded", function() {
151
- $(Rails.formInputClickSelector).on("click.confirm", (ev) => {
152
- handleConfirm(ev, getMatchingEventTarget(ev, Rails.formInputClickSelector));
163
+ // This is needed for the confirm dialog to work with Foundation Abide.
164
+ // Abide registers its own submit click listeners since Foundation 5.6.x
165
+ // which will be handled before the document listeners above. This would
166
+ // break the custom confirm functionality when used with Foundation Abide.
167
+ document.addEventListener("DOMContentLoaded", function() {
168
+ $(Rails.formInputClickSelector).on("click.confirm", (ev) => {
169
+ handleConfirm(ev, getMatchingEventTarget(ev, Rails.formInputClickSelector));
170
+ });
153
171
  });
154
- });
172
+ };
173
+
174
+ export default runConfirm;
@@ -13,11 +13,13 @@ export default function generateDatePicker(input, row, formats) {
13
13
  const date = document.createElement("input");
14
14
  date.setAttribute("id", `${input.id}_date`);
15
15
  date.setAttribute("type", "text");
16
+ date.setAttribute("aria-label", input.dataset.dateLabel);
16
17
 
17
18
  const calendar = document.createElement("button");
18
19
  calendar.innerHTML = icon("calendar-line");
19
20
  calendar.setAttribute("class", "datepicker__calendar-button");
20
21
  calendar.setAttribute("type", "button");
22
+ calendar.setAttribute("aria-label", input.dataset.buttonDateLabel);
21
23
 
22
24
  dateColumn.appendChild(date);
23
25
  dateColumn.appendChild(calendar);
@@ -13,11 +13,13 @@ export default function generateTimePicker(input, row, formats) {
13
13
  const time = document.createElement("input");
14
14
  time.setAttribute("id", `${input.id}_time`);
15
15
  time.setAttribute("type", "text");
16
+ time.setAttribute("aria-label", input.dataset.timeLabel);
16
17
 
17
18
  const clock = document.createElement("button");
18
19
  clock.innerHTML = icon("time-line")
19
20
  clock.setAttribute("class", "datepicker__clock-button");
20
21
  clock.setAttribute("type", "button");
22
+ clock.setAttribute("aria-label", input.dataset.buttonTimeLabel);
21
23
 
22
24
  timeColumn.appendChild(time);
23
25
  timeColumn.appendChild(clock);