decidim-meetings 0.24.3 → 0.25.0.rc4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (213) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/meetings/join_meeting_button/registration_confirm.erb +6 -0
  3. data/app/cells/decidim/meetings/join_meeting_button/show.erb +8 -6
  4. data/app/cells/decidim/meetings/meeting_m_cell.rb +4 -0
  5. data/app/cells/decidim/meetings/meeting_url/show.erb +7 -2
  6. data/app/cells/decidim/meetings/meeting_url_cell.rb +14 -2
  7. data/app/cells/decidim/meetings/meetings_map/show.erb +1 -1
  8. data/app/cells/decidim/meetings/online_meeting_cell.rb +36 -0
  9. data/app/cells/decidim/meetings/online_meeting_link/show.erb +44 -0
  10. data/app/cells/decidim/meetings/online_meeting_link_cell.rb +27 -0
  11. data/app/cells/decidim/meetings/public_participants_list/show.erb +16 -0
  12. data/app/cells/decidim/meetings/public_participants_list_cell.rb +31 -0
  13. data/app/cells/decidim/meetings/question_responses/show.erb +7 -0
  14. data/app/cells/decidim/meetings/question_responses_cell.rb +72 -0
  15. data/app/commands/decidim/meetings/admin/close_meeting.rb +3 -0
  16. data/app/commands/decidim/meetings/admin/copy_meeting.rb +0 -9
  17. data/app/commands/decidim/meetings/admin/create_meeting.rb +4 -20
  18. data/app/commands/decidim/meetings/admin/destroy_meeting.rb +2 -0
  19. data/app/commands/decidim/meetings/admin/publish_meeting.rb +72 -0
  20. data/app/commands/decidim/meetings/admin/unpublish_meeting.rb +45 -0
  21. data/app/commands/decidim/meetings/admin/update_meeting.rb +6 -3
  22. data/app/commands/decidim/meetings/admin/update_question_status.rb +57 -0
  23. data/app/commands/decidim/meetings/admin/update_questionnaire.rb +93 -0
  24. data/app/commands/decidim/meetings/close_meeting.rb +2 -0
  25. data/app/commands/decidim/meetings/create_answer.rb +52 -0
  26. data/app/commands/decidim/meetings/create_meeting.rb +3 -1
  27. data/app/commands/decidim/meetings/join_meeting.rb +2 -1
  28. data/app/commands/decidim/meetings/update_meeting.rb +2 -1
  29. data/app/controllers/concerns/decidim/meetings/admin/filterable.rb +55 -0
  30. data/app/controllers/concerns/decidim/meetings/polls_resources.rb +29 -0
  31. data/app/controllers/decidim/meetings/admin/application_controller.rb +6 -2
  32. data/app/controllers/decidim/meetings/admin/invites_controller.rb +0 -2
  33. data/app/controllers/decidim/meetings/admin/meeting_closes_controller.rb +1 -1
  34. data/app/controllers/decidim/meetings/admin/meetings_controller.rb +46 -1
  35. data/app/controllers/decidim/meetings/admin/meetings_poll_controller.rb +107 -0
  36. data/app/controllers/decidim/meetings/calendars_controller.rb +8 -0
  37. data/app/controllers/decidim/meetings/live_events_controller.rb +29 -0
  38. data/app/controllers/decidim/meetings/meeting_closes_controller.rb +1 -1
  39. data/app/controllers/decidim/meetings/polls/answers_controller.rb +37 -0
  40. data/app/controllers/decidim/meetings/polls/questions_controller.rb +45 -0
  41. data/app/events/decidim/meetings/create_meeting_event.rb +16 -0
  42. data/app/forms/decidim/meetings/admin/answer_option_form.rb +24 -0
  43. data/app/forms/decidim/meetings/admin/close_meeting_form.rb +3 -1
  44. data/app/forms/decidim/meetings/admin/meeting_form.rb +12 -1
  45. data/app/forms/decidim/meetings/admin/question_form.rb +44 -0
  46. data/app/forms/decidim/meetings/admin/questionnaire_form.rb +19 -0
  47. data/app/forms/decidim/meetings/answer_choice_form.rb +14 -0
  48. data/app/forms/decidim/meetings/answer_form.rb +65 -0
  49. data/app/forms/decidim/meetings/close_meeting_form.rb +4 -0
  50. data/app/forms/decidim/meetings/join_meeting_form.rb +1 -0
  51. data/app/forms/decidim/meetings/meeting_form.rb +9 -0
  52. data/app/helpers/decidim/meetings/admin/filterable_helper.rb +13 -0
  53. data/app/helpers/decidim/meetings/application_helper.rb +3 -2
  54. data/app/helpers/decidim/meetings/meetings_helper.rb +21 -0
  55. data/app/models/decidim/meetings/answer.rb +48 -0
  56. data/app/models/decidim/meetings/answer_choice.rb +15 -0
  57. data/app/models/decidim/meetings/answer_option.rb +24 -0
  58. data/app/models/decidim/meetings/meeting.rb +88 -42
  59. data/app/models/decidim/meetings/poll.rb +19 -0
  60. data/app/models/decidim/meetings/question.rb +54 -0
  61. data/app/models/decidim/meetings/questionnaire.rb +23 -0
  62. data/app/models/decidim/meetings/registration.rb +2 -0
  63. data/app/packs/entrypoints/decidim_meetings.js +6 -0
  64. data/app/packs/entrypoints/decidim_meetings_admin.js +5 -0
  65. data/app/{assets/images/decidim/gamification/badges/attended_meetings.svg → packs/images/decidim/gamification/badges/decidim_gamification_badges_attended_meetings.svg} +0 -0
  66. data/app/{assets/images/decidim/meetings/icon.svg → packs/images/decidim/meetings/decidim_meetings.svg} +0 -0
  67. data/app/packs/src/decidim/meetings/admin/agendas.js +159 -0
  68. data/app/{assets/javascripts/decidim/meetings/admin/destroy_meeting_alert.js.es6 → packs/src/decidim/meetings/admin/destroy_meeting_alert.js} +0 -0
  69. data/app/{assets/javascripts/decidim/meetings/admin/meetings_form.js.es6 → packs/src/decidim/meetings/admin/meetings_form.js} +7 -4
  70. data/app/{assets/javascripts/decidim/meetings/admin/registrations_form.js.es6 → packs/src/decidim/meetings/admin/registrations_form.js} +0 -0
  71. data/app/{assets/javascripts/decidim/meetings/admin/registrations_invite_form.es6 → packs/src/decidim/meetings/admin/registrations_invite_form.js} +3 -3
  72. data/app/packs/src/decidim/meetings/meetings_form.js +54 -0
  73. data/app/packs/src/decidim/meetings/meetings_polls.js +41 -0
  74. data/app/packs/src/decidim/meetings/poll.component.js +166 -0
  75. data/app/packs/stylesheets/decidim/meetings/_header.scss +52 -0
  76. data/app/packs/stylesheets/decidim/meetings/_main.scss +178 -0
  77. data/app/packs/stylesheets/decidim/meetings/_meetings.scss +2 -0
  78. data/app/permissions/decidim/meetings/admin/permissions.rb +16 -8
  79. data/app/permissions/decidim/meetings/permissions.rb +42 -17
  80. data/app/presenters/decidim/meetings/admin_log/meeting_presenter.rb +5 -1
  81. data/app/presenters/decidim/meetings/meeting_presenter.rb +25 -25
  82. data/app/queries/decidim/meetings/filtered_meetings.rb +1 -1
  83. data/app/queries/decidim/meetings/metrics/meetings_metric_manage.rb +1 -1
  84. data/app/queries/decidim/meetings/questionnaire_user_answers.rb +29 -0
  85. data/app/serializers/decidim/meetings/data_portability_invite_serializer.rb +2 -1
  86. data/app/serializers/decidim/meetings/data_portability_registration_serializer.rb +2 -1
  87. data/app/serializers/decidim/meetings/registration_serializer.rb +3 -12
  88. data/app/services/decidim/meetings/calendar/component_calendar.rb +2 -2
  89. data/app/services/decidim/meetings/calendar/meeting_calendar.rb +33 -0
  90. data/app/services/decidim/meetings/calendar_renderer.rb +2 -0
  91. data/app/services/decidim/meetings/meeting_iframe_embedder.rb +86 -0
  92. data/app/services/decidim/meetings/meeting_search.rb +2 -2
  93. data/app/views/decidim/meetings/admin/agenda/_form.html.erb +1 -1
  94. data/app/views/decidim/meetings/admin/invite_join_meeting_mailer/invite.html.erb +4 -4
  95. data/app/views/decidim/meetings/admin/invites/_form.html.erb +1 -1
  96. data/app/views/decidim/meetings/admin/meeting_closes/_form.html.erb +16 -1
  97. data/app/views/decidim/meetings/admin/meeting_copies/_form.html.erb +1 -1
  98. data/app/views/decidim/meetings/admin/meetings/_form.html.erb +16 -1
  99. data/app/views/decidim/meetings/admin/meetings/index.html.erb +39 -11
  100. data/app/views/decidim/meetings/admin/poll/_answer_option.html.erb +34 -0
  101. data/app/views/decidim/meetings/admin/poll/_answer_option_template.html.erb +7 -0
  102. data/app/views/decidim/meetings/admin/poll/_form.html.erb +63 -0
  103. data/app/views/decidim/meetings/admin/poll/_question.html.erb +96 -0
  104. data/app/views/decidim/meetings/admin/poll/edit.html.erb +9 -0
  105. data/app/views/decidim/meetings/admin/registrations/_form.html.erb +1 -1
  106. data/app/views/decidim/meetings/directory/meetings/index.html.erb +4 -4
  107. data/app/views/decidim/meetings/directory/meetings/index.js.erb +2 -0
  108. data/app/views/decidim/meetings/layouts/live_event.html.erb +50 -0
  109. data/app/views/decidim/meetings/live_events/show.html.erb +12 -0
  110. data/app/views/decidim/meetings/meeting_closes/_form.html.erb +8 -1
  111. data/app/views/decidim/meetings/meeting_closes/edit.html.erb +1 -1
  112. data/app/views/decidim/meetings/meetings/_calendar_modal.html.erb +26 -0
  113. data/app/views/decidim/meetings/meetings/_filters.html.erb +1 -1
  114. data/app/views/decidim/meetings/meetings/_form.html.erb +6 -1
  115. data/app/views/decidim/meetings/meetings/_linked_meetings.html.erb +1 -1
  116. data/app/views/decidim/meetings/meetings/_meeting_minutes.html.erb +7 -18
  117. data/app/views/decidim/meetings/meetings/edit.html.erb +1 -1
  118. data/app/views/decidim/meetings/meetings/index.html.erb +4 -4
  119. data/app/views/decidim/meetings/meetings/new.html.erb +1 -1
  120. data/app/views/decidim/meetings/meetings/show.html.erb +13 -14
  121. data/app/views/decidim/meetings/polls/answers/_multiple_option.html.erb +13 -0
  122. data/app/views/decidim/meetings/polls/answers/_single_option.html.erb +19 -0
  123. data/app/views/decidim/meetings/polls/answers/create.js.erb +5 -0
  124. data/app/views/decidim/meetings/polls/questions/_closed_question.html.erb +7 -0
  125. data/app/views/decidim/meetings/polls/questions/_index.html.erb +5 -0
  126. data/app/views/decidim/meetings/polls/questions/_index_admin.html.erb +43 -0
  127. data/app/views/decidim/meetings/polls/questions/_published_question.html.erb +29 -0
  128. data/app/views/decidim/meetings/polls/questions/_question.html.erb +7 -0
  129. data/app/views/decidim/meetings/polls/questions/index.js.erb +5 -0
  130. data/app/views/decidim/meetings/polls/questions/index_admin.js.erb +5 -0
  131. data/app/views/decidim/meetings/registration_mailer/confirmation.html.erb +5 -2
  132. data/app/views/devise/mailer/join_meeting.html.erb +6 -6
  133. data/config/assets.rb +11 -0
  134. data/config/locales/ar.yml +3 -24
  135. data/config/locales/ca.yml +36 -27
  136. data/config/locales/cs.yml +72 -26
  137. data/config/locales/de.yml +22 -27
  138. data/config/locales/el.yml +3 -24
  139. data/config/locales/en.yml +112 -26
  140. data/config/locales/es-MX.yml +13 -27
  141. data/config/locales/es-PY.yml +13 -27
  142. data/config/locales/es.yml +13 -27
  143. data/config/locales/eu.yml +3 -24
  144. data/config/locales/fi-plain.yml +70 -26
  145. data/config/locales/fi.yml +70 -26
  146. data/config/locales/fr-CA.yml +19 -26
  147. data/config/locales/fr-LU.yml +534 -0
  148. data/config/locales/fr.yml +19 -26
  149. data/config/locales/gl.yml +46 -24
  150. data/config/locales/hu.yml +5 -24
  151. data/config/locales/id-ID.yml +3 -24
  152. data/config/locales/is-IS.yml +2 -22
  153. data/config/locales/it.yml +117 -23
  154. data/config/locales/ja.yml +148 -58
  155. data/config/locales/lb-LU.yml +1 -0
  156. data/config/locales/lv.yml +3 -24
  157. data/config/locales/nl.yml +70 -25
  158. data/config/locales/no.yml +5 -26
  159. data/config/locales/pl.yml +22 -33
  160. data/config/locales/pt-BR.yml +178 -23
  161. data/config/locales/pt.yml +3 -24
  162. data/config/locales/ro-RO.yml +38 -27
  163. data/config/locales/ru.yml +3 -24
  164. data/config/locales/sk.yml +3 -24
  165. data/config/locales/sv.yml +36 -27
  166. data/config/locales/tr-TR.yml +5 -27
  167. data/config/locales/uk.yml +3 -24
  168. data/config/locales/zh-CN.yml +3 -24
  169. data/db/migrate/20210217124802_add_registration_custom_content_to_meetings.rb +8 -0
  170. data/db/migrate/20210413050756_add_published_at_to_meetings.rb +7 -0
  171. data/db/migrate/20210413050917_update_published_at_to_existing_meetings.rb +20 -0
  172. data/db/migrate/20210430123416_add_public_participation_to_decidim_meetings_registrations.rb +7 -0
  173. data/db/migrate/20210506180226_merge_meetings_minutes_into_meetings_table.rb +48 -0
  174. data/db/migrate/20210512055802_create_decidim_meetings_polls.rb +10 -0
  175. data/db/migrate/20210512100333_drop_decidim_meetings_minutes_table.rb +77 -0
  176. data/db/migrate/20210518133236_merge_minutes_with_closing_report_in_meetings_table.rb +57 -0
  177. data/db/migrate/20210520084247_create_decidim_meetings_questionnaires.rb +11 -0
  178. data/db/migrate/20210520084253_create_decidim_meetings_questions.rb +15 -0
  179. data/db/migrate/20210520084321_create_decidim_meetings_answers.rb +13 -0
  180. data/db/migrate/20210520084330_create_decidim_meetings_answer_options.rb +10 -0
  181. data/db/migrate/20210520084337_create_decidim_meetings_answer_choices.rb +13 -0
  182. data/db/migrate/20210520134834_add_status_to_meetings_questions.rb +7 -0
  183. data/db/migrate/20210602040614_add_setting_embed_iframe_to_meetings.rb +7 -0
  184. data/lib/decidim/api/linked_resources_interface.rb +8 -1
  185. data/lib/decidim/api/meeting_type.rb +19 -4
  186. data/lib/decidim/api/meetings_type.rb +2 -2
  187. data/lib/decidim/meetings/admin_engine.rb +5 -1
  188. data/lib/decidim/meetings/api.rb +0 -1
  189. data/lib/decidim/meetings/component.rb +41 -6
  190. data/lib/decidim/meetings/data_portability_user_answers_serializer.rb +33 -0
  191. data/lib/decidim/meetings/engine.rb +10 -11
  192. data/lib/decidim/meetings/meeting_serializer.rb +1 -0
  193. data/lib/decidim/meetings/polls.rb +10 -0
  194. data/lib/decidim/meetings/test/factories.rb +85 -9
  195. data/lib/decidim/meetings/user_answers_serializer.rb +47 -0
  196. data/lib/decidim/meetings/version.rb +1 -1
  197. data/lib/decidim/meetings.rb +10 -0
  198. metadata +103 -113
  199. data/app/assets/config/decidim_meetings_manifest.js +0 -9
  200. data/app/assets/javascripts/decidim/meetings/admin/agendas.js.es6 +0 -158
  201. data/app/assets/javascripts/decidim/meetings/meetings_form.js.es6 +0 -54
  202. data/app/commands/decidim/meetings/admin/create_minutes.rb +0 -55
  203. data/app/commands/decidim/meetings/admin/update_minutes.rb +0 -63
  204. data/app/controllers/decidim/meetings/admin/minutes_controller.rb +0 -69
  205. data/app/forms/decidim/meetings/admin/minutes_form.rb +0 -20
  206. data/app/models/decidim/meetings/minutes.rb +0 -22
  207. data/app/presenters/decidim/meetings/admin_log/minutes_presenter.rb +0 -42
  208. data/app/views/decidim/meetings/admin/minutes/_form.html.erb +0 -23
  209. data/app/views/decidim/meetings/admin/minutes/edit.html.erb +0 -7
  210. data/app/views/decidim/meetings/admin/minutes/new.html.erb +0 -7
  211. data/app/views/decidim/meetings/meetings/_online_meeting_link.html.erb +0 -11
  212. data/config/locales/ja-JP.yml +0 -494
  213. data/lib/decidim/api/minutes_type.rb +0 -20
@@ -0,0 +1,178 @@
1
+ .meeting-polls{
2
+ &__main{
3
+ flex: 1;
4
+ display: flex;
5
+ }
6
+
7
+ &__iframe{
8
+ @extend .bg-muted;
9
+
10
+ display: flex;
11
+
12
+ > *{
13
+ flex: 1;
14
+ border: 0;
15
+ }
16
+ }
17
+
18
+ &__aside{
19
+ flex: 0;
20
+ counter-reset: question;
21
+
22
+ & + div{
23
+ flex: 1;
24
+ }
25
+
26
+ &.is-open{
27
+ flex: 0 0 20%;
28
+
29
+ & + div{
30
+ flex: 0 0 80%;
31
+ }
32
+
33
+ // shrink iframe when there's two panels
34
+ & + & + div{
35
+ flex: 0 0 60%;
36
+ }
37
+
38
+ & + &{
39
+ border-left: 1px solid #e8e8e8;
40
+ }
41
+ }
42
+ }
43
+
44
+ &__question{
45
+ background-color: $white;
46
+ border-bottom: 1px solid #e8e8e8;
47
+
48
+ summary{
49
+ @extend .padding-1;
50
+
51
+ cursor: pointer;
52
+ font-weight: bold;
53
+ color: var(--secondary);
54
+ transition: background-color .2s ease-in-out;
55
+
56
+ &:hover{
57
+ background-color: #f4f4f4;
58
+ }
59
+
60
+ &::after{
61
+ counter-increment: question;
62
+ content: "#" counter(question);
63
+ }
64
+
65
+ & ~ *{
66
+ padding: 0 1rem 1rem;
67
+
68
+ // dynamic content
69
+ > :first-child{
70
+ font-weight: 600;
71
+ line-height: 1.25;
72
+ }
73
+
74
+ label{
75
+ display: flex;
76
+ align-items: baseline;
77
+ cursor: pointer;
78
+ line-height: 1.25;
79
+ }
80
+
81
+ label + label{
82
+ margin-top: $global_margin;
83
+ }
84
+ // end dynamic content
85
+ }
86
+ }
87
+
88
+ &[open] .meeting-polls__answer--bar > *{
89
+ opacity: 1;
90
+ transform: translateX(0);
91
+ }
92
+
93
+ &.is-new{
94
+ animation: animateHighlight 5s ease-in-out forwards;
95
+ }
96
+
97
+ @keyframes animateHighlight{
98
+ 0%,
99
+ 80%{
100
+ background-color: rgba(var(--warning-rgb), .1);
101
+ }
102
+ }
103
+ }
104
+
105
+ &__question--admin{
106
+ @extend .meeting-polls__question;
107
+
108
+ summary ~ *{
109
+ border-top: 1px solid #e8e8e8;
110
+ background-color: #f6f6f6;
111
+ padding: 1rem;
112
+
113
+ a{
114
+ text-decoration: underline;
115
+ margin-bottom: $global_margin;
116
+ display: block;
117
+ }
118
+
119
+ > :first-child{
120
+ font-weight: normal;
121
+ }
122
+ }
123
+ }
124
+
125
+ &__answer{
126
+ display: flex;
127
+ align-items: center;
128
+
129
+ &--value{
130
+ flex: 0 0 15%;
131
+ font-weight: bold;
132
+ }
133
+
134
+ &--bar{
135
+ flex: 0 0 85%;
136
+ height: 10px;
137
+ overflow: hidden;
138
+
139
+ > *{
140
+ background-color: var(--primary);
141
+ height: 100%;
142
+ opacity: 0;
143
+ }
144
+ }
145
+ }
146
+
147
+ // vertical flow
148
+ label + &__answer + label{
149
+ margin-top: 1.5 * $global_margin;
150
+ }
151
+
152
+ &__admin-label{
153
+ background-color: #9675ce;
154
+ color: $white;
155
+ font-weight: bold;
156
+ font-size: 14px;
157
+ padding: 1rem;
158
+ }
159
+
160
+ &__admin-action{
161
+ border-top: 1px solid #e8e8e8;
162
+ padding: .5rem 0;
163
+ display: flex;
164
+ align-items: center;
165
+
166
+ &:last-child{
167
+ border-bottom: 1px solid #e8e8e8;
168
+ }
169
+
170
+ :first-child{
171
+ flex: 0 0 40%;
172
+ }
173
+
174
+ :last-child{
175
+ flex: 0 0 60%;
176
+ }
177
+ }
178
+ }
@@ -0,0 +1,2 @@
1
+ @import "main";
2
+ @import "header";
@@ -11,7 +11,8 @@ module Decidim
11
11
  allowed_registration_form_action?
12
12
  allowed_meeting_action?
13
13
  allowed_agenda_action?
14
- allowed_minutes_action?
14
+ allowed_poll_action?
15
+ allowed_export_answers?
15
16
 
16
17
  permission_action
17
18
  end
@@ -26,8 +27,8 @@ module Decidim
26
27
  @agenda ||= context.fetch(:agenda, nil)
27
28
  end
28
29
 
29
- def minutes
30
- @minutes ||= context.fetch(:minutes, nil)
30
+ def poll
31
+ @poll ||= context.fetch(:poll, nil)
31
32
  end
32
33
 
33
34
  def registration_form
@@ -69,14 +70,21 @@ module Decidim
69
70
  end
70
71
  end
71
72
 
72
- def allowed_minutes_action?
73
- return unless permission_action.subject == :minutes
73
+ def allowed_poll_action?
74
+ return unless permission_action.subject == :poll
74
75
 
75
76
  case permission_action.action
76
- when :create
77
- toggle_allow(meeting.present?)
78
77
  when :update
79
- toggle_allow(minutes.present? && meeting.present?)
78
+ toggle_allow(poll.present? && meeting.present?)
79
+ end
80
+ end
81
+
82
+ def allowed_export_answers?
83
+ return unless permission_action.subject == :questionnaire
84
+
85
+ case permission_action.action
86
+ when :export_answers
87
+ permission_action.allow!
80
88
  end
81
89
  end
82
90
  end
@@ -10,23 +10,36 @@ module Decidim
10
10
  return Decidim::Meetings::Admin::Permissions.new(user, permission_action, context).permissions if permission_action.scope == :admin
11
11
  return permission_action if permission_action.scope != :public
12
12
 
13
- return permission_action if permission_action.subject != :meeting
14
-
15
- case permission_action.action
16
- when :join
17
- toggle_allow(can_join_meeting?)
18
- when :leave
19
- toggle_allow(can_leave_meeting?)
20
- when :decline_invitation
21
- toggle_allow(can_decline_invitation?)
22
- when :create
23
- toggle_allow(can_create_meetings?)
24
- when :update
25
- toggle_allow(can_update_meeting?)
26
- when :close
27
- toggle_allow(can_close_meeting?)
28
- when :register
29
- toggle_allow(can_register_invitation_meeting?)
13
+ case permission_action.subject
14
+ when :answer
15
+ case permission_action.action
16
+ when :create
17
+ toggle_allow(can_answer_question?)
18
+ end
19
+ when :question
20
+ case permission_action.action
21
+ when :update
22
+ toggle_allow(can_update_question?)
23
+ end
24
+ when :meeting
25
+ case permission_action.action
26
+ when :join
27
+ toggle_allow(can_join_meeting?)
28
+ when :leave
29
+ toggle_allow(can_leave_meeting?)
30
+ when :decline_invitation
31
+ toggle_allow(can_decline_invitation?)
32
+ when :create
33
+ toggle_allow(can_create_meetings?)
34
+ when :update
35
+ toggle_allow(can_update_meeting?)
36
+ when :close
37
+ toggle_allow(can_close_meeting?)
38
+ when :register
39
+ toggle_allow(can_register_invitation_meeting?)
40
+ end
41
+ else
42
+ return permission_action
30
43
  end
31
44
 
32
45
  permission_action
@@ -38,6 +51,10 @@ module Decidim
38
51
  @meeting ||= context.fetch(:meeting, nil)
39
52
  end
40
53
 
54
+ def question
55
+ @question ||= context.fetch(:question, nil)
56
+ end
57
+
41
58
  def can_join_meeting?
42
59
  meeting.can_be_joined_by?(user) &&
43
60
  authorized?(:join, resource: meeting)
@@ -73,6 +90,14 @@ module Decidim
73
90
  meeting.can_register_invitation?(user) &&
74
91
  authorized?(:register, resource: meeting)
75
92
  end
93
+
94
+ def can_answer_question?
95
+ question.present? && user.present? && !question.answered_by?(user)
96
+ end
97
+
98
+ def can_update_question?
99
+ user.present? && user.admin? && question.present?
100
+ end
76
101
  end
77
102
  end
78
103
  end
@@ -33,7 +33,11 @@ module Decidim
33
33
  start_date: :date,
34
34
  title: "Decidim::Meetings::AdminLog::ValueTypes::MeetingTitleDescriptionPresenter",
35
35
  private_meeting: :boolean,
36
- transparent: :boolean
36
+ transparent: :boolean,
37
+ published_at: :date,
38
+ video_url: :string,
39
+ audio_url: :string,
40
+ closing_visible: :boolean
37
41
  }
38
42
  end
39
43
 
@@ -5,22 +5,17 @@ module Decidim
5
5
  #
6
6
  # Decorator for meetings
7
7
  #
8
- class MeetingPresenter < SimpleDelegator
9
- include Decidim::TranslationsHelper
8
+ class MeetingPresenter < Decidim::ResourcePresenter
10
9
  include Decidim::ResourceHelper
11
- include Decidim::SanitizeHelper
12
10
 
13
11
  def meeting
14
12
  __getobj__
15
13
  end
16
14
 
17
- def title(links: false, all_locales: false)
15
+ def title(links: false, html_escape: false, all_locales: false)
18
16
  return unless meeting
19
17
 
20
- handle_locales(meeting.title, all_locales) do |content|
21
- renderer = Decidim::ContentRenderers::HashtagRenderer.new(decidim_html_escape(content))
22
- renderer.render(links: links).html_safe
23
- end
18
+ super meeting.title, links, html_escape, all_locales
24
19
  end
25
20
 
26
21
  def description(links: false, all_locales: false)
@@ -65,6 +60,26 @@ module Decidim
65
60
  end
66
61
  end
67
62
 
63
+ def registration_email_custom_content(links: false, all_locales: false)
64
+ return unless meeting
65
+
66
+ handle_locales(meeting.registration_email_custom_content, all_locales) do |content|
67
+ renderer = Decidim::ContentRenderers::HashtagRenderer.new(decidim_sanitize(content))
68
+ renderer.render(links: links).html_safe
69
+ end
70
+ end
71
+
72
+ # start time and end time in rfc3339 format removing '-' and ':' symbols
73
+ # joined with '/'. This format is used to generate the dates query param
74
+ # in google calendars event link
75
+ def dates_param
76
+ return unless meeting
77
+
78
+ [meeting.start_time, meeting.end_time].map do |date|
79
+ date.rfc3339.tr("-:", "")
80
+ end.join("/")
81
+ end
82
+
68
83
  # Next methods are used for present a Meeting As Proposal Author
69
84
  def name
70
85
  title
@@ -83,7 +98,7 @@ module Decidim
83
98
  end
84
99
 
85
100
  def avatar_url
86
- ActionController::Base.helpers.asset_path("decidim/meetings/icon.svg")
101
+ ActionController::Base.helpers.asset_pack_path("media/images/decidim_meetings.svg")
87
102
  end
88
103
 
89
104
  def deleted?
@@ -99,6 +114,7 @@ module Decidim
99
114
  end
100
115
 
101
116
  def proposals
117
+ return unless Decidim::Meetings.enable_proposal_linking
102
118
  return unless meeting
103
119
 
104
120
  @proposals ||= meeting.authored_proposals.load
@@ -109,22 +125,6 @@ module Decidim
109
125
 
110
126
  proposals.map.with_index { |proposal, index| "#{index + 1}) #{proposal.title}\n" }
111
127
  end
112
-
113
- private
114
-
115
- def handle_locales(content, all_locales, &block)
116
- if all_locales
117
- content.each_with_object({}) do |(key, value), parsed_content|
118
- parsed_content[key] = if key == "machine_translations"
119
- handle_locales(value, all_locales, &block)
120
- else
121
- block.call(value)
122
- end
123
- end
124
- else
125
- yield(translated_attribute(content))
126
- end
127
- end
128
128
  end
129
129
  end
130
130
  end
@@ -27,7 +27,7 @@ module Decidim
27
27
  # Finds the Projects scoped to an array of components and filtered
28
28
  # by a range of dates.
29
29
  def query
30
- meetings = Decidim::Meetings::Meeting.not_hidden.where(component: @components)
30
+ meetings = Decidim::Meetings::Meeting.not_hidden.published.where(component: @components)
31
31
  meetings = meetings.where("created_at >= ?", @start_at) if @start_at.present?
32
32
  meetings = meetings.where("created_at <= ?", @end_at) if @end_at.present?
33
33
  meetings
@@ -30,7 +30,7 @@ module Decidim
30
30
  spaces = Decidim.participatory_space_manifests.flat_map do |manifest|
31
31
  manifest.participatory_spaces.call(@organization).public_spaces
32
32
  end
33
- @query = Decidim::Meetings::Meeting.where(component: visible_component_ids_from_spaces(spaces)).joins(:component)
33
+ @query = Decidim::Meetings::Meeting.where(component: visible_components_from_spaces(spaces)).joins(:component)
34
34
  .left_outer_joins(:category).visible
35
35
  @query = @query.where("decidim_meetings_meetings.created_at <= ?", end_time)
36
36
  @query = @query.group("decidim_categorizations.decidim_category_id",
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Meetings
5
+ # A class used to collect user answers for a questionnaire
6
+ class QuestionnaireUserAnswers < Rectify::Query
7
+ # Syntactic sugar to initialize the class and return the queried objects.
8
+ #
9
+ # questionnaire - a Questionnaire object
10
+ def self.for(questionnaire)
11
+ new(questionnaire).query
12
+ end
13
+
14
+ # Initializes the class.
15
+ #
16
+ # questionnaire = a Questionnaire object
17
+ def initialize(questionnaire)
18
+ @questionnaire = questionnaire
19
+ end
20
+
21
+ # Finds and group answers by user for each questionnaire's question.
22
+ def query
23
+ answers = Answer.joins(:question).where(questionnaire: @questionnaire)
24
+
25
+ answers.sort_by { |answer| answer.question.position }.group_by(&:user).values
26
+ end
27
+ end
28
+ end
29
+ end
@@ -26,7 +26,8 @@ module Decidim
26
26
  attendees_count: resource.meeting.attendees_count,
27
27
  attending_organizations: resource.meeting.attending_organizations,
28
28
  closed_at: resource.meeting.closed_at,
29
- closing_report: resource.meeting.closing_report
29
+ closing_report: resource.meeting.closing_report,
30
+ published_at: resource.meeting.published_at
30
31
  }
31
32
  }
32
33
  end
@@ -24,7 +24,8 @@ module Decidim
24
24
  attendees_count: resource.meeting.attendees_count,
25
25
  attending_organizations: resource.meeting.attending_organizations,
26
26
  closed_at: resource.meeting.closed_at,
27
- closing_report: resource.meeting.closing_report
27
+ closing_report: resource.meeting.closing_report,
28
+ published_at: resource.meeting.published_at
28
29
  }
29
30
  }
30
31
  end
@@ -21,18 +21,9 @@ module Decidim
21
21
  private
22
22
 
23
23
  def serialize_answers
24
- questions = resource.meeting.questionnaire.questions
25
- answers = resource.meeting.questionnaire.answers.where(user: resource.user)
26
- questions.each_with_index.inject({}) do |serialized, (question, idx)|
27
- answer = answers.find_by(question: question)
28
- serialized.update("#{idx + 1}. #{translated_attribute(question.body)}" => normalize_body(answer))
29
- end
30
- end
31
-
32
- def normalize_body(answer)
33
- return "" unless answer
34
-
35
- answer.body || answer.choices.pluck(:body)
24
+ Decidim::Forms::UserAnswersSerializer.new(
25
+ resource.meeting.questionnaire.answers.where(user: resource.user)
26
+ ).serialize
36
27
  end
37
28
  end
38
29
  end
@@ -13,8 +13,8 @@ module Decidim
13
13
  def events
14
14
  Rails.cache.fetch(cache_key) do
15
15
  meetings.map do |meeting|
16
- MeetingToEvent.new(meeting).to_ical
17
- end.join
16
+ MeetingCalendar.new(meeting).events
17
+ end.compact.join
18
18
  end
19
19
  end
20
20
 
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Meetings
5
+ module Calendar
6
+ # This class handles how to convert a single meeting to the ICalendar
7
+ # format. It caches its result until the meeting is updated again.
8
+ # This class generates a ICalendar entry for an individual meeting.
9
+ class MeetingCalendar < BaseCalendar
10
+ # Renders the meeting in an ICalendar format. It caches the results in
11
+ # Rails' cache.
12
+ #
13
+ # Returns a String.
14
+ def events
15
+ Rails.cache.fetch(cache_key) do
16
+ MeetingToEvent.new(meeting).to_ical
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ alias meeting resource
23
+
24
+ # Defines the cache key for the given component.
25
+ #
26
+ # Returns a String.
27
+ def cache_key
28
+ "meetings-calendar-meeting-#{meeting.id}-#{meeting.updated_at.to_i}"
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -9,6 +9,8 @@ module Decidim
9
9
  Calendar::OrganizationCalendar.for(resource)
10
10
  when Decidim::Component
11
11
  Calendar::ComponentCalendar.for(resource)
12
+ when Decidim::Meetings::Meeting
13
+ Calendar::MeetingCalendar.for(resource)
12
14
  end
13
15
  end
14
16
  end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cgi"
4
+
5
+ module Decidim
6
+ module Meetings
7
+ # This class handles the streaming url to be included in the iframe present
8
+ # in the live event. For some services it's required to transforma a bit
9
+ # the structure of the URL.
10
+ class MeetingIframeEmbedder
11
+ # Public: Initializes the service.
12
+ # online_meeting_service_url - A String containing the url of the online meeting
13
+ def initialize(online_meeting_service_url)
14
+ @online_meeting_service_url = online_meeting_service_url
15
+ end
16
+
17
+ def embed_transformed_url(request_host)
18
+ return nil if parsed_online_meeting_uri.nil?
19
+
20
+ case parsed_online_meeting_uri.host
21
+ when "www.youtube.com"
22
+ transform_youtube_url(parsed_online_meeting_uri)
23
+ when "www.twitch.tv"
24
+ transform_twitch_url(parsed_online_meeting_uri, request_host)
25
+ else
26
+ online_meeting_service_url
27
+ end
28
+ end
29
+
30
+ def embeddable?
31
+ return nil if parsed_online_meeting_uri.nil?
32
+
33
+ EMBEDDABLE_SERVICES.include?(parsed_online_meeting_uri.host)
34
+ end
35
+
36
+ def embed_code(request_host)
37
+ return nil if parsed_online_meeting_uri.nil?
38
+
39
+ %(
40
+ <iframe
41
+ allow="camera; microphone; fullscreen; display-capture; autoplay"
42
+ loading="lazy"
43
+ src="#{embed_transformed_url(request_host)}"
44
+ style="height: 100%; width: 100%; border: 0px;"
45
+ />
46
+ )
47
+ end
48
+
49
+ private
50
+
51
+ EMBEDDABLE_SERVICES = %( www.youtube.com www.twitch.tv )
52
+
53
+ attr_accessor :online_meeting_service_url
54
+
55
+ # Youtube transformation consists on:
56
+ # 1. extract the video id from the parameter v
57
+ # 2. Create a new URL using the domain youtube-nocookie.com, converting it to an embed
58
+ # and appending the video id
59
+ def transform_youtube_url(uri)
60
+ return online_meeting_service_url if uri.query.blank?
61
+
62
+ video_id = CGI.parse(uri.query).fetch("v")&.first
63
+
64
+ return online_meeting_service_url if video_id.blank?
65
+
66
+ "https://www.youtube-nocookie.com/embed/#{video_id}"
67
+ end
68
+
69
+ # Twitch transformation consists on:
70
+ # 1. extract the video id from the third URL parameter
71
+ # 2. extract the request host
72
+ # 3. build the embed url using both the video ID and the request host as parent argument
73
+ def transform_twitch_url(uri, request_host)
74
+ _, param_name, video_id = *uri.path.split("/")
75
+
76
+ return online_meeting_service_url if video_id.blank? || param_name != "videos"
77
+
78
+ "https://player.twitch.tv/?video=#{video_id}&parent=#{request_host}"
79
+ end
80
+
81
+ def parsed_online_meeting_uri
82
+ @parsed_online_meeting_uri ||= URI.parse(online_meeting_service_url) if online_meeting_service_url.present?
83
+ end
84
+ end
85
+ end
86
+ end
@@ -11,9 +11,9 @@ module Decidim
11
11
  # Public: Initializes the service.
12
12
  # component - A Decidim::Component to get the meetings from.
13
13
  # page - The page number to paginate the results.
14
- # per_page - The number of proposals to return per page.
14
+ # per_page - The number of meetings to return per page.
15
15
  def initialize(options = {})
16
- scope = options.fetch(:scope, Meeting.all)
16
+ scope = options.fetch(:scope, Meeting.published)
17
17
  super(scope, options)
18
18
  end
19
19
 
@@ -65,4 +65,4 @@
65
65
  </div>
66
66
  </div>
67
67
 
68
- <%= javascript_include_tag "decidim/meetings/admin/agendas" %>
68
+ <%= javascript_pack_tag "decidim_meetings_admin" %>
@@ -1,8 +1,8 @@
1
- <p><%= t("devise.mailer.invitation_instructions.hello", email: @user.name) %></p>
1
+ <p class="email-greeting"><%= t("devise.mailer.invitation_instructions.hello", email: @user.name) %></p>
2
2
 
3
- <p>
3
+ <p class="email-instructions">
4
4
  <%= t ".invited_you_to_join_a_meeting", invited_by: @invited_by.name, application: @user.organization.name %>
5
5
  </p>
6
6
 
7
- <p><%= link_to t(".decline", meeting_title: present(@meeting).title),routes.decline_invitation_meeting_registration_url(meeting_id: @meeting, participatory_space_id: @meeting.component.participatory_space) %>
8
- <p><%= link_to t(".join", meeting_title: present(@meeting).title),routes.meeting_registration_url(meeting_id: @meeting, participatory_space_id: @meeting.component.participatory_space) %>
7
+ <p class="email-button email-button__cta cta-decline"><%= link_to t(".decline", meeting_title: present(@meeting).title),routes.decline_invitation_meeting_registration_url(meeting_id: @meeting, participatory_space_id: @meeting.component.participatory_space) %>
8
+ <p class="email-button email-button__cta cta-accept"><%= link_to t(".join", meeting_title: present(@meeting).title),routes.meeting_registration_url(meeting_id: @meeting, participatory_space_id: @meeting.component.participatory_space) %>