decidim-elections 0.22.0 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/decidim_elections_manifest.js +2 -0
  3. data/app/assets/javascripts/decidim/elections/vote.js.es6 +170 -0
  4. data/app/assets/stylesheets/decidim/elections/elections.scss +10 -0
  5. data/app/assets/stylesheets/decidim/elections/focus/_evote.scss +279 -0
  6. data/app/assets/stylesheets/decidim/elections/focus/_focus.scss +128 -0
  7. data/app/cells/decidim/elections/election_cell.rb +22 -0
  8. data/app/cells/decidim/elections/election_m/data.erb +19 -0
  9. data/app/cells/decidim/elections/election_m/footer.erb +5 -0
  10. data/app/cells/decidim/elections/election_m_cell.rb +103 -0
  11. data/app/cells/decidim/elections/voting_step_navigation/show.erb +29 -0
  12. data/app/cells/decidim/elections/voting_step_navigation_cell.rb +42 -0
  13. data/app/commands/decidim/elections/admin/create_election.rb +14 -3
  14. data/app/commands/decidim/elections/admin/create_question.rb +2 -1
  15. data/app/commands/decidim/elections/admin/destroy_election.rb +4 -0
  16. data/app/commands/decidim/elections/admin/import_proposals_to_elections.rb +87 -0
  17. data/app/commands/decidim/elections/admin/publish_election.rb +54 -0
  18. data/app/commands/decidim/elections/admin/unpublish_election.rb +43 -0
  19. data/app/commands/decidim/elections/admin/update_election.rb +15 -3
  20. data/app/commands/decidim/elections/admin/update_question.rb +2 -1
  21. data/app/controllers/concerns/decidim/elections/orderable.rb +36 -0
  22. data/app/controllers/decidim/elections/admin/answers_controller.rb +9 -1
  23. data/app/controllers/decidim/elections/admin/elections_controller.rb +26 -0
  24. data/app/controllers/decidim/elections/admin/proposals_imports_controller.rb +53 -0
  25. data/app/controllers/decidim/elections/elections_controller.rb +68 -0
  26. data/app/controllers/decidim/elections/votes_controller.rb +50 -0
  27. data/app/events/decidim/elections/election_published_event.rb +8 -0
  28. data/app/forms/decidim/elections/admin/answer_import_proposals_form.rb +41 -0
  29. data/app/forms/decidim/elections/admin/election_form.rb +15 -2
  30. data/app/forms/decidim/elections/admin/question_form.rb +1 -0
  31. data/app/helpers/decidim/elections/application_helper.rb +12 -0
  32. data/app/helpers/decidim/elections/election_cells_helper.rb +12 -0
  33. data/app/helpers/decidim/elections/votes_helper.rb +21 -0
  34. data/app/models/decidim/elections/answer.rb +5 -1
  35. data/app/models/decidim/elections/election.rb +69 -0
  36. data/app/models/decidim/elections/question.rb +9 -2
  37. data/app/permissions/decidim/elections/admin/permissions.rb +13 -3
  38. data/app/permissions/decidim/elections/permissions.rb +35 -0
  39. data/app/presenters/decidim/elections/admin_log/election_presenter.rb +41 -0
  40. data/app/presenters/decidim/elections/election_presenter.rb +28 -0
  41. data/app/queries/decidim/elections/filtered_elections.rb +37 -0
  42. data/app/services/decidim/elections/election_search.rb +35 -0
  43. data/app/types/decidim/elections/election_question_type.rb +2 -2
  44. data/app/types/decidim/elections/election_type.rb +2 -1
  45. data/app/types/decidim/elections/elections_type.rb +1 -1
  46. data/app/views/decidim/elections/admin/answers/index.html.erb +2 -1
  47. data/app/views/decidim/elections/admin/elections/_form.html.erb +2 -4
  48. data/app/views/decidim/elections/admin/elections/index.html.erb +23 -1
  49. data/app/views/decidim/elections/admin/proposals_imports/new.html.erb +24 -0
  50. data/app/views/decidim/elections/admin/questions/_form.html.erb +4 -0
  51. data/app/views/decidim/elections/admin/questions/index.html.erb +3 -1
  52. data/app/views/decidim/elections/elections/_count.html.erb +1 -0
  53. data/app/views/decidim/elections/elections/_election.html.erb +1 -0
  54. data/app/views/decidim/elections/elections/_elections.html.erb +24 -0
  55. data/app/views/decidim/elections/elections/_filters.html.erb +24 -0
  56. data/app/views/decidim/elections/elections/_filters_small_view.html.erb +18 -0
  57. data/app/views/decidim/elections/elections/index.html.erb +24 -0
  58. data/app/views/decidim/elections/elections/index.js.erb +10 -0
  59. data/app/views/decidim/elections/elections/show.html.erb +62 -0
  60. data/app/views/decidim/elections/votes/_election_votes_confirm.html.erb +53 -0
  61. data/app/views/decidim/elections/votes/_election_votes_confirm_footer.html.erb +24 -0
  62. data/app/views/decidim/elections/votes/_election_votes_confirmed.html.erb +26 -0
  63. data/app/views/decidim/elections/votes/_election_votes_encrypting.html.erb +20 -0
  64. data/app/views/decidim/elections/votes/_election_votes_header.html.erb +8 -0
  65. data/app/views/decidim/elections/votes/_election_votes_modal.html.erb +46 -0
  66. data/app/views/decidim/elections/votes/_election_votes_question.html.erb +49 -0
  67. data/app/views/decidim/elections/votes/_election_votes_steps_header.html.erb +12 -0
  68. data/app/views/decidim/elections/votes/new.html.erb +44 -0
  69. data/app/views/layouts/decidim/_election_votes_header.html.erb +13 -0
  70. data/app/views/layouts/decidim/election_votes.html.erb +24 -0
  71. data/config/locales/am-ET.yml +1 -0
  72. data/config/locales/bg.yml +7 -0
  73. data/config/locales/ca.yml +136 -6
  74. data/config/locales/cs.yml +133 -1
  75. data/config/locales/da.yml +1 -0
  76. data/config/locales/de.yml +93 -1
  77. data/config/locales/el.yml +11 -1
  78. data/config/locales/en.yml +131 -1
  79. data/config/locales/eo.yml +1 -0
  80. data/config/locales/es-MX.yml +131 -1
  81. data/config/locales/es-PY.yml +131 -1
  82. data/config/locales/es.yml +134 -4
  83. data/config/locales/et.yml +1 -0
  84. data/config/locales/fi-plain.yml +131 -1
  85. data/config/locales/fi.yml +131 -1
  86. data/config/locales/fr-CA.yml +131 -1
  87. data/config/locales/fr.yml +131 -1
  88. data/config/locales/hr.yml +1 -0
  89. data/config/locales/hu.yml +18 -0
  90. data/config/locales/is.yml +1 -0
  91. data/config/locales/it.yml +88 -1
  92. data/config/locales/ja-JP.yml +95 -8
  93. data/config/locales/ja.yml +254 -0
  94. data/config/locales/ko-KR.yml +1 -0
  95. data/config/locales/ko.yml +1 -0
  96. data/config/locales/lt.yml +1 -0
  97. data/config/locales/{lv-LV.yml → lv.yml} +11 -1
  98. data/config/locales/mt.yml +1 -0
  99. data/config/locales/nl.yml +88 -1
  100. data/config/locales/no.yml +64 -0
  101. data/config/locales/om-ET.yml +1 -0
  102. data/config/locales/pl.yml +124 -1
  103. data/config/locales/pt.yml +84 -1
  104. data/config/locales/ro-RO.yml +85 -1
  105. data/config/locales/so-SO.yml +1 -0
  106. data/config/locales/sv.yml +121 -1
  107. data/config/locales/ti-ER.yml +1 -0
  108. data/config/locales/vi-VN.yml +1 -0
  109. data/config/locales/vi.yml +1 -0
  110. data/config/locales/zh-CN.yml +254 -0
  111. data/config/locales/zh-TW.yml +1 -0
  112. data/db/migrate/20200601141412_add_published_at_to_elections.rb +7 -0
  113. data/db/migrate/20200807125040_remove_subtitle_from_decidim_elections.rb +7 -0
  114. data/db/migrate/20200910103648_add_min_selections_to_decidim_elections_questions.rb +7 -0
  115. data/lib/decidim/elections.rb +5 -0
  116. data/lib/decidim/elections/admin_engine.rb +7 -0
  117. data/lib/decidim/elections/bulletin_board_client.rb +35 -0
  118. data/lib/decidim/elections/component.rb +17 -12
  119. data/lib/decidim/elections/engine.rb +8 -1
  120. data/lib/decidim/elections/test/factories.rb +59 -6
  121. data/lib/decidim/elections/version.rb +1 -1
  122. data/lib/tasks/decidim_elections.rake +16 -0
  123. metadata +79 -13
@@ -0,0 +1,128 @@
1
+ .focus{
2
+ $top-padding: .5rem;
3
+
4
+ display: flex;
5
+ flex-direction: column;
6
+ min-height: 100vh;
7
+ background-color: $light-gray;
8
+
9
+ &__top{
10
+ padding: $top-padding 1rem;
11
+ background-color: $white;
12
+ position: sticky;
13
+ top: 0;
14
+ z-index: 1;
15
+ display: flex;
16
+ }
17
+
18
+ &__top_warning{
19
+ background: $alert;
20
+ color: white;
21
+ text-align: center;
22
+ display: block;
23
+ padding: 2px 0;
24
+ }
25
+
26
+ &__brand{
27
+ display: flex;
28
+ align-items: center;
29
+
30
+ img{
31
+ max-height: 20px;
32
+ }
33
+ }
34
+
35
+ &__user{
36
+ margin: 0 15px 0 auto;
37
+ padding-right: 0;
38
+ display: flex;
39
+ align-items: center;
40
+ }
41
+
42
+ &__exit{
43
+ display: flex;
44
+ align-items: center;
45
+ line-height: 1;
46
+ margin: -$top-padding 0;
47
+ padding: .5rem .25rem .5rem 1rem;
48
+ border-left: 1px solid rgba(black, .2);
49
+ }
50
+
51
+ &__x-icon{
52
+ font-size: 2rem;
53
+ margin-right: .5rem;
54
+ color: $muted;
55
+
56
+ a:hover &{
57
+ color: inherit;
58
+ }
59
+ }
60
+
61
+ &__header{
62
+ background-color: $dark-gray;
63
+ color: $white;
64
+ padding: 1rem;
65
+
66
+ &-title{
67
+ margin-bottom: 0;
68
+ }
69
+ }
70
+
71
+ &__step{
72
+ display: flex;
73
+ flex-direction: column;
74
+ flex-grow: 1;
75
+ }
76
+
77
+ &__steps{
78
+ text-transform: uppercase;
79
+ font-size: 80%;
80
+ letter-spacing: .05em;
81
+ color: $light-gray;
82
+ }
83
+
84
+ &__content{
85
+ flex-grow: 1;
86
+ display: flex;
87
+
88
+ .row{
89
+ flex-grow: 1;
90
+ }
91
+ }
92
+
93
+ &__footer{
94
+ margin-top: auto;
95
+ padding: 1rem;
96
+ background-color: $white;
97
+
98
+ .row{
99
+ display: flex;
100
+ align-items: baseline;
101
+ width: 100%;
102
+ }
103
+
104
+ .button{
105
+ margin-bottom: 0;
106
+ }
107
+ }
108
+
109
+ &__back{
110
+ font-weight: 600;
111
+
112
+ svg{
113
+ font-size: .7em;
114
+ margin-right: .3em;
115
+ vertical-align: 0;
116
+ }
117
+ }
118
+
119
+ &__next{
120
+ margin-left: auto;
121
+
122
+ svg{
123
+ font-size: .7em;
124
+ margin-left: .3em;
125
+ vertical-align: 0;
126
+ }
127
+ }
128
+ }
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Elections
5
+ # This cell renders the election card for an instance of an election
6
+ # the default size is the Medium Card (:m)
7
+ class ElectionCell < Decidim::ViewModel
8
+ include ElectionCellsHelper
9
+ include Cell::ViewModel::Partial
10
+
11
+ def show
12
+ cell card_size, model, options
13
+ end
14
+
15
+ private
16
+
17
+ def card_size
18
+ "decidim/elections/election_m"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ <div class="card__icondata">
2
+ <ul class="card-data">
3
+ <li class="card-data__item">
4
+ <%= icon "datetime", class: "icon--big", role: "img", aria_label: t("decidim.elections.election_m.label.date") %>
5
+ </li>
6
+ <li class="card-data__item">
7
+ <div class="card-data__item--centerblock">
8
+ <strong><%= t("decidim.elections.election_m.start_date") %></strong>
9
+ <%= start_date ? l(start_date, format: :decidim_short) : t("decidim.elections.election_m.unspecified") %>
10
+ </div>
11
+ </li>
12
+ <li class="card-data__item">
13
+ <div class="card-data__item--centerblock">
14
+ <strong><%= t("decidim.elections.election_m.end_date") %></strong>
15
+ <%= end_date ? l(end_date, format: :decidim_short) : t("decidim.elections.election_m.unspecified") %>
16
+ </div>
17
+ </li>
18
+ </ul>
19
+ </div>
@@ -0,0 +1,5 @@
1
+ <div class="card__footer card__footer--spaces">
2
+ <div class="card__support">
3
+ <%= link_to footer_button_text, resource_path, class: "card__button button button--sc light small" %>
4
+ </div>
5
+ </div>
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Elections
5
+ # This cell renders the Medium (:m) election card
6
+ # for a given instance of an Election
7
+ class ElectionMCell < Decidim::CardMCell
8
+ include ElectionCellsHelper
9
+
10
+ def date
11
+ render
12
+ end
13
+
14
+ private
15
+
16
+ def has_state?
17
+ true
18
+ end
19
+
20
+ def title
21
+ present(model).title
22
+ end
23
+
24
+ def resource_icon
25
+ icon "elections", class: "icon--big"
26
+ end
27
+
28
+ # Even though we need to render the badge, we can't do it in the normal
29
+ # way, because the paragraph comes from a user input and contains HTML.
30
+ # This causes the badge and the paragraph to appear in different lines.
31
+ # In order to fix it, check the `description` method.
32
+ def has_badge?
33
+ false
34
+ end
35
+
36
+ def badge_name
37
+ text = model.voting_period_status
38
+ return unless text
39
+
40
+ I18n.t(text, scope: "decidim.elections.election_m.badge_name")
41
+ end
42
+
43
+ # In order to render the badge inline with the paragraph text we need to
44
+ # find the opening `<p>` tag and include the badge right after it. This
45
+ # makes the layout look good.
46
+ def has_image?
47
+ model.photos.present?
48
+ end
49
+
50
+ def resource_image_path
51
+ model.photos.first.url if has_image?
52
+ end
53
+
54
+ def description
55
+ text = super
56
+ text.gsub!(/^<p>/, "<p>#{render :badge}")
57
+ html_truncate(text, length: 100)
58
+ end
59
+
60
+ def state_classes
61
+ case model.voting_period_status
62
+ when :ongoing
63
+ ["success"]
64
+ when :upcoming
65
+ ["warning"]
66
+ else
67
+ ["muted"]
68
+ end
69
+ end
70
+
71
+ def start_date
72
+ return unless model.start_time
73
+
74
+ model.start_time.to_date
75
+ end
76
+
77
+ def end_date
78
+ return unless model.end_time
79
+
80
+ model.end_time.to_date
81
+ end
82
+
83
+ def questions_count_status
84
+ content_tag(
85
+ :strong,
86
+ t("decidim.elections.election_m.questions", count: model.questions.count)
87
+ )
88
+ end
89
+
90
+ def footer_button_text
91
+ if model.ongoing?
92
+ t("decidim.elections.election_m.footer.vote")
93
+ else
94
+ t("decidim.elections.election_m.footer.view")
95
+ end
96
+ end
97
+
98
+ def statuses
99
+ [:questions_count]
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,29 @@
1
+ <div class="focus__footer">
2
+ <div class="row">
3
+ <% if first_step? %>
4
+ <a></a>
5
+ <% else %>
6
+ <%= link_to(
7
+ icon("chevron-left", class: "icon", role: "img") + " " + t("decidim.elections.votes.voting_step.back"),
8
+ "#",
9
+ class: "focus__back",
10
+ data: {
11
+ toggle: [previous_step_dom_id, current_step_dom_id].join(" ")
12
+ }
13
+ ) %>
14
+ <% end %>
15
+
16
+ <span class="button focus__next disabled-continue disabled">
17
+ <%= button_continue_text %>
18
+ </span>
19
+
20
+ <%= link_to(
21
+ button_continue_text,
22
+ "#",
23
+ class: "button focus__next enabled-continue",
24
+ data: {
25
+ toggle: [next_step_dom_id, current_step_dom_id].join(" ")
26
+ }
27
+ ) %>
28
+ </div>
29
+ </div>
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Elections
5
+ # This cell renders the navigation of a questionnaire step.
6
+ class VotingStepNavigationCell < Decidim::ViewModel
7
+ include Decidim::LayoutHelper
8
+
9
+ def current_step_index
10
+ model
11
+ end
12
+
13
+ def first_step?
14
+ current_step_index.zero?
15
+ end
16
+
17
+ def last_step?
18
+ current_step_index + 1 == total_steps
19
+ end
20
+
21
+ def total_steps
22
+ options[:total_steps]
23
+ end
24
+
25
+ def button_continue_text
26
+ "#{t("decidim.elections.votes.voting_step.continue")} #{icon("chevron-right", class: "icon", role: "img")}"
27
+ end
28
+
29
+ def previous_step_dom_id
30
+ "step-#{current_step_index - 1}"
31
+ end
32
+
33
+ def next_step_dom_id
34
+ "step-#{current_step_index + 1}"
35
+ end
36
+
37
+ def current_step_dom_id
38
+ "step-#{current_step_index}"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -6,6 +6,9 @@ module Decidim
6
6
  # This command is executed when the user creates an Election
7
7
  # from the admin panel.
8
8
  class CreateElection < Rectify::Command
9
+ include ::Decidim::AttachmentMethods
10
+ include ::Decidim::GalleryMethods
11
+
9
12
  def initialize(form)
10
13
  @form = form
11
14
  end
@@ -16,19 +19,26 @@ module Decidim
16
19
  def call
17
20
  return broadcast(:invalid) if form.invalid?
18
21
 
19
- create_election!
22
+ if process_gallery?
23
+ build_gallery
24
+ return broadcast(:invalid) if gallery_invalid?
25
+ end
26
+
27
+ transaction do
28
+ create_election!
29
+ create_gallery if process_gallery?
30
+ end
20
31
 
21
32
  broadcast(:ok, election)
22
33
  end
23
34
 
24
35
  private
25
36
 
26
- attr_reader :form, :election
37
+ attr_reader :form, :election, :gallery
27
38
 
28
39
  def create_election!
29
40
  attributes = {
30
41
  title: form.title,
31
- subtitle: form.subtitle,
32
42
  description: form.description,
33
43
  start_time: form.start_time,
34
44
  end_time: form.end_time,
@@ -41,6 +51,7 @@ module Decidim
41
51
  attributes,
42
52
  visibility: "all"
43
53
  )
54
+ @attached_to = @election
44
55
  end
45
56
  end
46
57
  end
@@ -36,7 +36,8 @@ module Decidim
36
36
  description: form.description,
37
37
  max_selections: form.max_selections,
38
38
  weight: form.weight,
39
- random_answers_order: form.random_answers_order
39
+ random_answers_order: form.random_answers_order,
40
+ min_selections: form.min_selections
40
41
  }
41
42
 
42
43
  @question = Decidim.traceability.create!(
@@ -6,9 +6,13 @@ module Decidim
6
6
  # This command is executed when the user destroys an Election
7
7
  # from the admin panel.
8
8
  class DestroyElection < Rectify::Command
9
+ include ::Decidim::AttachmentMethods
10
+ include ::Decidim::GalleryMethods
11
+
9
12
  def initialize(election, current_user)
10
13
  @election = election
11
14
  @current_user = current_user
15
+ @attached_to = election
12
16
  end
13
17
 
14
18
  # Destroys the election if valid.
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Elections
5
+ module Admin
6
+ # A command is executed when an admin imports proposals from
7
+ # one component to answers of elections component.
8
+ class ImportProposalsToElections < Rectify::Command
9
+ # Public: Initializes the command.
10
+ #
11
+ # form - A form object with the params.
12
+ def initialize(form)
13
+ @form = form
14
+ end
15
+
16
+ # Executes the command. Broadcasts these events:
17
+ #
18
+ # - :ok when everything is valid.
19
+ # - :invalid if the form wasn't valid and we couldn't proceed.
20
+ #
21
+ # Returns nothing.
22
+ def call
23
+ return broadcast(:invalid) if invalid?
24
+
25
+ broadcast(:ok, create_answers_from_accepted_proposals)
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :form, :answer
31
+
32
+ def invalid?
33
+ form.election.started? || form.invalid?
34
+ end
35
+
36
+ def create_answers_from_accepted_proposals
37
+ transaction do
38
+ proposals.map do |original_proposal|
39
+ next if proposal_already_copied?(original_proposal)
40
+
41
+ create_answer_from_proposal(original_proposal)
42
+ answer.link_resources([original_proposal], "related_proposals")
43
+ end.compact
44
+ end
45
+ end
46
+
47
+ def create_answer_from_proposal(original_proposal)
48
+ params = {
49
+ question: form.question,
50
+ title: original_proposal.title,
51
+ description: original_proposal.body,
52
+ weight: form.weight
53
+ }
54
+
55
+ @answer = Decidim.traceability.create!(
56
+ Answer,
57
+ form.current_user,
58
+ params,
59
+ visibility: "all"
60
+ )
61
+ end
62
+
63
+ def proposals
64
+ @proposals ||= if @form.import_all_accepted_proposals?
65
+ Decidim::Proposals::Proposal.where(component: origin_component).where(state: "accepted")
66
+ else
67
+ Decidim::Proposals::Proposal.where(component: origin_component)
68
+ end
69
+ end
70
+
71
+ def origin_component
72
+ @form.origin_component
73
+ end
74
+
75
+ def target_question
76
+ @form.question
77
+ end
78
+
79
+ def proposal_already_copied?(original_proposal)
80
+ original_proposal.linked_resources(:answers, "related_proposals").any? do |answer|
81
+ answer.question == target_question
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end