decidim-initiatives 0.30.1 → 0.31.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +46 -9
  3. data/app/cells/decidim/initiatives/content_blocks/highlighted_initiatives_settings_form/show.erb +7 -2
  4. data/app/cells/decidim/initiatives/initiative_g_cell.rb +5 -1
  5. data/app/commands/decidim/initiatives/admin/publish_initiative.rb +1 -5
  6. data/app/commands/decidim/initiatives/admin/update_initiative.rb +1 -2
  7. data/app/commands/decidim/initiatives/create_initiative.rb +0 -1
  8. data/app/commands/decidim/initiatives/update_initiative.rb +1 -3
  9. data/app/commands/decidim/initiatives/vote_initiative.rb +1 -11
  10. data/app/controllers/concerns/decidim/initiatives/has_signature_workflow.rb +36 -0
  11. data/app/controllers/concerns/decidim/initiatives/needs_initiative.rb +1 -12
  12. data/app/controllers/decidim/initiatives/admin/initiatives_controller.rb +2 -2
  13. data/app/controllers/decidim/initiatives/admin/initiatives_settings_controller.rb +1 -1
  14. data/app/controllers/decidim/initiatives/admin/initiatives_type_scopes_controller.rb +2 -2
  15. data/app/controllers/decidim/initiatives/admin/initiatives_types_controller.rb +2 -2
  16. data/app/controllers/decidim/initiatives/committee_requests_controller.rb +10 -2
  17. data/app/controllers/decidim/initiatives/create_initiative_controller.rb +84 -18
  18. data/app/controllers/decidim/initiatives/initiative_signatures_controller.rb +133 -42
  19. data/app/controllers/decidim/initiatives/initiative_votes_controller.rb +3 -2
  20. data/app/controllers/decidim/initiatives/initiatives_controller.rb +21 -2
  21. data/app/forms/decidim/initiatives/admin/initiative_form.rb +0 -1
  22. data/app/forms/decidim/initiatives/initiative_form.rb +0 -3
  23. data/app/helpers/decidim/initiatives/application_helper.rb +2 -0
  24. data/app/helpers/decidim/initiatives/initiatives_helper.rb +0 -1
  25. data/app/models/decidim/initiative.rb +7 -31
  26. data/app/models/decidim/initiatives_committee_member.rb +1 -1
  27. data/app/models/decidim/initiatives_type.rb +5 -2
  28. data/app/models/decidim/initiatives_vote.rb +2 -2
  29. data/app/packs/entrypoints/decidim_initiatives.js +1 -1
  30. data/app/packs/entrypoints/decidim_initiatives_admin.scss +1 -1
  31. data/app/packs/src/decidim/initiatives/admin/initiatives_types.js +2 -11
  32. data/app/packs/src/decidim/initiatives/admin/invite_users.js +1 -1
  33. data/app/packs/src/decidim/initiatives/application.js +1 -1
  34. data/app/packs/src/decidim/initiatives/check_code.js +114 -0
  35. data/app/packs/src/decidim/initiatives/initiative_creation_wizard.js +16 -0
  36. data/app/packs/src/decidim/initiatives/scoped_type.js +1 -1
  37. data/app/packs/stylesheets/initiatives.scss +16 -2
  38. data/app/permissions/decidim/initiatives/admin/permissions.rb +4 -7
  39. data/app/permissions/decidim/initiatives/permissions.rb +26 -16
  40. data/app/presenters/decidim/initiative_presenter.rb +12 -6
  41. data/app/presenters/decidim/initiatives/admin_log/initiative_presenter.rb +1 -2
  42. data/app/queries/decidim/initiatives/initiatives_stats_followers_count.rb +14 -0
  43. data/app/queries/decidim/initiatives/initiatives_stats_participants_count.rb +14 -0
  44. data/app/serializers/decidim/initiatives/open_data_initiative_serializer.rb +0 -1
  45. data/app/services/decidim/initiatives/data_encryptor.rb +1 -1
  46. data/app/services/decidim/initiatives/legacy_signature_handler.rb +25 -0
  47. data/app/services/decidim/initiatives/progress_notifier.rb +1 -7
  48. data/app/services/decidim/initiatives/signature_handler.rb +248 -0
  49. data/app/services/decidim/initiatives/status_change_notifier.rb +1 -7
  50. data/app/views/decidim/initiatives/admin/committee_requests/index.html.erb +29 -11
  51. data/app/views/decidim/initiatives/admin/exports/_dropdown.html.erb +17 -20
  52. data/app/views/decidim/initiatives/admin/initiatives/_form.html.erb +7 -13
  53. data/app/views/decidim/initiatives/admin/initiatives/_initiative_attachments.erb +2 -2
  54. data/app/views/decidim/initiatives/admin/initiatives/index.html.erb +76 -47
  55. data/app/views/decidim/initiatives/admin/initiatives_types/_form.html.erb +13 -21
  56. data/app/views/decidim/initiatives/admin/initiatives_types/_initiative_type_scopes.html.erb +28 -12
  57. data/app/views/decidim/initiatives/admin/initiatives_types/index.html.erb +33 -15
  58. data/app/views/decidim/initiatives/create_initiative/_committee_member.html.erb +27 -0
  59. data/app/views/decidim/initiatives/create_initiative/_return_to_initiatives_button.html.erb +3 -0
  60. data/app/views/decidim/initiatives/create_initiative/_send_to_technical_validation_button.html.erb +10 -0
  61. data/app/views/decidim/initiatives/create_initiative/_share_committee_link.html.erb +5 -1
  62. data/app/views/decidim/initiatives/create_initiative/fill_data.html.erb +7 -11
  63. data/app/views/decidim/initiatives/create_initiative/finish.html.erb +16 -13
  64. data/app/views/decidim/initiatives/create_initiative/promotal_committee.html.erb +33 -6
  65. data/app/views/decidim/initiatives/create_initiative/select_initiative_type.html.erb +40 -26
  66. data/app/views/decidim/initiatives/initiative_signatures/_sms_code_form.html.erb +22 -0
  67. data/app/views/decidim/initiatives/initiative_signatures/_sms_phone_number_form.html.erb +13 -0
  68. data/app/views/decidim/initiatives/initiative_signatures/fill_personal_data.html.erb +23 -22
  69. data/app/views/decidim/initiatives/initiative_signatures/finish.html.erb +17 -5
  70. data/app/views/decidim/initiatives/initiative_signatures/sms_code.html.erb +6 -8
  71. data/app/views/decidim/initiatives/initiative_signatures/sms_phone_number.html.erb +3 -8
  72. data/app/views/decidim/initiatives/initiative_signatures/update_buttons_and_counters.js.erb +3 -14
  73. data/app/views/decidim/initiatives/initiative_votes/update_buttons_and_counters.js.erb +3 -14
  74. data/app/views/decidim/initiatives/initiatives/_committee_members.html.erb +1 -1
  75. data/app/views/decidim/initiatives/initiatives/_form.html.erb +1 -3
  76. data/app/views/decidim/initiatives/initiatives/_new_initiative_button.html.erb +10 -3
  77. data/app/views/decidim/initiatives/initiatives/_pending_initiatives.html.erb +5 -0
  78. data/app/views/decidim/initiatives/initiatives/index.html.erb +8 -0
  79. data/app/views/decidim/initiatives/initiatives/show.html.erb +2 -2
  80. data/app/views/layouts/decidim/_initiative_signature_creation_header.html.erb +20 -2
  81. data/app/views/layouts/decidim/admin/_manage_initiatives.html.erb +1 -1
  82. data/app/views/layouts/decidim/initiative_signature_creation.html.erb +3 -1
  83. data/config/assets.rb +2 -2
  84. data/config/locales/ar.yml +0 -45
  85. data/config/locales/bg.yml +0 -54
  86. data/config/locales/ca-IT.yml +99 -51
  87. data/config/locales/ca.yml +99 -51
  88. data/config/locales/cs.yml +93 -54
  89. data/config/locales/de.yml +100 -52
  90. data/config/locales/el.yml +0 -45
  91. data/config/locales/en.yml +99 -51
  92. data/config/locales/es-MX.yml +99 -51
  93. data/config/locales/es-PY.yml +99 -51
  94. data/config/locales/es.yml +99 -51
  95. data/config/locales/eu.yml +120 -72
  96. data/config/locales/fi-plain.yml +99 -51
  97. data/config/locales/fi.yml +99 -51
  98. data/config/locales/fr-CA.yml +44 -51
  99. data/config/locales/fr.yml +44 -51
  100. data/config/locales/ga-IE.yml +0 -17
  101. data/config/locales/gl.yml +0 -41
  102. data/config/locales/hu.yml +0 -54
  103. data/config/locales/id-ID.yml +0 -40
  104. data/config/locales/is-IS.yml +0 -22
  105. data/config/locales/it.yml +0 -53
  106. data/config/locales/ja.yml +98 -49
  107. data/config/locales/lb.yml +0 -50
  108. data/config/locales/lt.yml +0 -56
  109. data/config/locales/lv.yml +0 -46
  110. data/config/locales/nl.yml +0 -47
  111. data/config/locales/no.yml +0 -53
  112. data/config/locales/pl.yml +0 -56
  113. data/config/locales/pt-BR.yml +0 -53
  114. data/config/locales/pt.yml +0 -53
  115. data/config/locales/ro-RO.yml +92 -50
  116. data/config/locales/ru.yml +0 -25
  117. data/config/locales/sk.yml +0 -43
  118. data/config/locales/sl.yml +0 -1
  119. data/config/locales/sv.yml +10 -53
  120. data/config/locales/tr-TR.yml +0 -53
  121. data/config/locales/uk.yml +0 -25
  122. data/config/locales/zh-CN.yml +0 -45
  123. data/config/locales/zh-TW.yml +0 -53
  124. data/db/migrate/20250605104500_remove_hashtag_column_initiatives.rb +7 -0
  125. data/lib/decidim/api/initiative_api_type.rb +3 -0
  126. data/lib/decidim/api/initiative_type.rb +23 -4
  127. data/lib/decidim/exporters/initiative_votes_pdf.rb +1 -1
  128. data/lib/decidim/initiatives/default_signature_authorizer.rb +17 -0
  129. data/lib/decidim/initiatives/engine.rb +17 -14
  130. data/lib/decidim/initiatives/menu.rb +1 -1
  131. data/lib/decidim/initiatives/participatory_space.rb +15 -1
  132. data/lib/decidim/initiatives/seeds.rb +1 -2
  133. data/lib/decidim/initiatives/signature_workflow_manifest.rb +176 -0
  134. data/lib/decidim/initiatives/signatures.rb +12 -0
  135. data/lib/decidim/initiatives/test/factories.rb +7 -7
  136. data/lib/decidim/initiatives/test/initiatives_signatures_test_helpers.rb +19 -0
  137. data/lib/decidim/initiatives/validatable_authorizations.rb +83 -0
  138. data/lib/decidim/initiatives/version.rb +1 -1
  139. data/lib/decidim/initiatives.rb +23 -12
  140. metadata +33 -21
  141. data/app/events/decidim/initiatives/endorse_initiative_event.rb +0 -13
  142. data/app/forms/decidim/initiatives/vote_form.rb +0 -208
  143. data/app/packs/src/decidim/initiatives/identity_selector_dialog.js +0 -14
  144. data/app/services/decidim/initiatives/pdf_signature_example.rb +0 -110
  145. data/app/views/decidim/initiatives/initiative_signatures/_wizard_steps.html.erb +0 -15
  146. data/app/views/decidim/initiatives/initiatives/_interactions.html.erb +0 -10
  147. data/app/views/layouts/decidim/_initiative_header.html.erb +0 -27
@@ -0,0 +1,248 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Initiatives
5
+ # This is the base class for signature handlers, all implementations
6
+ # should inherit from it.
7
+ # Each SignatureHandler is a form that will be used to check if the
8
+ # signature is valid or not. When it is valid the initiatives votes
9
+ # defined by the initiative type will be created for the user.
10
+ #
11
+ # Feel free to use validations to assert fields against a remote API,
12
+ # local database, or whatever.
13
+ #
14
+ # It also sets two default attributes, `user` and `initiative`.
15
+ class SignatureHandler < Form
16
+ include ValidatableAuthorizations
17
+
18
+ mimic :initiatives_vote
19
+
20
+ # The user that is trying to sign, it is initialized with the
21
+ # `current_user` from the controller.
22
+ attribute :user, Decidim::User
23
+
24
+ # The initiative to be signed
25
+ attribute :initiative, Decidim::Initiative
26
+
27
+ attribute :tos_agreement, if: :ephemeral_tos_pending?
28
+ validates :tos_agreement, presence: true, if: :ephemeral_tos_pending?
29
+ validate :tos_agreement_acceptance, if: :ephemeral_tos_pending?
30
+
31
+ attribute :transfer_status
32
+
33
+ validates :initiative, :user, presence: true
34
+ validate :uniqueness
35
+ validate :valid_metadata
36
+ validate :valid_authorized_scopes
37
+
38
+ delegate :promote_authorization_validation_errors, :authorization_handler_form_class, :ephemeral?, to: :workflow_manifest
39
+ delegate :scope, to: :initiative
40
+
41
+ # A unique ID to be implemented by the signature handler that ensures
42
+ # no duplicates are created.
43
+ def unique_id
44
+ nil
45
+ end
46
+
47
+ def encrypted_metadata
48
+ return if metadata.blank?
49
+
50
+ @encrypted_metadata ||= encryptor.encrypt(metadata)
51
+ end
52
+
53
+ # Public: Builds the list of scopes where the user is authorized to vote in. This is used when
54
+ # the initiative allows also voting on child scopes, not only the main scope.
55
+ #
56
+ # Instead of just listing the children of the main scope, we just want to select the ones that
57
+ # have been added to the InitiativeType with its voting settings.
58
+ #
59
+ def authorized_scopes
60
+ initiative.votable_initiative_type_scopes.select do |initiative_type_scope|
61
+ initiative_type_scope.global_scope? ||
62
+ initiative_type_scope.scope == user_signature_scope ||
63
+ initiative_type_scope.scope.ancestor_of?(user_signature_scope)
64
+ end.flat_map(&:scope)
65
+ end
66
+
67
+ # Public: Finds the scope the user has an authorization for, this way the user can vote
68
+ # on that scope and its parents.
69
+ #
70
+ # This is can be used to allow users that are authorized with a children
71
+ # scope to sign an initiative with a parent scope.
72
+ #
73
+ # As an example: A city (global scope) has many districts (scopes with
74
+ # parent nil), and each district has different neighbourhoods (with its
75
+ # parent as a district). If we setup the authorization handler to match
76
+ # a neighbourhood, the same authorization can be used to participate
77
+ # in district, neighbourhoods or city initiatives.
78
+ #
79
+ # Returns a Decidim::Scope.
80
+ def user_signature_scope
81
+ return if signature_scope_id.blank?
82
+
83
+ @user_signature_scope ||= signature_scope_candidates.find do |scope_candidate|
84
+ scope_candidate&.id == signature_scope_id
85
+ end
86
+ end
87
+
88
+ # Public: Builds a list of Decidim::Scopes where the user could have a
89
+ # valid authorization.
90
+ #
91
+ # If the initiative is set with a global scope (meaning the scope is nil),
92
+ # all the scopes in the organization are valid.
93
+ #
94
+ # Returns an array of Decidim::Scopes.
95
+ def signature_scope_candidates
96
+ signature_scope_candidates = [initiative.scope]
97
+ signature_scope_candidates += if initiative.scope.present?
98
+ initiative.scope.descendants
99
+ else
100
+ initiative.organization.scopes
101
+ end
102
+ signature_scope_candidates.uniq
103
+ end
104
+
105
+ # Any data that the developer would like to inject to the `metadata` field
106
+ # of a vote when it is created. Can be useful if some of the params the
107
+ # user sent with the signature form want to be persisted for future use.
108
+ #
109
+ # Returns a Hash.
110
+ def metadata
111
+ {}
112
+ end
113
+
114
+ # Params to be sent to the authorization handler. By default consists on
115
+ # the metadata hash including the signer user
116
+ def authorization_handler_params
117
+ params = metadata.merge(user:)
118
+ params = params.merge(tos_agreement:) if ephemeral_tos_pending?
119
+ params
120
+ end
121
+
122
+ # The signature_scope_id can be defined in the signature workflow to be
123
+ # used by the author scope feature
124
+ def signature_scope_id
125
+ scope.id
126
+ end
127
+
128
+ def authorization_handler
129
+ return if authorization_handler_form_class.blank?
130
+
131
+ @authorization_handler ||= authorization_handler_form_class.from_params(authorization_handler_params)
132
+ end
133
+
134
+ def signature_workflow_name
135
+ @signature_workflow_name ||= initiative&.type&.document_number_authorization_handler
136
+ end
137
+
138
+ def hash_id
139
+ return unless initiative && (unique_id || user)
140
+
141
+ @hash_id ||= Digest::SHA256.hexdigest(
142
+ [
143
+ initiative.id,
144
+ unique_id || user.id,
145
+ Rails.application.secret_key_base
146
+ ].compact.join("-")
147
+ )
148
+ end
149
+
150
+ # The attributes of the handler that should be exposed as form input when
151
+ # rendering the handler in a form.
152
+ #
153
+ # Returns an Array of Strings.
154
+ def form_attributes
155
+ attributes.except("id", "user", "initiative", "tos_agreement", "transfer_status").keys
156
+ end
157
+
158
+ # The String partial path so Rails can render the handler as a form. This
159
+ # is useful if you want to have a custom view to render the form instead of
160
+ # the default view.
161
+ #
162
+ # Example:
163
+ #
164
+ # A handler named Decidim::CensusHandler would look for its partial in:
165
+ # decidim/census/form
166
+ #
167
+ # Returns a String.
168
+ def to_partial_path
169
+ "decidim/initiatives/initiative_signatures/#{signature_workflow_name.sub(/_handler$/, "")}/form"
170
+ end
171
+
172
+ def self.requires_extra_attributes?
173
+ new.form_attributes.present?
174
+ end
175
+
176
+ def already_voted?
177
+ Decidim::InitiativesVote.exists?(author: user, initiative:)
178
+ end
179
+
180
+ private
181
+
182
+ # It is expected to validate that no other user has voted with the same
183
+ # unique_id and scope. The unique_id should be defined by the classes
184
+ # inherited from this taking a personal data attribute like a document
185
+ # number. If not defined the user id is used
186
+ def uniqueness
187
+ add_invalid_base_error if Decidim::InitiativesVote.exists?(scope:, hash_id:)
188
+ end
189
+
190
+ def valid_metadata
191
+ return if authorization_handler_errors.blank?
192
+
193
+ keys = attributes.except("tos_agreement").keys.map(&:to_sym) & authorization_handler_errors.attribute_names
194
+
195
+ return if keys.blank? && authorization_handler_errors[:base].blank?
196
+
197
+ # Promote errors
198
+ if promote_authorization_validation_errors
199
+ keys.each do |attribute|
200
+ errors.add(attribute, authorization_handler_errors[attribute])
201
+ end
202
+ end
203
+
204
+ add_invalid_base_error
205
+ end
206
+
207
+ def tos_agreement_acceptance
208
+ return if (error_message = authorization_handler_errors[:tos_agreement]).blank?
209
+
210
+ errors.add(:tos_agreement, error_message)
211
+ end
212
+
213
+ def valid_authorized_scopes
214
+ return if authorized_scopes.present?
215
+
216
+ add_invalid_base_error
217
+ end
218
+
219
+ def add_invalid_base_error
220
+ errors.delete(:base)
221
+ errors.add(:base, I18n.t("invalid_data", scope: "decidim.initiatives.initiative_signatures.fill_personal_data"))
222
+ end
223
+
224
+ def encryptor
225
+ @encryptor ||= DataEncryptor.new(secret: Decidim::Initiatives.signature_handler_encryption_secret)
226
+ end
227
+
228
+ def workflow_manifest
229
+ @workflow_manifest ||= Decidim::Initiatives::Signatures.find_workflow_manifest(signature_workflow_name) || Decidim::Initiatives::SignatureWorkflowManifest.new
230
+ end
231
+
232
+ def ephemeral_tos_pending?
233
+ return unless ephemeral? && user.ephemeral?
234
+
235
+ !user.tos_accepted?
236
+ end
237
+
238
+ def authorization_handler_errors
239
+ @authorization_handler_errors ||= if authorization_handler.blank?
240
+ ActiveModel::Errors.new(nil)
241
+ else
242
+ authorization_handler.validate
243
+ authorization_handler.errors
244
+ end
245
+ end
246
+ end
247
+ end
248
+ end
@@ -21,7 +21,7 @@ module Decidim
21
21
  # * published, discarded: Initiative authors will be notified about the
22
22
  # result of the technical validation process.
23
23
  #
24
- # * rejected, accepted: Initiative's followers and authors will be
24
+ # * rejected, accepted: Initiative authors and committee members will be
25
25
  # notified about the result of the initiative.
26
26
  def notify
27
27
  notify_initiative_creation if initiative.created?
@@ -58,12 +58,6 @@ module Decidim
58
58
  end
59
59
 
60
60
  def notify_support_result
61
- initiative.followers.each do |follower|
62
- Decidim::Initiatives::InitiativesMailer
63
- .notify_state_change(initiative, follower)
64
- .deliver_later
65
- end
66
-
67
61
  initiative.committee_members.approved.each do |committee_member|
68
62
  Decidim::Initiatives::InitiativesMailer
69
63
  .notify_state_change(initiative, committee_member.user)
@@ -20,12 +20,12 @@
20
20
  </div>
21
21
  </div>
22
22
 
23
- <div class="table-scroll mt-4">
23
+ <div class="table-stacked mt-4">
24
24
  <table class="table-list">
25
25
  <thead>
26
26
  <tr>
27
27
  <th><%= t "user", scope: "activemodel.attributes.initiatives_committee_member" %></th>
28
- <th></th>
28
+ <th><%= t "actions", scope: "decidim.admin.models.initiatives_committee_member.fields" %></th>
29
29
  </tr>
30
30
  </thead>
31
31
  <tbody>
@@ -38,17 +38,35 @@
38
38
 
39
39
  <% current_initiative.committee_members.each do |request| %>
40
40
  <tr data-id="<%= request.id %>">
41
- <td>
41
+ <td data-label="<%= t "user", scope: "activemodel.attributes.initiatives_committee_member" %>">
42
42
  <%= link_to request.user.name, "mailto:#{request.user.email}" %>
43
43
  </td>
44
- <td class="table-list__actions">
45
- <% if allowed_to? :approve, :initiative_committee_member, request: request %>
46
- <%= icon_link_to "check-line", approve_initiative_committee_request_path(current_initiative, request), t(".approve"), class: "action-icon--check" %>
47
- <% end %>
48
-
49
- <% if allowed_to? :revoke, :initiative_committee_member, request: request %>
50
- <%= icon_link_to "delete-bin-line", revoke_initiative_committee_request_path(current_initiative, request), t(".revoke"), class: "action-icon--remove", method: :delete, data: { confirm: t(".confirm_revoke") } %>
51
- <% end %>
44
+
45
+ <td class="table-list__actions" data-label="<%= t "actions", scope: "decidim.admin.models.initiatives_committee_member.fields" %>">
46
+ <button type="button" data-controller="dropdown" data-target="actions-initiative-committee-request-<%= request.id %>" aria-label="<%= t("decidim.admin.actions.actions_label", resource: request.user.name) %>">
47
+ <%= icon "more-fill", class: "text-secondary" %>
48
+ </button>
49
+
50
+ <div class="inline-block relative">
51
+ <ul id="actions-initiative-committee-request-<%= request.id %>" class="dropdown dropdown__action" aria-hidden="true">
52
+ <% if allowed_to? :approve, :initiative_committee_member, request: request %>
53
+ <li class="dropdown__item">
54
+ <%= link_to approve_initiative_committee_request_path(current_initiative, request), class: "dropdown__button" do %> <%= icon "check-line" %>
55
+ <%= t(".approve") %>
56
+ <% end %>
57
+ </li>
58
+ <% end %>
59
+
60
+ <% if allowed_to? :revoke, :initiative_committee_member, request: request %>
61
+ <li class="dropdown__item">
62
+ <%= link_to revoke_initiative_committee_request_path(current_initiative, request), method: :delete, data: { confirm: t(".confirm_revoke") }, class: "dropdown__button" do %>
63
+ <%= icon "delete-bin-line" %>
64
+ <%= t(".revoke") %>
65
+ <% end %>
66
+ </li>
67
+ <% end %>
68
+ </ul>
69
+ </div>
52
70
  </td>
53
71
  </tr>
54
72
  <% end %>
@@ -1,25 +1,22 @@
1
- <span class="exports button button__sm button__secondary" data-toggle="<%= dropdown_id(collection_ids) %>">
2
- <% if collection_ids.present? %>
3
- <%= t("actions.export-selection", scope: "decidim.admin") %>
4
- <% else %>
5
- <%= t("actions.export", scope: "decidim.admin") %>
6
- <% end %>
7
- <%= icon "arrow-down-s-line", class: "dropdown-filter-icon" %>
8
- </span>
9
- <div class="dropdown-pane"
10
- id="<%= dropdown_id(collection_ids) %>"
11
- data-dropdown
12
- data-position=bottom
13
- data-alignment=right
14
- data-auto-focus="true"
15
- data-close-on-click="true">
16
- <ul class="vertical menu add-components">
1
+ <% menu_id = "top-#{SecureRandom.uuid}" %>
2
+ <div class="relative">
3
+ <button class="button button__sm button__transparent-secondary" data-controller="dropdown" data-target="dropdown-exports-<%= menu_id %>-<%= dropdown_id(collection_ids) %>">
4
+ <% if collection_ids.present? %>
5
+ <%= t("actions.export-selection", scope: "decidim.admin") %>
6
+ <% else %>
7
+ <%= t("actions.export", scope: "decidim.admin") %>
8
+ <% end %>
9
+ <%= icon "arrow-down-s-line" %>
10
+ <%= icon "arrow-down-s-line" %>
11
+ </button>
12
+
13
+ <ul id="dropdown-exports-<%= menu_id %>-<%= dropdown_id(collection_ids) %>" class="dropdown dropdown__bottom" aria-hidden="true">
17
14
  <% %w(CSV JSON).each do |format| %>
18
- <%= link_to export_initiatives_path(format:, collection_ids:) do %>
19
- <li class="exports--format--<%= format.downcase %> exports--initiatives">
15
+ <li class="dropdown__item">
16
+ <%= link_to export_initiatives_path(format:, collection_ids:), class: "dropdown__button" do %>
20
17
  <%= t("decidim.admin.exports.export_as", name: t("decidim.initiatives.admin.exports.initiatives"), export_format: format.upcase) %>
21
- </li>
22
- <% end %>
18
+ <% end %>
19
+ </li>
23
20
  <% end %>
24
21
  </ul>
25
22
  </div>
@@ -1,5 +1,5 @@
1
1
  <div class="form__wrapper">
2
- <div class="card" data-component="accordion" id="accordion-title">
2
+ <div class="card" data-controller="accordion" id="accordion-title">
3
3
  <div class="card-divider">
4
4
  <button class="card-divider-button" data-open="true" data-controls="panel-title" type="button">
5
5
  <%= icon "arrow-right-s-line" %>
@@ -17,16 +17,10 @@
17
17
  <div class="row column">
18
18
  <%= form.translated :editor, :description, lines: 8, disabled: !allowed_to?(:update, :initiative, initiative: current_initiative), aria: { label: :description } %>
19
19
  </div>
20
-
21
- <div class="row column">
22
- <div class="columns">
23
- <%= form.text_field :hashtag, disabled: !allowed_to?(:update, :initiative, initiative: current_initiative) %>
24
- </div>
25
- </div>
26
20
  </div>
27
21
  </div>
28
22
 
29
- <div class="card" data-component="accordion" id="accordion-settings">
23
+ <div class="card" data-controller="accordion" id="accordion-settings">
30
24
  <div class="card-divider">
31
25
  <button class="card-divider-button" data-open="true" data-controls="panel-settings" type="button">
32
26
  <%= icon "arrow-right-s-line" %>
@@ -116,17 +110,17 @@
116
110
  <% end %>
117
111
  </div>
118
112
  </div>
119
- <div class="card" data-component="accordion" id="accordion-homepage_highlighted_content_banner_title">
113
+ <div class="card" data-controller="accordion" id="accordion-homepage_attachments">
120
114
  <div class="card-divider">
121
- <button class="card-divider-button" data-open="true" data-controls="panel-homepage_highlighted_content_banner_title" type="button">
115
+ <button class="card-divider-button" data-open="true" data-controls="panel-homepage_attachments" type="button">
122
116
  <%= icon "arrow-right-s-line" %>
123
- <h2 class="card-title" id="homepage_highlighted_content_banner_title">
124
- <%= t("homepage_highlighted_content_banner_title", scope: "decidim.admin.organization_appearance.form") %>
117
+ <h2 class="card-title" id="homepage_attachments">
118
+ <%= t(".attachments_title") %>
125
119
  </h2>
126
120
  </button>
127
121
  </div>
128
122
 
129
- <div id="panel-homepage_highlighted_content_banner_title" class="card-section">
123
+ <div id="panel-homepage_attachments" class="card-section">
130
124
  <div class="row">
131
125
  <% if allowed_to?(:read, :attachment, initiative: current_participatory_space) %>
132
126
  <%= render partial: "initiative_attachments", locals: { current_initiative:, current_participatory_space: } %>
@@ -31,10 +31,10 @@
31
31
  <% if allowed_to?(:update, :initiative, initiative: current_initiative) %>
32
32
  <%= aria_selected_link_to t(".edit"),
33
33
  decidim_admin_initiatives.initiative_attachments_path(current_participatory_space),
34
- class: "button button__sm button__secondary" %>
34
+ class: "button button__sm button__transparent-secondary" %>
35
35
  <%= aria_selected_link_to t(".new"),
36
36
  decidim_admin_initiatives.new_initiative_attachment_path(current_participatory_space),
37
- class: "button button__sm button__secondary" %>
37
+ class: "button button__sm button__transparent-secondary" %>
38
38
  <% else %>
39
39
  <%= link_to t(".edit"), "#", class: "button button__sm button__secondary muted disabled" %>
40
40
  <%= link_to t(".new"), "#", class: "button button__sm button__secondary muted disabled" %>
@@ -15,7 +15,7 @@
15
15
  <% link = link_to(t("button", scope:"decidim.initiatives.admin.index.initiatives_types"), new_initiatives_type_path, class: "button button__sm button__secondary mt-4") %>
16
16
  <%= cell("decidim/announcement", t("alert_html", scope:"decidim.initiatives.admin.index.initiatives_types", link:), callout_class: "alert") %>
17
17
  <% else %>
18
- <div class="table-scroll">
18
+ <div class="table-stacked">
19
19
  <table class="table-list">
20
20
  <thead>
21
21
  <tr>
@@ -31,8 +31,8 @@
31
31
  <tbody>
32
32
  <% @initiatives.each do |initiative| %>
33
33
  <tr>
34
- <td><%= initiative.id %></td>
35
- <td class="!text-left">
34
+ <td data-label="<%= t("models.initiatives.fields.id", scope: "decidim.admin") %>"><%= initiative.id %></td>
35
+ <td class="!text-left" data-label="<%= t("models.initiatives.fields.title", scope: "decidim.admin") %>">
36
36
  <% if allowed_to? :edit, :initiative, initiative: initiative %>
37
37
  <%= link_to translated_attribute(initiative.title),
38
38
  decidim_admin_initiatives.edit_initiative_path(initiative.to_param) %>
@@ -40,56 +40,85 @@
40
40
  <%= translated_attribute(initiative.title) %>
41
41
  <% end %>
42
42
  </td>
43
- <td><%= humanize_admin_state initiative.state %></td>
44
- <td><%= initiative.supports_count %>/<%= initiative.scoped_type.supports_required %></td>
45
- <td class="table-list__date"><%= l initiative.created_at, format: :short %></td>
46
- <td class="table-list__date"><%= initiative.published_at? ? l(initiative.published_at, format: :short) : "" %></td>
47
- <td class="table-list__actions">
43
+ <td data-label="<%= t("models.initiatives.fields.state", scope: "decidim.admin") %>"><%= humanize_admin_state initiative.state %></td>
44
+ <td data-label="<%= t("models.initiatives.fields.supports_count", scope: "decidim.admin") %>"><%= initiative.supports_count %>/<%= initiative.scoped_type.supports_required %></td>
45
+ <td class="table-list__date" data-label="<%= t("models.initiatives.fields.created_at", scope: "decidim.admin") %>"><%= l initiative.created_at, format: :short %></td>
46
+ <td class="table-list__date" data-label="<%= t("models.initiatives.fields.published_at", scope: "decidim.admin") %>"><%= initiative.published_at? ? l(initiative.published_at, format: :short) : "" %></td>
47
+ <td class="table-list__actions" data-label="<%= t(".actions_title") %>">
48
+ <button type="button" data-controller="dropdown" data-target="actions-initiative-<%= initiative.id %>" aria-label="<%= t("decidim.admin.actions.actions_label", resource: translated_attribute(initiative.title)) %>">
49
+ <%= icon "more-fill", class: "text-secondary" %>
50
+ </button>
48
51
 
49
- <% if allowed_to? :read, :share_tokens, current_participatory_space: initiative %>
50
- <%= icon_link_to "share-line", decidim_admin_initiatives.initiative_share_tokens_path(initiative), t("actions.share_tokens", scope: "decidim.admin"), class: "action-icon--new" %>
51
- <% else %>
52
- <span class="action-space icon"></span>
53
- <% end %>
52
+ <div class="inline-block relative">
53
+ <ul id="actions-initiative-<%= initiative.id %>" class="dropdown dropdown__action" aria-hidden="true">
54
+ <% if allowed_to? :edit, :initiative, initiative: initiative %>
55
+ <li class="dropdown__item">
56
+ <%= link_to decidim_admin_initiatives.edit_initiative_path(initiative.to_param), class: "dropdown__button" do %>
57
+ <%= icon "pencil-line" %>
58
+ <%= t("actions.edit", scope: "decidim.admin") %>
59
+ <% end %>
60
+ </li>
61
+ <% end %>
54
62
 
55
- <% if allowed_to? :edit, :initiative, initiative: initiative %>
56
- <%= icon_link_to "pencil-line",
57
- decidim_admin_initiatives.edit_initiative_path(initiative.to_param),
58
- t("actions.configure", scope: "decidim.admin"),
59
- class: "action-icon--edit" %>
60
- <% else %>
61
- <span class="action-space icon"></span>
62
- <% end %>
63
+ <hr>
63
64
 
64
- <% if allowed_to?(:answer, :initiative, initiative: initiative) %>
65
- <%= icon_link_to "chat-1-line", edit_initiative_answer_path(initiative.slug), t("actions.answer", scope: "decidim.initiatives"), class: "action-icon action-icon--answer" %>
66
- <% else %>
67
- <%= icon "chat-1-line", scope: "decidim.admin", class: "action-icon action-icon--disabled", role: "img", "aria-hidden": true %>
68
- <% end %>
65
+ <% if allowed_to?(:answer, :initiative, initiative: initiative) %>
66
+ <li class="dropdown__item">
67
+ <%= link_to edit_initiative_answer_path(initiative.slug), class: "dropdown__button" do %>
68
+ <%= icon "chat-1-line" %>
69
+ <%= t("actions.answer", scope: "decidim.initiatives") %>
70
+ <% end %>
71
+ </li>
72
+ <% else %>
73
+ <li class="dropdown__item">
74
+ <div class="dropdown__button-disabled">
75
+ <%= with_tooltip(t("actions.cannot_answer", scope: "decidim.admin")) do %>
76
+ <%= icon "chat-1-line", class: "text-gray" %>
77
+ <span><%= t("actions.answer", scope: "decidim.initiatives") %></span>
78
+ <% end %>
79
+ </div>
80
+ </li>
81
+ <% end %>
69
82
 
70
- <% if allowed_to? :print, :initiative, initiative: initiative %>
71
- <%= icon_link_to "printer-line",
72
- decidim_initiatives.print_initiative_path(initiative),
73
- t(".print"),
74
- class: "action-icon--print",
75
- target: "_blank",
76
- data: { "external-link": false } %>
77
- <% else %>
78
- <span class="action-space icon"></span>
79
- <% end %>
83
+ <% if allowed_to? :print, :initiative, initiative: initiative %>
84
+ <li class="dropdown__item">
85
+ <%= link_to decidim_initiatives.print_initiative_path(initiative), class: "dropdown__button", target: "_blank", data: { "external-link": false } do %>
86
+ <%= icon "printer-line" %>
87
+ <%= t(".print") %>
88
+ <% end %>
89
+ </li>
90
+ <% end %>
80
91
 
81
- <% if allowed_to? :preview, :initiative, initiative: initiative %>
82
- <%= icon_link_to "eye-line",
83
- decidim_initiatives.initiative_path(initiative.to_param),
84
- t(".preview"),
85
- class: "action-icon--preview",
86
- target: "_blank",
87
- data: { "external-link": false } %>
88
- <% else %>
89
- <span class="action-space icon"></span>
90
- <% end %>
92
+ <hr>
93
+
94
+ <% if allowed_to? :preview, :initiative, initiative: initiative %>
95
+ <li class="dropdown__item">
96
+ <%= link_to decidim_initiatives.initiative_path(initiative.to_param), class: "dropdown__button", target: "_blank", data: { "external-link": false } do %>
97
+ <%= icon "eye-line" %>
98
+ <%= t(".preview") %>
99
+ <% end %>
100
+ </li>
101
+ <% end %>
102
+
103
+ <% if allowed_to? :read, :share_token, current_participatory_space: initiative %>
104
+ <li class="dropdown__item">
105
+ <%= link_to decidim_admin_initiatives.initiative_share_tokens_path(initiative), class: "dropdown__button" do %>
106
+ <%= icon "share-line" %>
107
+ <%= t("actions.share_tokens", scope: "decidim.admin") %>
108
+ <% end %>
109
+ </li>
110
+
111
+ <hr>
112
+ <% end %>
113
+
114
+ <% if (link = free_resource_permissions_link(initiative)) %>
115
+ <li class="dropdown__item">
116
+ <%= link %>
117
+ </li>
118
+ <% end %>
119
+ </ul>
120
+ </div>
91
121
 
92
- <%= free_resource_permissions_link(initiative) || content_tag(:span, nil, class: "action-space icon") %>
93
122
  </td>
94
123
  </tr>
95
124
  <% end %>