decidim-proposals 0.29.3 → 0.30.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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/proposals/highlighted_proposals_for_component/show.erb +13 -1
  3. data/app/cells/decidim/proposals/highlighted_proposals_for_component_cell.rb +1 -1
  4. data/app/cells/decidim/proposals/participatory_text_proposal_cell.rb +1 -1
  5. data/app/cells/decidim/proposals/proposal_g/show.erb +13 -0
  6. data/app/cells/decidim/proposals/proposal_g_cell.rb +13 -0
  7. data/app/cells/decidim/proposals/proposal_history_cell.rb +107 -0
  8. data/app/cells/decidim/proposals/proposal_l/show.erb +37 -0
  9. data/app/cells/decidim/proposals/proposal_l_cell.rb +9 -0
  10. data/app/cells/decidim/proposals/proposal_metadata_cell.rb +2 -2
  11. data/app/cells/decidim/proposals/proposal_vote/show.erb +75 -0
  12. data/app/cells/decidim/proposals/proposal_vote_cell.rb +43 -0
  13. data/app/commands/decidim/proposals/accept_coauthorship.rb +62 -0
  14. data/app/commands/decidim/proposals/admin/assign_proposals_to_valuator.rb +14 -0
  15. data/app/commands/decidim/proposals/admin/create_proposal.rb +6 -14
  16. data/app/commands/decidim/proposals/admin/create_proposal_note.rb +20 -11
  17. data/app/commands/decidim/proposals/admin/import_proposals.rb +60 -7
  18. data/app/commands/decidim/proposals/admin/merge_proposals.rb +2 -2
  19. data/app/commands/decidim/proposals/admin/proposal_notes_methods.rb +48 -0
  20. data/app/commands/decidim/proposals/admin/reply_proposal_note.rb +92 -0
  21. data/app/commands/decidim/proposals/admin/split_proposals.rb +2 -2
  22. data/app/commands/decidim/proposals/admin/update_proposal.rb +10 -16
  23. data/app/commands/decidim/proposals/admin/update_proposal_taxonomies.rb +34 -0
  24. data/app/commands/decidim/proposals/cancel_coauthorship.rb +32 -0
  25. data/app/commands/decidim/proposals/create_collaborative_draft.rb +1 -2
  26. data/app/commands/decidim/proposals/create_proposal.rb +1 -2
  27. data/app/commands/decidim/proposals/invite_coauthor.rb +45 -0
  28. data/app/commands/decidim/proposals/publish_collaborative_draft.rb +1 -2
  29. data/app/commands/decidim/proposals/reject_coauthorship.rb +54 -0
  30. data/app/commands/decidim/proposals/update_collaborative_draft.rb +1 -2
  31. data/app/commands/decidim/proposals/update_proposal.rb +1 -2
  32. data/app/controllers/concerns/decidim/proposals/admin/filterable.rb +5 -1
  33. data/app/controllers/concerns/decidim/proposals/admin/needs_interpolations.rb +40 -0
  34. data/app/controllers/decidim/proposals/admin/proposal_answers_controller.rb +46 -5
  35. data/app/controllers/decidim/proposals/admin/proposal_notes_controller.rb +18 -0
  36. data/app/controllers/decidim/proposals/admin/proposals_controller.rb +41 -85
  37. data/app/controllers/decidim/proposals/admin/proposals_imports_controller.rb +2 -2
  38. data/app/controllers/decidim/proposals/collaborative_drafts_controller.rb +2 -4
  39. data/app/controllers/decidim/proposals/invite_coauthors_controller.rb +87 -0
  40. data/app/controllers/decidim/proposals/proposals_controller.rb +7 -32
  41. data/app/controllers/decidim/proposals/versions_controller.rb +1 -1
  42. data/app/events/decidim/proposals/accepted_coauthorship_event.rb +8 -0
  43. data/app/events/decidim/proposals/admin/proposal_assigned_to_valuator_event.rb +27 -0
  44. data/app/events/decidim/proposals/admin/proposal_note_created_event.rb +5 -0
  45. data/app/events/decidim/proposals/coauthor_accepted_invite_event.rb +49 -0
  46. data/app/events/decidim/proposals/coauthor_invited_event.rb +45 -0
  47. data/app/events/decidim/proposals/coauthor_rejected_invite_event.rb +8 -0
  48. data/app/events/decidim/proposals/rejected_coauthorship_event.rb +8 -0
  49. data/app/events/decidim/proposals/update_proposal_taxonomies_event.rb +9 -0
  50. data/app/forms/decidim/proposals/admin/proposal_answer_form.rb +16 -0
  51. data/app/forms/decidim/proposals/admin/proposal_base_form.rb +3 -31
  52. data/app/forms/decidim/proposals/admin/proposal_form.rb +11 -6
  53. data/app/forms/decidim/proposals/admin/proposals_import_form.rb +0 -5
  54. data/app/forms/decidim/proposals/collaborative_draft_form.rb +0 -8
  55. data/app/forms/decidim/proposals/proposal_form.rb +5 -32
  56. data/app/helpers/decidim/proposals/admin/proposal_bulk_actions_helper.rb +25 -0
  57. data/app/helpers/decidim/proposals/admin/proposals_helper.rb +0 -1
  58. data/app/helpers/decidim/proposals/application_helper.rb +23 -15
  59. data/app/helpers/decidim/proposals/collaborative_draft_helper.rb +7 -7
  60. data/app/helpers/decidim/proposals/map_helper.rb +0 -18
  61. data/app/helpers/decidim/proposals/proposal_votes_helper.rb +15 -2
  62. data/app/helpers/decidim/proposals/proposals_helper.rb +3 -1
  63. data/app/jobs/decidim/proposals/admin/proposal_answer_job.rb +20 -0
  64. data/app/models/decidim/proposals/collaborative_draft.rb +13 -3
  65. data/app/models/decidim/proposals/proposal.rb +71 -5
  66. data/app/models/decidim/proposals/proposal_note.rb +11 -0
  67. data/app/models/decidim/proposals/proposal_state.rb +1 -1
  68. data/app/packs/entrypoints/decidim_proposals.js +1 -0
  69. data/app/packs/entrypoints/decidim_proposals_geocoding.js +2 -0
  70. data/app/packs/src/decidim/proposals/admin/proposals.js +16 -1
  71. data/app/packs/src/decidim/proposals/exit_handler.js +73 -0
  72. data/app/packs/stylesheets/decidim/proposals/proposals.scss +248 -3
  73. data/app/permissions/decidim/proposals/admin/permissions.rb +2 -5
  74. data/app/permissions/decidim/proposals/permissions.rb +42 -0
  75. data/app/presenters/decidim/proposals/admin_log/proposal_presenter.rb +1 -1
  76. data/app/presenters/decidim/proposals/proposal_presenter.rb +1 -1
  77. data/app/queries/decidim/proposals/filtered_proposals.rb +2 -2
  78. data/app/queries/decidim/proposals/metrics/accepted_proposals_metric_manage.rb +2 -2
  79. data/app/queries/decidim/proposals/metrics/endorsements_metric_manage.rb +10 -10
  80. data/app/queries/decidim/proposals/metrics/proposal_followers_metric_measure.rb +4 -4
  81. data/app/queries/decidim/proposals/metrics/proposal_participants_metric_measure.rb +6 -6
  82. data/app/queries/decidim/proposals/metrics/proposals_metric_manage.rb +6 -6
  83. data/app/queries/decidim/proposals/metrics/votes_metric_manage.rb +6 -6
  84. data/app/services/decidim/proposals/proposal_builder.rb +1 -2
  85. data/app/views/decidim/proposals/admin/proposal_notes/_form.html.erb +3 -3
  86. data/app/views/decidim/proposals/admin/proposal_notes/_proposal_note.html.erb +28 -0
  87. data/app/views/decidim/proposals/admin/proposal_notes/_proposal_note_reply.html.erb +9 -0
  88. data/app/views/decidim/proposals/admin/proposal_notes/_proposal_notes.html.erb +4 -28
  89. data/app/views/decidim/proposals/admin/proposal_states/_form.html.erb +1 -1
  90. data/app/views/decidim/proposals/admin/proposals/_actions.html.erb +21 -0
  91. data/app/views/decidim/proposals/admin/proposals/_bulk-actions.html.erb +3 -2
  92. data/app/views/decidim/proposals/admin/proposals/_form.html.erb +17 -24
  93. data/app/views/decidim/proposals/admin/proposals/_proposal-tr.html.erb +12 -28
  94. data/app/views/decidim/proposals/admin/proposals/_proposals-thead.html.erb +45 -0
  95. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_apply_answer_template.html.erb +22 -0
  96. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_dropdown.html.erb +15 -11
  97. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_taxonomy_change.html.erb +23 -0
  98. data/app/views/decidim/proposals/admin/proposals/index.html.erb +17 -48
  99. data/app/views/decidim/proposals/admin/proposals/manage_trash.html.erb +18 -0
  100. data/app/views/decidim/proposals/admin/proposals/publish_answers.js.erb +1 -1
  101. data/app/views/decidim/proposals/admin/proposals/show.html.erb +14 -26
  102. data/app/views/decidim/proposals/admin/proposals/update_attribute.js.erb +1 -1
  103. data/app/views/decidim/proposals/admin/proposals_imports/new.html.erb +0 -3
  104. data/app/views/decidim/proposals/collaborative_drafts/_collaborative_actions.html.erb +9 -0
  105. data/app/views/decidim/proposals/collaborative_drafts/_collaborative_draft_aside.html.erb +0 -15
  106. data/app/views/decidim/proposals/collaborative_drafts/_edit_form_fields.html.erb +4 -6
  107. data/app/views/decidim/proposals/collaborative_drafts/index.html.erb +6 -2
  108. data/app/views/decidim/proposals/collaborative_drafts/show.html.erb +27 -11
  109. data/app/views/decidim/proposals/proposal_votes/update_buttons_and_counters.js.erb +29 -9
  110. data/app/views/decidim/proposals/proposals/_actions.html.erb +4 -7
  111. data/app/views/decidim/proposals/proposals/_edit_form_fields.html.erb +17 -22
  112. data/app/views/decidim/proposals/proposals/_exit_modal.html.erb +17 -0
  113. data/app/views/decidim/proposals/proposals/_notification_alert_box.html.erb +1 -0
  114. data/app/views/decidim/proposals/proposals/_proposal_actions.html.erb +19 -0
  115. data/app/views/decidim/proposals/proposals/_proposal_aside.html.erb +9 -32
  116. data/app/views/decidim/proposals/proposals/_proposal_voting_rules.html.erb +33 -0
  117. data/app/views/decidim/proposals/proposals/_proposals.html.erb +1 -1
  118. data/app/views/decidim/proposals/proposals/_remaining_votes_count.html.erb +2 -2
  119. data/app/views/decidim/proposals/proposals/_remaining_votes_notification.html.erb +12 -0
  120. data/app/views/decidim/proposals/proposals/_update_proposal_voting_rules.html.erb +6 -0
  121. data/app/views/decidim/proposals/proposals/_vote_button.html.erb +12 -8
  122. data/app/views/decidim/proposals/proposals/_votes_count.html.erb +2 -1
  123. data/app/views/decidim/proposals/proposals/_voting_rules.html.erb +1 -7
  124. data/app/views/decidim/proposals/proposals/index.html.erb +3 -18
  125. data/app/views/decidim/proposals/proposals/index.js.erb +1 -1
  126. data/app/views/decidim/proposals/proposals/participatory_texts/_proposal_vote_button.html.erb +3 -1
  127. data/app/views/decidim/proposals/proposals/show.html.erb +35 -15
  128. data/config/locales/ar.yml +15 -73
  129. data/config/locales/bg.yml +12 -89
  130. data/config/locales/bs-BA.yml +2 -13
  131. data/config/locales/ca.yml +209 -84
  132. data/config/locales/cs.yml +210 -81
  133. data/config/locales/de.yml +213 -89
  134. data/config/locales/el.yml +12 -84
  135. data/config/locales/en.yml +206 -81
  136. data/config/locales/es-MX.yml +213 -88
  137. data/config/locales/es-PY.yml +213 -88
  138. data/config/locales/es.yml +214 -89
  139. data/config/locales/eu.yml +288 -164
  140. data/config/locales/fi-plain.yml +213 -87
  141. data/config/locales/fi.yml +213 -87
  142. data/config/locales/fr-CA.yml +116 -97
  143. data/config/locales/fr.yml +116 -97
  144. data/config/locales/ga-IE.yml +1 -21
  145. data/config/locales/gl.yml +8 -45
  146. data/config/locales/hu.yml +12 -68
  147. data/config/locales/id-ID.yml +9 -43
  148. data/config/locales/is-IS.yml +0 -19
  149. data/config/locales/it.yml +11 -77
  150. data/config/locales/ja.yml +165 -106
  151. data/config/locales/lt.yml +13 -85
  152. data/config/locales/lv.yml +10 -52
  153. data/config/locales/nl.yml +10 -59
  154. data/config/locales/no.yml +10 -44
  155. data/config/locales/pl.yml +11 -88
  156. data/config/locales/pt-BR.yml +9 -74
  157. data/config/locales/pt.yml +10 -56
  158. data/config/locales/ro-RO.yml +12 -72
  159. data/config/locales/ru.yml +0 -23
  160. data/config/locales/sk.yml +10 -52
  161. data/config/locales/sr-CS.yml +2 -14
  162. data/config/locales/sv.yml +127 -86
  163. data/config/locales/tr-TR.yml +10 -53
  164. data/config/locales/uk.yml +0 -23
  165. data/config/locales/zh-CN.yml +10 -53
  166. data/config/locales/zh-TW.yml +12 -86
  167. data/db/migrate/20171220084719_add_published_at_to_proposals.rb +1 -1
  168. data/db/migrate/20181016132225_add_organization_as_author.rb +1 -1
  169. data/db/migrate/20200120215928_move_proposal_endorsements_to_core_endorsements.rb +1 -1
  170. data/db/migrate/20200827154156_add_commentable_counter_cache_to_proposals.rb +3 -3
  171. data/db/migrate/20210310102839_add_followable_counter_cache_to_proposals.rb +1 -1
  172. data/db/migrate/20240110203504_create_default_proposal_states.rb +1 -1
  173. data/db/migrate/20240404202756_add_valuation_assignments_count_to_decidim_proposals_proposals.rb +1 -1
  174. data/db/migrate/20240617091140_add_email_on_assigned_proposals_to_users.rb +7 -0
  175. data/db/migrate/20240617170052_add_parent_relation_to_decidim_proposal_notes.rb +7 -0
  176. data/db/migrate/20240828103755_add_deleted_at_to_decidim_proposals_proposals.rb +8 -0
  177. data/decidim-proposals.gemspec +1 -1
  178. data/lib/decidim/api/functions/proposal_finder_helper.rb +12 -0
  179. data/lib/decidim/api/functions/proposal_list_helper.rb +12 -0
  180. data/lib/decidim/api/proposal_type.rb +17 -25
  181. data/lib/decidim/api/proposals_type.rb +4 -19
  182. data/lib/decidim/proposals/admin_engine.rb +12 -3
  183. data/lib/decidim/proposals/admin_filter.rb +3 -6
  184. data/lib/decidim/proposals/component.rb +4 -5
  185. data/lib/decidim/proposals/download_your_data_proposal_serializer.rb +15 -0
  186. data/lib/decidim/proposals/engine.rb +5 -0
  187. data/lib/decidim/proposals/import/proposal_creator.rb +4 -4
  188. data/lib/decidim/proposals/proposal_serializer.rb +12 -29
  189. data/lib/decidim/proposals/seeds.rb +21 -17
  190. data/lib/decidim/proposals/test/factories.rb +2 -1
  191. data/lib/decidim/proposals/version.rb +1 -1
  192. data/lib/decidim/proposals.rb +4 -0
  193. data/lib/tasks/proposals/upgrade/decidim_proposals_upgrade_tasks.rake +0 -22
  194. metadata +65 -34
  195. data/app/commands/decidim/proposals/admin/update_proposal_category.rb +0 -70
  196. data/app/commands/decidim/proposals/admin/update_proposal_scope.rb +0 -75
  197. data/app/events/decidim/proposals/admin/update_proposal_category_event.rb +0 -11
  198. data/app/events/decidim/proposals/admin/update_proposal_scope_event.rb +0 -11
  199. data/app/jobs/decidim/proposals/admin/import_proposals_job.rb +0 -91
  200. data/app/mailers/decidim/proposals/admin/import_proposals_mailer.rb +0 -30
  201. data/app/views/decidim/proposals/admin/import_proposals_mailer/notify_failure.html.erb +0 -1
  202. data/app/views/decidim/proposals/admin/import_proposals_mailer/notify_success.html.erb +0 -2
  203. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_recategorize.html.erb +0 -15
  204. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_scope-change.html.erb +0 -21
  205. data/app/views/decidim/proposals/collaborative_drafts/_actions.html.erb +0 -7
  206. data/config/locales/ca-IT.yml +0 -945
@@ -7,6 +7,7 @@ module Decidim
7
7
  include Decidim::Resourceable
8
8
  include Decidim::Coauthorable
9
9
  include Decidim::HasComponent
10
+ include Decidim::Taxonomizable
10
11
  include Decidim::ScopableResource
11
12
  include Decidim::HasReference
12
13
  include Decidim::HasCategory
@@ -28,6 +29,7 @@ module Decidim
28
29
  include Decidim::TranslatableResource
29
30
  include Decidim::TranslatableAttributes
30
31
  include Decidim::FilterableResource
32
+ include Decidim::SoftDeletable
31
33
 
32
34
  def assign_state(token)
33
35
  proposal_state = Decidim::Proposals::ProposalState.where(component:, token:).first
@@ -301,6 +303,11 @@ module Decidim
301
303
  state == "evaluating"
302
304
  end
303
305
 
306
+ # Public: Overrides the `reported_content_url` Reportable concern method.
307
+ def reported_content_url
308
+ ResourceLocatorPresenter.new(self).url
309
+ end
310
+
304
311
  # Returns the presenter for this author, to be used in the views.
305
312
  # Required by ResourceRenderer.
306
313
  def presenter
@@ -398,13 +405,21 @@ module Decidim
398
405
  end
399
406
 
400
407
  def self.ransackable_scopes(_auth_object = nil)
401
- [:with_any_origin, :with_any_state, :state_eq, :voted_by, :coauthored_by, :related_to, :with_any_scope, :with_any_category, :valuator_role_ids_has]
408
+ [:with_any_origin, :with_any_state, :state_eq, :voted_by, :coauthored_by, :related_to, :with_any_taxonomies, :valuator_role_ids_has]
402
409
  end
403
410
 
404
411
  # Create i18n ransackers for :title and :body.
405
412
  # Create the :search_text ransacker alias for searching from both of these.
406
413
  ransacker_i18n_multi :search_text, [:title, :body]
407
414
 
415
+ def self.ransackable_attributes(_auth_object = nil)
416
+ %w(id_string search_text title body is_emendation comments_count proposal_votes_count published_at proposal_notes_count)
417
+ end
418
+
419
+ def self.ransackable_associations(_auth_object = nil)
420
+ %w(taxonomies proposal_state)
421
+ end
422
+
408
423
  ransacker :state_published do
409
424
  Arel.sql("CASE
410
425
  WHEN EXISTS (
@@ -450,7 +465,7 @@ module Decidim
450
465
  end
451
466
 
452
467
  def self.export_serializer
453
- Decidim::Proposals::ProposalSerializer
468
+ Decidim::Proposals::DownloadYourDataProposalSerializer
454
469
  end
455
470
 
456
471
  def self.download_your_data_images(user)
@@ -467,8 +482,18 @@ module Decidim
467
482
  return true if draft?
468
483
  return true if component.settings.proposal_edit_time == "infinite"
469
484
 
470
- limit = updated_at + component.settings.proposal_edit_before_minutes.minutes
471
- Time.current < limit
485
+ time_value, time_unit = component.settings.edit_time
486
+
487
+ limit_time = case time_unit
488
+ when "minutes"
489
+ updated_at + time_value.minutes
490
+ when "hours"
491
+ updated_at + time_value.hours
492
+ else
493
+ updated_at + time_value.days
494
+ end
495
+
496
+ Time.current < limit_time
472
497
  end
473
498
 
474
499
  def process_amendment_state_change!
@@ -481,10 +506,51 @@ module Decidim
481
506
  end
482
507
  end
483
508
 
509
+ def user_has_actions?(user)
510
+ return false if authors.include?(user)
511
+ return false if user&.blocked?
512
+ return false if user&.deleted?
513
+ return false unless user&.confirmed?
514
+
515
+ true
516
+ end
517
+
518
+ def actions_for_comment(comment, current_user)
519
+ return if comment.commentable != self
520
+ return unless authors.include?(current_user)
521
+ return unless user_has_actions?(comment.author)
522
+
523
+ if coauthor_invitations_for(comment.author).any?
524
+ [
525
+ {
526
+ label: I18n.t("decidim.proposals.actions.cancel_coauthor_invitation"),
527
+ url: EngineRouter.main_proxy(component).cancel_proposal_invite_coauthors_path(proposal_id: id, id: comment.author.id),
528
+ icon: "user-forbid-line",
529
+ method: :delete,
530
+ data: { confirm: I18n.t("decidim.proposals.actions.cancel_coauthor_invitation_confirm") }
531
+ }
532
+ ]
533
+ else
534
+ [
535
+ {
536
+ label: I18n.t("decidim.proposals.actions.mark_as_coauthor"),
537
+ url: EngineRouter.main_proxy(component).proposal_invite_coauthors_path(proposal_id: id, id: comment.author.id),
538
+ icon: "user-add-line",
539
+ method: :post,
540
+ data: { confirm: I18n.t("decidim.proposals.actions.mark_as_coauthor_confirm") }
541
+ }
542
+ ]
543
+ end
544
+ end
545
+
546
+ def coauthor_invitations_for(user)
547
+ Decidim::Notification.where(event_class: "Decidim::Proposals::CoauthorInvitedEvent", resource: self, user:)
548
+ end
549
+
484
550
  private
485
551
 
486
552
  def copied_from_other_component?
487
- linked_resources(:proposals, "copied_from_component").any?
553
+ linked_resources(:proposals, %w(splitted_from_component merged_from_component copied_from_component)).any?
488
554
  end
489
555
  end
490
556
  end
@@ -9,12 +9,23 @@ module Decidim
9
9
 
10
10
  belongs_to :proposal, foreign_key: "decidim_proposal_id", class_name: "Decidim::Proposals::Proposal", counter_cache: true
11
11
  belongs_to :author, foreign_key: "decidim_author_id", class_name: "Decidim::User"
12
+ has_many :replies, foreign_key: "parent_id", class_name: "Decidim::Proposals::ProposalNote", inverse_of: :parent, dependent: :destroy
13
+ belongs_to :parent, class_name: "Decidim::Proposals::ProposalNote", inverse_of: :replies, optional: true
12
14
 
15
+ scope :not_reply, -> { where(parent_id: nil) }
13
16
  default_scope { order(created_at: :asc) }
14
17
 
15
18
  def self.log_presenter_class_for(_log)
16
19
  Decidim::Proposals::AdminLog::ProposalNotePresenter
17
20
  end
21
+
22
+ def reply?
23
+ parent.present?
24
+ end
25
+
26
+ def formatted_body
27
+ Decidim::ContentProcessor.render_without_format(body)
28
+ end
18
29
  end
19
30
  end
20
31
  end
@@ -38,7 +38,7 @@ module Decidim
38
38
  protected
39
39
 
40
40
  def generate_token
41
- self.token = ensure_unique_token(translated_attribute(title).parameterize(separator: "_"))
41
+ self.token = ensure_unique_token(token.presence || translated_attribute(title).parameterize(separator: "_"))
42
42
  end
43
43
 
44
44
  def ensure_unique_token(token)
@@ -1,6 +1,7 @@
1
1
  import "src/decidim/proposals/utils"
2
2
  import "src/decidim/proposals/add_proposal"
3
3
  import "src/decidim/proposals/choose_proposals"
4
+ import "src/decidim/proposals/exit_handler"
4
5
 
5
6
  // Images
6
7
  require.context("../images", true)
@@ -0,0 +1,2 @@
1
+ import "src/decidim/proposals/reverse_geocoding.js"
2
+ import "stylesheets/decidim/proposals/geocoding_addons.scss";
@@ -13,13 +13,20 @@ $(() => {
13
13
  return $(".table-list [data-published-state=false] .js-check-all-proposal:checked").length
14
14
  }
15
15
 
16
+ const selectedProposalsAllowsAnswerCount = function() {
17
+ return $(".table-list [data-allow-answer=true] .js-check-all-proposal:checked").length
18
+ }
19
+
16
20
  const selectedProposalsCountUpdate = function() {
17
21
  const selectedProposals = selectedProposalsCount();
18
22
  const selectedProposalsNotPublishedAnswer = selectedProposalsNotPublishedAnswerCount();
23
+ const allowAnswerProposals = selectedProposalsAllowsAnswerCount();
24
+
19
25
  if (selectedProposals === 0) {
20
26
  $("#js-selected-proposals-count").text("")
21
27
  $("#js-assign-proposals-to-valuator-actions").addClass("hide");
22
28
  $("#js-unassign-proposals-from-valuator-actions").addClass("hide");
29
+ $("#js-taxonomy-change-proposals-actions").addClass("hide");
23
30
  } else {
24
31
  $("#js-selected-proposals-count").text(selectedProposals);
25
32
  }
@@ -36,6 +43,13 @@ $(() => {
36
43
  } else {
37
44
  $('button[data-action="publish-answers"]').parent().hide();
38
45
  }
46
+
47
+ if (allowAnswerProposals > 0) {
48
+ $('button[data-action="apply-answer-template"]').parent().show();
49
+ $("#js-form-apply-answer-template-number").text(allowAnswerProposals);
50
+ } else {
51
+ $('button[data-action="apply-answer-template"]').parent().hide();
52
+ }
39
53
  }
40
54
 
41
55
  const showBulkActionsButton = function() {
@@ -91,7 +105,8 @@ $(() => {
91
105
  let action = $(e.target).data("action");
92
106
  const panelActions = [
93
107
  "assign-proposals-to-valuator",
94
- "unassign-proposals-from-valuator"
108
+ "unassign-proposals-from-valuator",
109
+ "taxonomy-change-proposals"
95
110
  ];
96
111
 
97
112
  if (!action) {
@@ -0,0 +1,73 @@
1
+ const allowExitFrom = (el) => {
2
+ if (el.id === "exit-proposal-notification-link" || el.classList.contains("no-modal")) {
3
+ return true;
4
+ }
5
+
6
+ return false;
7
+ };
8
+
9
+ document.addEventListener("DOMContentLoaded", () => {
10
+ const exitNotification = document.getElementById("exit-proposal-notification");
11
+ const exitLink = document.getElementById("exit-proposal-notification-link");
12
+ if (!exitLink) {
13
+ return;
14
+ }
15
+ const defaultExitUrl = exitLink.href;
16
+ const defaultExitLinkText = exitLink.textContent;
17
+ const signOutPath = window.Decidim.config.get("sign_out_path");
18
+ let exitLinkText = defaultExitLinkText;
19
+
20
+ if (!exitNotification) {
21
+ // Do not apply when not inside the voting pipeline
22
+ return;
23
+ }
24
+
25
+ const openExitNotification = (url, method = null) => {
26
+ if (method && method !== "get") {
27
+ exitLink.setAttribute("data-method", method);
28
+ } else {
29
+ exitLink.removeAttribute("data-method");
30
+ }
31
+
32
+ exitLink.setAttribute("href", url);
33
+ exitLink.textContent = exitLinkText;
34
+ window.Decidim.currentDialogs["exit-proposal-notification"].open();
35
+ };
36
+
37
+ const handleClicks = (link) => {
38
+ link.addEventListener("click", (event) => {
39
+ exitLinkText = defaultExitLinkText;
40
+
41
+ if (
42
+ !allowExitFrom(link) &&
43
+ ((window.Decidim.currentDialogs["exit-proposal-notification"].dialog.querySelector("[data-dialog-container]")).dataset.minimumVotesReached !== "true") &&
44
+ ((window.Decidim.currentDialogs["exit-proposal-notification"].dialog.querySelector("[data-dialog-container]")).dataset.minimumVotesCount > 0)
45
+ ) {
46
+ event.preventDefault();
47
+ openExitNotification(link.getAttribute("href"), link.dataset.method);
48
+ }
49
+ });
50
+ };
51
+
52
+ document.querySelectorAll("a").forEach(handleClicks);
53
+ // Custom handling for the header sign-out link
54
+ const signOutLink = document.querySelector(`[href='${signOutPath}']`);
55
+ if (signOutLink) {
56
+ signOutLink.addEventListener("click", (event) => {
57
+ event.preventDefault();
58
+ event.stopPropagation();
59
+
60
+ exitLinkText = signOutLink.textContent;
61
+ openExitNotification(signOutLink.getAttribute("href"), signOutLink.dataset.method);
62
+ });
63
+ }
64
+
65
+ // Custom handling for links that open the exit notification dialog
66
+ const dialogOpenLinks = document.querySelectorAll("a[data-dialog-open='exit-proposal-notification']");
67
+ dialogOpenLinks.forEach((link) => {
68
+ link.addEventListener("click", () => {
69
+ exitLinkText = defaultExitLinkText;
70
+ openExitNotification(defaultExitUrl);
71
+ });
72
+ });
73
+ });
@@ -103,11 +103,213 @@
103
103
  }
104
104
 
105
105
  &__grid-text-title {
106
- @apply flex justify-between flex-row md:flex-col items-start lg:flex-row;
106
+ @apply flex justify-between items-start gap-y-2 flex-row md:flex-col lg:flex-row;
107
107
  }
108
108
 
109
- &__list-metadata {
110
- @apply md:flex md:flex-row md:items-center md:gap-4;
109
+ &__list-list {
110
+ .card__proposals-item {
111
+ @apply border-l-4 rounded-[10px] md:rounded-md border-[#F5F5F5] hover:border-tertiary focus-visible:border-tertiary flex flex-col md:items-center md:flex-row md:justify-between min-h-0 md:min-h-[127px] lg:min-h-[83px];
112
+
113
+ .card__list {
114
+ @apply border-none md:flex-1 md:items-center mb-2.5 md:mb-0;
115
+
116
+ &-metadata {
117
+ @apply gap-y-1 md:flex md:flex-row md:items-center md:gap-x-2 md:mt-0.5;
118
+
119
+ & > div {
120
+ @apply md:pl-0 md:pr-2;
121
+ }
122
+ }
123
+ }
124
+
125
+ .card__proposals-votes {
126
+ @apply flex md:flex-col lg:flex-row bg-[#F5F5F5] justify-around md:flex-[0.6] lg:flex-[0.8] items-center md:items-stretch lg:items-center lg:min-w-[196px] h-[68px] md:h-auto lg:h-[68px];
127
+
128
+ button {
129
+ @apply bg-[var(--secondary)];
130
+
131
+ &[disabled] {
132
+ @apply opacity-50 text-white border-current cursor-not-allowed;
133
+ }
134
+ }
135
+
136
+ &-limited {
137
+ @apply flex flex-col p-0 md:p-2 lg:p-0 md:flex-row md:flex-wrap lg:flex-col lg:flex-nowrap flex-1 md:flex-none lg:flex-1 justify-center;
138
+
139
+ .proposals__aside-progress {
140
+ @apply flex flex-col w-[80%] items-center md:order-1 lg:order-none md:flex-[1_1_100%] lg:flex-none;
141
+ }
142
+
143
+ .progress-bar {
144
+ &__units {
145
+ @apply md:order-none;
146
+ }
147
+
148
+ &__number {
149
+ span {
150
+ @apply text-sm;
151
+ }
152
+ }
153
+ }
154
+ }
155
+
156
+ &-unlimited {
157
+ @apply flex flex-row flex-1 lg:flex-1 justify-center md:flex-none;
158
+
159
+ .progress-bar__number {
160
+ span {
161
+ @apply text-xl;
162
+ }
163
+ }
164
+ }
165
+
166
+ .progress-bar {
167
+ &__number {
168
+ @apply flex justify-center items-center mx-1;
169
+
170
+ span {
171
+ @apply leading-6 font-semibold #{!important};
172
+ }
173
+ }
174
+
175
+ &__units {
176
+ @apply text-sm leading-[22px] mx-1;
177
+ }
178
+ }
179
+
180
+ &-container {
181
+ @apply flex-1 md:w-full md:flex-none p-2 lg:p-0 lg:m-3 lg:w-[164px];
182
+
183
+ button {
184
+ @apply text-sm leading-[18px] font-semibold py-1;
185
+ }
186
+ }
187
+
188
+ &-hidden {
189
+ @apply md:flex-none;
190
+
191
+ .card__proposals-votes-container {
192
+ @apply md:flex-none md:w-[130px] lg:w-[164px] lg:p-0;
193
+ }
194
+ }
195
+ }
196
+ }
197
+ }
198
+
199
+ &__grid-grid {
200
+ @apply md:gap-4;
201
+
202
+ .card__proposals-item {
203
+ @apply flex flex-col justify-between border-solid border-[#D3D5D9] border rounded;
204
+
205
+ .card__grid {
206
+ @apply mb-2.5 md:mb-0 flex-1 justify-between;
207
+
208
+ box-shadow: none !important;
209
+
210
+ &-text {
211
+ @apply justify-end lg:gap-y-8;
212
+ }
213
+
214
+ &-metadata {
215
+ @apply mt-0 md:flex md:flex-row;
216
+
217
+ > :first-child {
218
+ flex: 1 !important;
219
+ }
220
+
221
+ > :not(:first-child) {
222
+ @apply border-r border-[#D3D5D9];
223
+ }
224
+
225
+ & > div {
226
+ @apply px-2 md:px-2;
227
+ }
228
+ }
229
+ }
230
+
231
+ .card__proposals-votes {
232
+ @apply flex bg-[#F5F5F5EE] justify-around items-center px-4 h-[68px];
233
+
234
+ button {
235
+ @apply bg-[var(--secondary)];
236
+
237
+ &[disabled] {
238
+ @apply opacity-50 text-white border-current cursor-not-allowed;
239
+ }
240
+ }
241
+
242
+ &-limited {
243
+ @apply flex flex-col lg:items-start flex-1 md:mr-4 lg:mr-0;
244
+
245
+ .proposals__aside-progress {
246
+ @apply flex flex-col-reverse w-[80%] items-center;
247
+ }
248
+
249
+ .progress-bar {
250
+ &__number {
251
+ @apply lg:w-[80%];
252
+
253
+ span {
254
+ @apply text-sm;
255
+ }
256
+ }
257
+
258
+ &__units {
259
+ @apply flex justify-center w-[80%];
260
+ }
261
+ }
262
+ }
263
+
264
+ &-unlimited {
265
+ @apply flex flex-row flex-1 lg:flex-1 md:flex-none md:mr-4 lg:mr-0;
266
+
267
+ .progress-bar__number {
268
+ span {
269
+ @apply text-xl;
270
+ }
271
+ }
272
+ }
273
+
274
+ .progress-bar {
275
+ &__number {
276
+ @apply flex justify-center items-center;
277
+
278
+ span {
279
+ @apply leading-6 font-semibold #{!important};
280
+ }
281
+ }
282
+
283
+ &__units {
284
+ @apply text-sm leading-[22px] mx-1;
285
+ }
286
+ }
287
+
288
+ &-container {
289
+ @apply flex-1 lg:w-[164px] lg:flex-none;
290
+
291
+ button {
292
+ @apply text-sm leading-[18px] font-semibold py-1;
293
+
294
+ .already-voted-icon {
295
+ @apply w-4 h-4;
296
+ }
297
+ }
298
+ }
299
+
300
+ &-hidden {
301
+ @apply md:justify-end;
302
+
303
+ .card__proposals-votes-container {
304
+ @apply md:flex-none md:w-[130px] lg:w-[164px];
305
+
306
+ button {
307
+ @apply md:px-0 lg:px-6;
308
+ }
309
+ }
310
+ }
311
+ }
312
+ }
111
313
  }
112
314
  }
113
315
 
@@ -129,3 +331,46 @@
129
331
  }
130
332
  }
131
333
  }
334
+
335
+ #proposal-voting-rules {
336
+ [id="dropdown-menu-proposal-voting-rules"] {
337
+ &[aria-hidden="true"] {
338
+ @apply hidden;
339
+ }
340
+ }
341
+
342
+ [data-target="dropdown-menu-proposal-voting-rules"] {
343
+ @apply w-full flex items-center justify-between gap-2 p-2 first-of-type:[&>svg]:block last-of-type:[&>svg]:hidden;
344
+
345
+ & > span {
346
+ @apply hidden font-semibold text-secondary;
347
+
348
+ &:only-of-type,
349
+ &.is-active {
350
+ @apply flex items-center gap-2 [&_svg]:fill-current;
351
+ }
352
+ }
353
+
354
+ > svg {
355
+ @apply w-6 h-6 flex-none text-secondary fill-current;
356
+ }
357
+
358
+ &[aria-expanded="false"] > svg:last-of-type,
359
+ &[aria-expanded="true"] > svg:first-of-type {
360
+ @apply hidden;
361
+ }
362
+
363
+ &[aria-expanded="true"] > svg:last-of-type,
364
+ &[aria-expanded="false"] > svg:first-of-type {
365
+ @apply block;
366
+ }
367
+ }
368
+ }
369
+
370
+ .layout-item-complementary {
371
+ @apply container grid grid-cols-1 lg:grid-cols-12;
372
+
373
+ .item_voting_rules {
374
+ @apply lg:col-start-2 lg:col-span-10;
375
+ }
376
+ }
@@ -30,11 +30,8 @@ module Decidim
30
30
  # time limit.
31
31
  allow! if permission_action.subject == :proposal && permission_action.action == :edit && admin_edition_is_available?
32
32
 
33
- # Every user allowed by the space can update the category of the proposal
34
- allow! if permission_action.subject == :proposal_category && permission_action.action == :update
35
-
36
- # Every user allowed by the space can update the scope of the proposal
37
- allow! if permission_action.subject == :proposal_scope && permission_action.action == :update
33
+ # Every user allowed by the space can update the taxonomy of the proposal
34
+ allow! if permission_action.subject == :proposal_taxonomy && permission_action.action == :update
38
35
 
39
36
  # Every user allowed by the space can import proposals from another_component
40
37
  allow! if permission_action.subject == :proposals && permission_action.action == :import
@@ -16,6 +16,8 @@ module Decidim
16
16
  apply_proposal_permissions(permission_action) unless permission_action.action == :read
17
17
  when :collaborative_draft
18
18
  apply_collaborative_draft_permissions(permission_action)
19
+ when :proposal_coauthor_invites
20
+ apply_proposal_coauthor_invites(permission_action)
19
21
  else
20
22
  permission_action
21
23
  end
@@ -159,6 +161,46 @@ module Decidim
159
161
 
160
162
  toggle_allow(collaborative_draft.created_by?(user))
161
163
  end
164
+
165
+ def apply_proposal_coauthor_invites(permission_action)
166
+ return toggle_allow(false) unless coauthor
167
+ return toggle_allow(false) unless proposal
168
+
169
+ case permission_action.action
170
+ when :invite
171
+ toggle_allow(valid_coauthor? && !notification_already_sent?)
172
+ when :cancel
173
+ toggle_allow(valid_coauthor? && notification_already_sent?)
174
+ when :accept, :decline
175
+ toggle_allow(can_be_coauthor?)
176
+ end
177
+ end
178
+
179
+ def coauthor
180
+ context.fetch(:coauthor, nil)
181
+ end
182
+
183
+ def notification_already_sent?
184
+ @notification_already_sent ||= proposal.coauthor_invitations_for(coauthor).any?
185
+ end
186
+
187
+ def coauthor_in_comments?
188
+ @coauthor_in_comments ||= proposal.comments.where(author: coauthor).any?
189
+ end
190
+
191
+ def valid_coauthor?
192
+ return false unless proposal.authors.include?(user)
193
+ return false unless proposal.user_has_actions?(coauthor)
194
+
195
+ coauthor_in_comments?
196
+ end
197
+
198
+ def can_be_coauthor?
199
+ return false unless user == coauthor
200
+ return false unless proposal.user_has_actions?(coauthor)
201
+
202
+ notification_already_sent?
203
+ end
162
204
  end
163
205
  end
164
206
  end
@@ -27,7 +27,7 @@ module Decidim
27
27
 
28
28
  def action_string
29
29
  case action
30
- when "answer", "create", "update", "publish_answer"
30
+ when "answer", "create", "update", "publish_answer", "soft_delete", "restore"
31
31
  "decidim.proposals.admin_log.proposal.#{action}"
32
32
  else
33
33
  super
@@ -40,7 +40,7 @@ module Decidim
40
40
  def title(links: false, extras: true, html_escape: false, all_locales: false)
41
41
  return unless proposal
42
42
 
43
- super proposal.title, links, html_escape, all_locales, extras:
43
+ super(proposal.title, links, html_escape, all_locales, extras:)
44
44
  end
45
45
 
46
46
  def id_and_title(links: false, extras: true, html_escape: false)
@@ -28,8 +28,8 @@ module Decidim
28
28
  # by a range of dates.
29
29
  def query
30
30
  proposals = Decidim::Proposals::Proposal.where(component: @components)
31
- proposals = proposals.where("created_at >= ?", @start_at) if @start_at.present?
32
- proposals = proposals.where("created_at <= ?", @end_at) if @end_at.present?
31
+ proposals = proposals.where(created_at: @start_at..) if @start_at.present?
32
+ proposals = proposals.where(created_at: ..@end_at) if @end_at.present?
33
33
  proposals
34
34
  end
35
35
  end
@@ -18,13 +18,13 @@ module Decidim
18
18
  end
19
19
  @query = Decidim::Proposals::Proposal.where(component: visible_components_from_spaces(spaces)).joins(:component)
20
20
  .left_outer_joins(:category)
21
- @query = @query.where("decidim_proposals_proposals.created_at <= ?", end_time).accepted
21
+ @query = @query.where(decidim_proposals_proposals: { created_at: ..end_time }).accepted
22
22
  @query = @query.group("decidim_categorizations.id", :participatory_space_type, :participatory_space_id)
23
23
  @query
24
24
  end
25
25
 
26
26
  def quantity
27
- @quantity ||= query.where("decidim_proposals_proposals.created_at >= ?", start_time).count
27
+ @quantity ||= query.where(decidim_proposals_proposals: { created_at: start_time.. }).count
28
28
  end
29
29
  end
30
30
  end