decidim-meetings 0.26.9 → 0.27.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (174) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/meetings/content_blocks/highlighted_meetings_cell.rb +1 -1
  3. data/app/cells/decidim/meetings/content_blocks/upcoming_meetings/show.erb +2 -2
  4. data/app/cells/decidim/meetings/content_blocks/upcoming_meetings_cell.rb +4 -12
  5. data/app/cells/decidim/meetings/highlighted_meetings_for_component_cell.rb +10 -1
  6. data/app/cells/decidim/meetings/meeting_list_item_cell.rb +1 -1
  7. data/app/cells/decidim/meetings/meeting_m_cell.rb +1 -1
  8. data/app/cells/decidim/meetings/meeting_s_cell.rb +6 -0
  9. data/app/commands/decidim/meetings/admin/close_meeting.rb +1 -1
  10. data/app/commands/decidim/meetings/admin/copy_meeting.rb +5 -24
  11. data/app/commands/decidim/meetings/admin/create_agenda.rb +1 -1
  12. data/app/commands/decidim/meetings/admin/create_meeting.rb +1 -5
  13. data/app/commands/decidim/meetings/admin/destroy_meeting.rb +1 -1
  14. data/app/commands/decidim/meetings/admin/export_meeting_registrations.rb +1 -1
  15. data/app/commands/decidim/meetings/admin/invite_user_to_join_meeting.rb +9 -11
  16. data/app/commands/decidim/meetings/admin/publish_meeting.rb +2 -2
  17. data/app/commands/decidim/meetings/admin/unpublish_meeting.rb +1 -1
  18. data/app/commands/decidim/meetings/admin/update_agenda.rb +1 -1
  19. data/app/commands/decidim/meetings/admin/update_meeting.rb +1 -5
  20. data/app/commands/decidim/meetings/admin/update_question_status.rb +1 -1
  21. data/app/commands/decidim/meetings/admin/update_questionnaire.rb +10 -7
  22. data/app/commands/decidim/meetings/admin/update_registrations.rb +5 -3
  23. data/app/commands/decidim/meetings/admin/validate_registration_code.rb +1 -1
  24. data/app/commands/decidim/meetings/close_meeting.rb +1 -1
  25. data/app/commands/decidim/meetings/create_answer.rb +1 -1
  26. data/app/commands/decidim/meetings/create_meeting.rb +1 -1
  27. data/app/commands/decidim/meetings/decline_invitation.rb +1 -1
  28. data/app/commands/decidim/meetings/join_meeting.rb +6 -7
  29. data/app/commands/decidim/meetings/leave_meeting.rb +3 -3
  30. data/app/commands/decidim/meetings/update_meeting.rb +1 -1
  31. data/app/commands/decidim/meetings/withdraw_meeting.rb +1 -1
  32. data/app/controllers/concerns/decidim/meetings/admin/filterable.rb +4 -4
  33. data/app/controllers/concerns/decidim/meetings/filterable.rb +1 -7
  34. data/app/controllers/decidim/meetings/admin/meeting_copies_controller.rb +2 -2
  35. data/app/controllers/decidim/meetings/calendars_controller.rb +1 -1
  36. data/app/controllers/decidim/meetings/directory/meetings_controller.rb +17 -16
  37. data/app/controllers/decidim/meetings/meetings_controller.rb +21 -15
  38. data/app/events/decidim/meetings/meeting_registration_notification_event.rb +1 -1
  39. data/app/forms/decidim/meetings/admin/close_meeting_form.rb +1 -1
  40. data/app/forms/decidim/meetings/admin/meeting_copy_form.rb +51 -0
  41. data/app/forms/decidim/meetings/admin/meeting_form.rb +11 -15
  42. data/app/forms/decidim/meetings/admin/meeting_registration_invite_form.rb +1 -1
  43. data/app/forms/decidim/meetings/admin/meeting_registrations_form.rb +3 -0
  44. data/app/forms/decidim/meetings/answer_form.rb +0 -1
  45. data/app/forms/decidim/meetings/close_meeting_form.rb +1 -1
  46. data/app/forms/decidim/meetings/meeting_form.rb +2 -2
  47. data/app/helpers/decidim/meetings/application_helper.rb +10 -19
  48. data/app/helpers/decidim/meetings/directory/application_helper.rb +8 -6
  49. data/app/helpers/decidim/meetings/meetings_helper.rb +1 -3
  50. data/app/jobs/decidim/meetings/send_close_meeting_reminder_job.rb +17 -0
  51. data/app/mailers/decidim/meetings/close_meeting_reminder_mailer.rb +43 -0
  52. data/app/models/decidim/meetings/agenda_item.rb +1 -1
  53. data/app/models/decidim/meetings/answer.rb +2 -2
  54. data/app/models/decidim/meetings/invite.rb +2 -2
  55. data/app/models/decidim/meetings/meeting.rb +36 -21
  56. data/app/models/decidim/meetings/questionnaire.rb +6 -0
  57. data/app/models/decidim/meetings/registration.rb +2 -2
  58. data/app/packs/src/decidim/meetings/admin/meetings_form.js +0 -4
  59. data/app/packs/src/decidim/meetings/admin/registrations_form.js +2 -0
  60. data/app/presenters/decidim/meetings/admin_log/questionnaire_presenter.rb +39 -0
  61. data/app/presenters/decidim/meetings/meeting_presenter.rb +6 -1
  62. data/app/queries/decidim/meetings/admin/invites.rb +1 -1
  63. data/app/queries/decidim/meetings/filtered_meetings.rb +1 -1
  64. data/app/queries/decidim/meetings/questionnaire_user_answers.rb +1 -1
  65. data/app/serializers/decidim/meetings/{data_portability_invite_serializer.rb → download_your_data_invite_serializer.rb} +2 -2
  66. data/app/serializers/decidim/meetings/{data_portability_registration_serializer.rb → download_your_data_registration_serializer.rb} +2 -2
  67. data/app/services/decidim/meetings/calendar/base_calendar.rb +4 -3
  68. data/app/services/decidim/meetings/calendar/component_calendar.rb +7 -9
  69. data/app/services/decidim/meetings/calendar/organization_calendar.rb +1 -1
  70. data/app/services/decidim/meetings/calendar_renderer.rb +4 -4
  71. data/app/services/decidim/meetings/close_meeting_reminder_generator.rb +68 -0
  72. data/app/services/decidim/meetings/meeting_iframe_embedder.rb +10 -6
  73. data/app/services/decidim/meetings/meeting_search.rb +7 -53
  74. data/app/views/decidim/meetings/_calendar_modal.html.erb +19 -2
  75. data/app/views/decidim/meetings/admin/meeting_copies/_form.html.erb +64 -0
  76. data/app/views/decidim/meetings/admin/meeting_copies/new.html.erb +1 -1
  77. data/app/views/decidim/meetings/admin/meetings/_form.html.erb +5 -17
  78. data/app/views/decidim/meetings/admin/meetings/edit.html.erb +0 -1
  79. data/app/views/decidim/meetings/admin/meetings/index.html.erb +1 -2
  80. data/app/views/decidim/meetings/admin/meetings/new.html.erb +0 -1
  81. data/app/views/decidim/meetings/admin/registrations/_form.html.erb +10 -1
  82. data/app/views/decidim/meetings/close_meeting_reminder_mailer/close_meeting_reminder.html.erb +5 -0
  83. data/app/views/decidim/meetings/directory/meetings/_filters.html.erb +7 -7
  84. data/app/views/decidim/meetings/directory/meetings/_meetings.html.erb +6 -1
  85. data/app/views/decidim/meetings/directory/meetings/index.html.erb +1 -1
  86. data/app/views/decidim/meetings/directory/meetings/index.js.erb +7 -1
  87. data/app/views/decidim/meetings/layouts/live_event.html.erb +6 -2
  88. data/app/views/decidim/meetings/meetings/_count.html.erb +1 -1
  89. data/app/views/decidim/meetings/meetings/_filters.html.erb +7 -7
  90. data/app/views/decidim/meetings/meetings/_form.html.erb +4 -2
  91. data/app/views/decidim/meetings/meetings/_linked_meetings.html.erb +1 -1
  92. data/app/views/decidim/meetings/meetings/_meetings.html.erb +11 -6
  93. data/app/views/decidim/meetings/meetings/index.html.erb +3 -3
  94. data/app/views/decidim/meetings/meetings/index.js.erb +2 -2
  95. data/app/views/decidim/participatory_spaces/_conference_venues.html.erb +0 -3
  96. data/config/locales/am-ET.yml +1 -0
  97. data/config/locales/ar.yml +12 -258
  98. data/config/locales/bg.yml +7 -0
  99. data/config/locales/ca.yml +39 -34
  100. data/config/locales/cs.yml +40 -35
  101. data/config/locales/da.yml +1 -0
  102. data/config/locales/de.yml +24 -109
  103. data/config/locales/el.yml +12 -229
  104. data/config/locales/en.yml +33 -29
  105. data/config/locales/eo.yml +1 -0
  106. data/config/locales/es-MX.yml +42 -37
  107. data/config/locales/es-PY.yml +39 -34
  108. data/config/locales/es.yml +41 -36
  109. data/config/locales/et.yml +1 -0
  110. data/config/locales/eu.yml +208 -253
  111. data/config/locales/fi-plain.yml +42 -35
  112. data/config/locales/fi.yml +53 -46
  113. data/config/locales/fr-CA.yml +38 -33
  114. data/config/locales/fr.yml +37 -32
  115. data/config/locales/ga-IE.yml +8 -12
  116. data/config/locales/gl.yml +32 -6
  117. data/config/locales/hr.yml +1 -0
  118. data/config/locales/hu.yml +24 -149
  119. data/config/locales/id-ID.yml +12 -4
  120. data/config/locales/is-IS.yml +9 -10
  121. data/config/locales/it.yml +24 -20
  122. data/config/locales/ja.yml +43 -36
  123. data/config/locales/ko.yml +1 -0
  124. data/config/locales/lb.yml +21 -18
  125. data/config/locales/lt.yml +1 -681
  126. data/config/locales/lv.yml +12 -4
  127. data/config/locales/mt.yml +1 -0
  128. data/config/locales/nl.yml +68 -90
  129. data/config/locales/no.yml +21 -14
  130. data/config/locales/om-ET.yml +1 -0
  131. data/config/locales/pl.yml +16 -41
  132. data/config/locales/pt-BR.yml +17 -64
  133. data/config/locales/pt.yml +18 -16
  134. data/config/locales/ro-RO.yml +27 -44
  135. data/config/locales/ru.yml +13 -7
  136. data/config/locales/si-LK.yml +1 -0
  137. data/config/locales/sk.yml +13 -5
  138. data/config/locales/sl.yml +4 -3
  139. data/config/locales/so-SO.yml +1 -0
  140. data/config/locales/sr-CS.yml +1 -0
  141. data/config/locales/sv.yml +26 -25
  142. data/config/locales/sw-KE.yml +1 -0
  143. data/config/locales/ti-ER.yml +1 -0
  144. data/config/locales/tr-TR.yml +14 -18
  145. data/config/locales/uk.yml +13 -7
  146. data/config/locales/val-ES.yml +1 -0
  147. data/config/locales/vi.yml +1 -0
  148. data/config/locales/zh-CN.yml +12 -12
  149. data/config/locales/zh-TW.yml +1 -649
  150. data/db/migrate/20210512100333_drop_decidim_meetings_minutes_table.rb +2 -2
  151. data/db/migrate/20210518133236_merge_minutes_with_closing_report_in_meetings_table.rb +1 -1
  152. data/db/migrate/20211105115625_remove_not_null_on_customize_registration_email.rb +7 -0
  153. data/lib/decidim/api/meeting_type.rb +1 -1
  154. data/lib/decidim/meetings/component.rb +7 -7
  155. data/lib/decidim/meetings/{data_portability_user_answers_serializer.rb → download_your_data_user_answers_serializer.rb} +2 -2
  156. data/lib/decidim/meetings/engine.rb +17 -4
  157. data/lib/decidim/meetings/meeting_serializer.rb +2 -2
  158. data/lib/decidim/meetings/test/factories.rb +3 -1
  159. data/lib/decidim/meetings/test/notifications_handling.rb +1 -1
  160. data/lib/decidim/meetings/test/translated_event.rb +2 -2
  161. data/lib/decidim/meetings/user_answers_serializer.rb +1 -1
  162. data/lib/decidim/meetings/version.rb +1 -1
  163. data/lib/decidim/meetings.rb +5 -1
  164. metadata +29 -31
  165. data/app/services/decidim/meetings/directory/meeting_search.rb +0 -53
  166. data/config/environment.rb +0 -3
  167. data/config/locales/fa-IR.yml +0 -1
  168. data/config/locales/gn-PY.yml +0 -1
  169. data/config/locales/ka-GE.yml +0 -1
  170. data/config/locales/kaa.yml +0 -7
  171. data/config/locales/lo-LA.yml +0 -1
  172. data/config/locales/oc-FR.yml +0 -1
  173. data/config/locales/sq-AL.yml +0 -1
  174. data/config/locales/th-TH.yml +0 -1
@@ -4,6 +4,8 @@ module Decidim
4
4
  module Meetings
5
5
  # The data store for a Questionnaire in the Decidim::Meetings component.
6
6
  class Questionnaire < Meetings::ApplicationRecord
7
+ include Decidim::Traceable
8
+
7
9
  belongs_to :questionnaire_for, polymorphic: true
8
10
 
9
11
  has_many :questions, -> { order(:position) }, class_name: "Question", foreign_key: "decidim_questionnaire_id", dependent: :destroy
@@ -18,6 +20,10 @@ module Decidim
18
20
  def all_questions_unpublished?
19
21
  questions.all?(&:unpublished?)
20
22
  end
23
+
24
+ def self.log_presenter_class_for(_log)
25
+ Decidim::Meetings::AdminLog::QuestionnairePresenter
26
+ end
21
27
  end
22
28
  end
23
29
  end
@@ -4,7 +4,7 @@ module Decidim
4
4
  module Meetings
5
5
  # The data store for a Registration in the Decidim::Meetings component.
6
6
  class Registration < Meetings::ApplicationRecord
7
- include Decidim::DataPortability
7
+ include Decidim::DownloadYourData
8
8
 
9
9
  belongs_to :meeting, foreign_key: "decidim_meeting_id", class_name: "Decidim::Meetings::Meeting"
10
10
  belongs_to :user, foreign_key: "decidim_user_id", class_name: "Decidim::User"
@@ -23,7 +23,7 @@ module Decidim
23
23
  end
24
24
 
25
25
  def self.export_serializer
26
- Decidim::Meetings::DataPortabilityRegistrationSerializer
26
+ Decidim::Meetings::DownloadYourDataRegistrationSerializer
27
27
  end
28
28
 
29
29
  # Pluck all Decidim::UserGroup ID's
@@ -102,9 +102,7 @@ $(() => {
102
102
  attachGeocoding($form.find("#meeting_address"));
103
103
 
104
104
  const $meetingRegistrationType = $form.find("#meeting_registration_type");
105
- const $meetingRegistrationTerms = $form.find("#meeting_registration_terms");
106
105
  const $meetingRegistrationUrl = $form.find("#meeting_registration_url");
107
- const $meetingAvailableSlots = $form.find("#meeting_available_slots");
108
106
 
109
107
  const toggleDependsOnSelect = ($target, $showDiv, type) => {
110
108
  const value = $target.val();
@@ -113,8 +111,6 @@ $(() => {
113
111
 
114
112
  $meetingRegistrationType.on("change", (ev) => {
115
113
  const $target = $(ev.target);
116
- toggleDependsOnSelect($target, $meetingAvailableSlots, "on_this_platform");
117
- toggleDependsOnSelect($target, $meetingRegistrationTerms, "on_this_platform");
118
114
  toggleDependsOnSelect($target, $meetingRegistrationUrl, "on_different_platform");
119
115
  });
120
116
 
@@ -5,11 +5,13 @@ $(() => {
5
5
  const $registrationsEnabled = $form.find("#meeting_registrations_enabled");
6
6
  const $availableSlots = $form.find("#meeting_available_slots");
7
7
  const $reservedSlots = $form.find("#meeting_reserved_slots");
8
+ const $customizeRegistrationEmail = $form.find("#meeting_customize_registration_email");
8
9
 
9
10
  const toggleDisabledFields = () => {
10
11
  const enabled = $registrationsEnabled.prop("checked");
11
12
  $availableSlots.attr("disabled", !enabled);
12
13
  $reservedSlots.attr("disabled", !enabled);
14
+ $customizeRegistrationEmail.attr("disabled", !enabled);
13
15
 
14
16
  $form.find(".editor-container").each((idx, node) => {
15
17
  const quill = Quill.find(node);
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Meetings
5
+ module AdminLog
6
+ # This class holds the logic to present a `Decidim::Meetings::Questionnaire`
7
+ # for the `AdminLog` log.
8
+ #
9
+ # Usage should be automatic and you shouldn't need to call this class
10
+ # directly, but here's an example:
11
+ #
12
+ # action_log = Decidim::ActionLog.last
13
+ # view_helpers # => this comes from the views
14
+ # QuestionnairePresenter.new(action_log, view_helpers).present
15
+ class QuestionnairePresenter < Decidim::Log::BasePresenter
16
+ private
17
+
18
+ def action_string
19
+ case action
20
+ when "update"
21
+ "decidim.meetings.admin_log.questionnaire.#{action}"
22
+ else
23
+ super
24
+ end
25
+ end
26
+
27
+ def resource_presenter
28
+ @resource_presenter ||= Decidim::Log::ResourcePresenter.new(action_log.resource.questionnaire_for.meeting, h, action_log.resource.questionnaire_for.meeting)
29
+ end
30
+
31
+ def i18n_params
32
+ super.merge(
33
+ meeting_name: resource_presenter.try(:present)
34
+ )
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -31,7 +31,12 @@ module Decidim
31
31
  def description(links: false, extras: true, strip_tags: false, all_locales: false)
32
32
  return unless meeting
33
33
 
34
- content_handle_locale(meeting.description, all_locales, extras, links, strip_tags)
34
+ new_description = handle_locales(meeting.description, all_locales) do |content|
35
+ renderer = Decidim::ContentRenderers::HashtagRenderer.new(sanitized(content))
36
+ renderer.render(links: links).html_safe
37
+ end
38
+
39
+ content_handle_locale(new_description, all_locales, extras, links, strip_tags)
35
40
  end
36
41
 
37
42
  def location(all_locales: false)
@@ -4,7 +4,7 @@ module Decidim
4
4
  module Meetings
5
5
  module Admin
6
6
  # A class used to find the Invites by their status status.
7
- class Invites < Rectify::Query
7
+ class Invites < Decidim::Query
8
8
  # Syntactic sugar to initialize the class and return the queried objects.
9
9
  #
10
10
  # invites - the initial Invites relation that needs to be filtered.
@@ -3,7 +3,7 @@
3
3
  module Decidim
4
4
  module Meetings
5
5
  # A class used to find meetings filtered by components and a date range
6
- class FilteredMeetings < Rectify::Query
6
+ class FilteredMeetings < Decidim::Query
7
7
  # Syntactic sugar to initialize the class and return the queried objects.
8
8
  #
9
9
  # components - An array of Decidim::Component
@@ -3,7 +3,7 @@
3
3
  module Decidim
4
4
  module Meetings
5
5
  # A class used to collect user answers for a questionnaire
6
- class QuestionnaireUserAnswers < Rectify::Query
6
+ class QuestionnaireUserAnswers < Decidim::Query
7
7
  # Syntactic sugar to initialize the class and return the queried objects.
8
8
  #
9
9
  # questionnaire - a Questionnaire object
@@ -2,8 +2,8 @@
2
2
 
3
3
  module Decidim
4
4
  module Meetings
5
- class DataPortabilityInviteSerializer < Decidim::Exporters::Serializer
6
- # Serializes an invite for data portability
5
+ class DownloadYourDataInviteSerializer < Decidim::Exporters::Serializer
6
+ # Serializes an invite for download your data
7
7
  def serialize
8
8
  {
9
9
  id: resource.id,
@@ -2,8 +2,8 @@
2
2
 
3
3
  module Decidim
4
4
  module Meetings
5
- class DataPortabilityRegistrationSerializer < Decidim::Exporters::Serializer
6
- # Serializes a registration for data portability
5
+ class DownloadYourDataRegistrationSerializer < Decidim::Exporters::Serializer
6
+ # Serializes a registration for download your data
7
7
  def serialize
8
8
  {
9
9
  id: resource.id,
@@ -14,15 +14,16 @@ module Decidim
14
14
  # resource - a resource that has meetings.
15
15
  #
16
16
  # Returns a String.
17
- def self.for(resource)
18
- new(resource).calendar
17
+ def self.for(resource, filters = nil)
18
+ new(resource, filters).calendar
19
19
  end
20
20
 
21
21
  # Initializes the class.
22
22
  #
23
23
  # resource - a resource that has meetings.
24
- def initialize(resource)
24
+ def initialize(resource, filters = nil)
25
25
  @resource = resource
26
+ @filters = filters
26
27
  end
27
28
 
28
29
  # Converts the resource meetings to the ICalendar format.
@@ -11,11 +11,9 @@ module Decidim
11
11
  #
12
12
  # Returns a String.
13
13
  def events
14
- Rails.cache.fetch(cache_key) do
15
- meetings.map do |meeting|
16
- MeetingCalendar.new(meeting).events
17
- end.compact.join
18
- end
14
+ filtered_meetings.map do |meeting|
15
+ MeetingCalendar.new(meeting).events
16
+ end.compact.join
19
17
  end
20
18
 
21
19
  private
@@ -29,11 +27,11 @@ module Decidim
29
27
  Decidim::Meetings::Meeting.where(component: component)
30
28
  end
31
29
 
32
- # Defines the cache key for the given component.
30
+ # Finds the component meetings.
33
31
  #
34
- # Returns a String.
35
- def cache_key
36
- "meetings-calendar-component-#{component.id}-#{component.updated_at.to_i}"
32
+ # Returns a collection of Meetings filtered based on provided params.
33
+ def filtered_meetings
34
+ meetings.not_hidden.published.except_withdrawn.ransack(@filters).result
37
35
  end
38
36
  end
39
37
  end
@@ -13,7 +13,7 @@ module Decidim
13
13
  # Returns a String.
14
14
  def events
15
15
  @events ||= components.map do |component|
16
- ComponentCalendar.new(component).events
16
+ ComponentCalendar.new(component, @filters).events
17
17
  end.compact.join
18
18
  end
19
19
 
@@ -3,14 +3,14 @@
3
3
  module Decidim
4
4
  module Meetings
5
5
  class CalendarRenderer
6
- def self.for(resource)
6
+ def self.for(resource, filters = nil)
7
7
  case resource
8
8
  when Decidim::Organization
9
- Calendar::OrganizationCalendar.for(resource)
9
+ Calendar::OrganizationCalendar.for(resource, filters)
10
10
  when Decidim::Component
11
- Calendar::ComponentCalendar.for(resource)
11
+ Calendar::ComponentCalendar.for(resource, filters)
12
12
  when Decidim::Meetings::Meeting
13
- Calendar::MeetingCalendar.for(resource)
13
+ Calendar::MeetingCalendar.for(resource, filters)
14
14
  end
15
15
  end
16
16
  end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Meetings
5
+ # This class is the generator class which creates and updates meetings related reminders,
6
+ # after reminder is generated it is send to user who have not closed past meetings.
7
+ class CloseMeetingReminderGenerator
8
+ attr_reader :reminder_jobs_queued
9
+
10
+ def initialize
11
+ @reminder_manifest = Decidim.reminders_registry.for(:close_meeting)
12
+ @reminder_jobs_queued = 0
13
+ end
14
+
15
+ # Creates reminders and updates them if they already exists.
16
+ def generate
17
+ Decidim::Component.where(manifest_name: "meetings").published.each do |component|
18
+ send_reminders(component)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :reminder_manifest
25
+
26
+ def finder_query(component_id, interval)
27
+ Decidim::Meetings::Meeting
28
+ .published
29
+ .not_hidden
30
+ .except_withdrawn
31
+ .where(
32
+ "decidim_component_id = ? AND end_time >= ? AND end_time <= ? AND closed_at IS NULL",
33
+ component_id,
34
+ interval.ago.beginning_of_day,
35
+ interval.ago.end_of_day
36
+ )
37
+ end
38
+
39
+ def send_reminders(component)
40
+ intervals = Array(reminder_manifest.settings.attributes[:reminder_times].default)
41
+ space_admins = Decidim::ParticipatoryProcessUserRole.where(decidim_participatory_process_id: component.participatory_space_id, role: "admin").collect(&:user)
42
+ space_admins = (global_admins + space_admins).uniq
43
+ intervals.each do |interval|
44
+ finder_query(component.id, interval).find_each do |meeting|
45
+ authors = meeting.official? ? space_admins : [meeting.author]
46
+ authors.each do |author|
47
+ send_notif = author.notification_settings.fetch("close_meeting_reminder", "1")
48
+ next unless send_notif == "1"
49
+
50
+ reminder = Decidim::Reminder.find_or_create_by(user: author, component: component)
51
+ record = Decidim::ReminderRecord.find_or_create_by(reminder: reminder, remindable: meeting)
52
+ record.update(state: "active") unless record.active?
53
+ reminder.records << record
54
+ reminder.save!
55
+
56
+ Decidim::Meetings::SendCloseMeetingReminderJob.perform_later(record)
57
+ @reminder_jobs_queued += 1
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ def global_admins
64
+ @global_admins ||= Decidim::User.where(admin: true).all
65
+ end
66
+ end
67
+ end
68
+ end
@@ -30,28 +30,31 @@ module Decidim
30
30
  def embeddable?
31
31
  return nil if parsed_online_meeting_uri.nil?
32
32
 
33
- EMBEDDABLE_SERVICES.include?(parsed_online_meeting_uri.host)
33
+ embeddable_services.include?(parsed_online_meeting_uri.host)
34
34
  end
35
35
 
36
36
  def embed_code(request_host)
37
37
  return nil if parsed_online_meeting_uri.nil?
38
38
 
39
39
  %(
40
- <iframe
40
+ <div
41
+ class="disabled-iframe"
41
42
  allow="camera; microphone; fullscreen; display-capture; autoplay"
42
43
  loading="lazy"
43
44
  src="#{embed_transformed_url(request_host)}"
44
45
  style="height: 100%; width: 100%; border: 0px;"
45
- ></iframe>
46
+ ></div>
46
47
  )
47
48
  end
48
49
 
49
50
  private
50
51
 
51
- EMBEDDABLE_SERVICES = %( www.youtube.com www.twitch.tv meet.jit.si )
52
-
53
52
  attr_accessor :online_meeting_service_url
54
53
 
54
+ def embeddable_services
55
+ @embeddable_services ||= Meetings.embeddable_services
56
+ end
57
+
55
58
  # Youtube transformation consists on:
56
59
  # 1. extract the video id from the parameter v
57
60
  # 2. Create a new URL using the domain youtube-nocookie.com, converting it to an embed
@@ -59,7 +62,8 @@ module Decidim
59
62
  def transform_youtube_url(uri)
60
63
  return online_meeting_service_url if uri.query.blank?
61
64
 
62
- video_id = CGI.parse(uri.query).fetch("v")&.first
65
+ parsed_query = CGI.parse(uri.query)
66
+ video_id = parsed_query.has_key?("v") ? CGI.parse(uri.query).fetch("v")&.first : nil
63
67
 
64
68
  return online_meeting_service_url if video_id.blank?
65
69
 
@@ -2,63 +2,17 @@
2
2
 
3
3
  module Decidim
4
4
  module Meetings
5
- # This class handles search and filtering of meetings. Needs a
6
- # `current_component` param with a `Decidim::Component` in order to
7
- # find the meetings.
5
+ # This service scopes the meeting searches with parameters that cannot be
6
+ # passed from the user interface.
8
7
  class MeetingSearch < ResourceSearch
9
- text_search_fields :title, :description
8
+ attr_reader :activity
10
9
 
11
- # Public: Initializes the service.
12
- # component - A Decidim::Component to get the meetings from.
13
- # page - The page number to paginate the results.
14
- # per_page - The number of meetings to return per page.
15
- def initialize(options = {})
16
- options[:scope] = options.fetch(:scope, Meeting.published)
17
- options[:scope] = options[:state] == "withdrawn" ? options[:scope].withdrawn : options[:scope].except_withdrawn
18
- options[:scope] = options[:scope].includes(:component, :attachments)
19
- super(options[:scope], options)
20
- end
21
-
22
- # Handle the date filter
23
- def search_date
24
- apply_scopes(%w(upcoming past), date)
25
- end
26
-
27
- def search_space
28
- return query if options[:space].blank? || options[:space] == "all"
29
-
30
- query.joins(:component).where(decidim_components: { participatory_space_type: options[:space].classify })
31
- end
10
+ def build(params)
11
+ @activity = params[:activity]
32
12
 
33
- def search_type
34
- fields = Decidim::Meetings::Meeting::TYPE_OF_MEETING
35
- filtered = []
36
- options[:type].each do |inquiry|
37
- filtered.push(inquiry) if fields.include?(inquiry)
38
- end
39
- filtered.size.positive? ? query.where(decidim_meetings_meetings: { type_of_meeting: filtered }) : query
40
- end
41
-
42
- # Handle the activity filter
43
- def search_activity
44
- case activity
45
- when "my_meetings"
46
- query
47
- .where(decidim_author_id: user.id)
48
- else
49
- query
50
- end
51
- end
52
-
53
- # Handle the state filter
54
- def search_state
55
- return query.withdrawn if state == "withdrawn"
56
-
57
- query.except_withdrawn
58
- end
13
+ add_scope(:authored_by, user) if params[:activity] == "my_meetings" && user
59
14
 
60
- def results
61
- super.includes(attachments: :file_attachment, component: { participatory_space: :organization })
15
+ super
62
16
  end
63
17
  end
64
18
  end
@@ -2,12 +2,29 @@
2
2
  <%= t(".export_calendar") %>
3
3
  <%= icon "share", class: "icon--after", role: "img", "aria-hidden": true %>
4
4
  </button>
5
+
5
6
  <div class="reveal" id="calendarShare" data-reveal>
6
7
  <div class="reveal__header">
7
- <h2 class="reveal__title"><%= t(".calendar_url") %>:</h2>
8
+ <h3 class="reveal__title"><%= t(".calendar_url") %>:</h3>
8
9
  <button class="close-button" data-close aria-label="<%= t(".close_window") %>" type="button">
9
10
  <span aria-hidden="true">&times;</span>
10
11
  </button>
11
12
  </div>
12
- <input type="text" value="<%= "#{path}" %>" readonly title="<%= t(".calendar_url") %>">
13
+
14
+ <div class="calendar-url-description"><%= t(".copy_calendar_url_description") %></div>
15
+ <div class="calendar-url-explanation"><%= t(".copy_calendar_url_explanation") %></div>
16
+
17
+ <div class="input-group calendar_url_input">
18
+ <input id="urlCalendarUrl" class="input-group-field" type="text" title="<%= t(".calendar_url") %>" value="<%= "#{path}" %>" readonly>
19
+ <div class="input-group-button">
20
+ <button class="button primary"
21
+ data-clipboard-copy="#urlCalendarUrl"
22
+ data-clipboard-copy-label="<%= t(".copy_calendar_url_copied") %>"
23
+ data-clipboard-copy-message="<%= t(".copy_calendar_url_message") %>"
24
+ aria-label="<%= t(".copy_calendar_url_clarification") %>">
25
+ <%= icon "clipboard", role: "img", "aria-hidden": true %>
26
+ <%= t(".copy_calendar_url") %>
27
+ </button>
28
+ </div>
29
+ </div>
13
30
  </div>
@@ -0,0 +1,64 @@
1
+ <div class="card" id="meetings">
2
+ <div class="card-divider">
3
+ <h2 class="card-title"><%= title %></h2>
4
+ </div>
5
+
6
+ <div class="card-section">
7
+ <div class="row column hashtags__container">
8
+ <%= form.translated :text_field, :title, autofocus: true, class: "js-hashtags", hashtaggable: true, value: @meeting.present? ? present(@meeting).title(all_locales: true) : @form.title.with_indifferent_access %>
9
+ </div>
10
+
11
+ <div class="row column hashtags__container">
12
+ <%= form.translated :editor, :description, hashtaggable: true, value: @meeting.present? ? present(@meeting).description(all_locales: true) : @form.description.with_indifferent_access %>
13
+ </div>
14
+
15
+ <div class="row column">
16
+ <%= form.select :type_of_meeting,
17
+ @form.type_of_meeting_select,
18
+ { include_blank: t(".select_a_meeting_type") },
19
+ { multiple: false } %>
20
+ </div>
21
+
22
+ <div class="row column field" data-meeting-type="in_person">
23
+ <%= form.geocoding_field :address %>
24
+ </div>
25
+
26
+ <div class="row column field" data-meeting-type="in_person">
27
+ <%= form.translated :text_area, :location %>
28
+ </div>
29
+
30
+ <div class="row column">
31
+ <%= form.translated :text_area, :location_hints %>
32
+ </div>
33
+
34
+ <div class="row column field" data-meeting-type="online">
35
+ <%= form.text_field :online_meeting_url %>
36
+ </div>
37
+
38
+ <div class="row column field" data-meeting-type="online">
39
+ <%= form.check_box :show_embedded_iframe %>
40
+ </div>
41
+
42
+ <div class="row">
43
+ <div class="columns xlarge-6">
44
+ <%= form.datetime_field :start_time %>
45
+ </div>
46
+
47
+ <div class="columns xlarge-6">
48
+ <%= form.datetime_field :end_time %>
49
+ </div>
50
+ </div>
51
+
52
+ <div class="row column" id="private_meeting">
53
+ <%= form.check_box :private_meeting %>
54
+ </div>
55
+
56
+ <div class="row column" id="transparent">
57
+ <%= form.check_box :transparent %>
58
+ </div>
59
+ </div>
60
+ </div>
61
+
62
+ <%= render "decidim/meetings/admin/meetings/services", form: form %>
63
+
64
+ <%= javascript_pack_tag "decidim_meetings_admin" %>
@@ -1,5 +1,5 @@
1
1
  <%= decidim_form_for(@form, url: meeting_copies_path(meeting), method: :post, html: { class: "form copy_meetings " }) do |f| %>
2
- <%= render partial: "decidim/meetings/admin/meetings/form", object: f, locals: { title: t("meeting_copies.new.title", scope: "decidim.admin"), select: t("meeting_copies.new.select", scope: "decidim.admin") } %>
2
+ <%= render partial: "form", object: f, locals: { title: t("meeting_copies.new.title", scope: "decidim.admin"), select: t("meeting_copies.new.select", scope: "decidim.admin") } %>
3
3
 
4
4
  <div class="button--double form-general-submit">
5
5
  <%= f.submit t("meeting_copies.new.copy", scope: "decidim.admin") %>
@@ -43,7 +43,7 @@
43
43
  <%= form.select :iframe_embed_type,
44
44
  @form.iframe_embed_type_select,
45
45
  { multiple: false } %>
46
- <p class="help-text"><%= t(".show_embedded_iframe_help") %></p>
46
+ <p class="help-text"><%= t(".show_embedded_iframe_help", domains: Decidim::Meetings.embeddable_services&.join(" ")) %></p>
47
47
  </div>
48
48
 
49
49
  <div class="row column field iframe-fields--access-level" data-meeting-type="online-access-level">
@@ -82,15 +82,12 @@
82
82
  { multiple: false } %>
83
83
  </div>
84
84
 
85
- <div class="row column" id="meeting_available_slots">
86
- <%= form.number_field :available_slots %>
87
- <p class="help-text"><%= t(".available_slots_help") %></p>
88
- </div>
89
-
90
85
  <div class="field" id="meeting_registration_url">
91
86
  <%= form.text_field :registration_url %>
92
87
  <p class="help-text"><%= t(".registration_url_help") %></p>
93
- <label><%= t(".disclaimer", organization: current_component.organization.name) %></label><br>
88
+ <div class="callout alert">
89
+ <%= t(".disclaimer", organization: current_component.organization.name) %>
90
+ </div>
94
91
  </div>
95
92
 
96
93
  <div class="row column" id="private_meeting">
@@ -101,19 +98,10 @@
101
98
  <%= form.check_box :transparent %>
102
99
  </div>
103
100
 
104
- <div class="row column">
105
- <%= form.check_box :customize_registration_email, :"data-toggle" => "customize_registration_email-div" %>
106
- </div>
107
-
108
- <div id="customize_registration_email-div" data-toggler=".hide" class="row column <%= @form.customize_registration_email? ? nil : "hide" %>">
109
- <%= form.translated :editor, :registration_email_custom_content, lines: 10 %>
110
- <p class="help-text"><%= t(".registration_email_help") %></p>
111
- </div>
112
-
113
101
  <%= render partial: "decidim/comments/admin/shared/availability_fields", locals: { form: form } %>
114
102
  </div>
115
103
  </div>
116
104
 
117
- <%= render "decidim/meetings/admin/meetings/services", form: form, id: tabs_id_for_service(blank_service) %>
105
+ <%= render "services", form: form, id: tabs_id_for_service(blank_service) %>
118
106
 
119
107
  <%= javascript_pack_tag "decidim_meetings_admin" %>
@@ -1,4 +1,3 @@
1
- <% add_decidim_page_title(t(".title")) %>
2
1
  <%= decidim_form_for(@form, url: { action: "update" }, html: { class: "form meetings_form edit_meeting" }) do |f| %>
3
2
  <%= render partial: "form", object: f, locals: { title: t(".title") } %>
4
3
 
@@ -1,4 +1,3 @@
1
- <% add_decidim_page_title(t(".title")) %>
2
1
  <div class="card">
3
2
  <div class="card-divider">
4
3
  <h2 class="card-title">
@@ -49,7 +48,7 @@
49
48
  <%= meeting.id %><br>
50
49
  </td>
51
50
  <td>
52
- <%= present(meeting).title(html_escape: true) %><br>
51
+ <%= present(meeting).title %><br>
53
52
  </td>
54
53
  <td>
55
54
  <% if meeting.start_time %>
@@ -1,4 +1,3 @@
1
- <% add_decidim_page_title(t(".title")) %>
2
1
  <%= decidim_form_for(@form, html: { class: "form meetings_form new_meeting" }) do |f| %>
3
2
  <%= render partial: "form", object: f, locals: { title: t(".title") } %>
4
3
 
@@ -42,7 +42,16 @@
42
42
  </div>
43
43
 
44
44
  <div class="row column">
45
- <%= form.translated :editor, :registration_terms, toolbar: :content %>
45
+ <%= form.check_box :customize_registration_email, :"data-toggle" => "customize_registration_email-div" %>
46
+ </div>
47
+
48
+ <div id="customize_registration_email-div" data-toggler=".hide" class="row column <%= @form.customize_registration_email? ? nil : "hide" %>">
49
+ <%= form.translated :editor, :registration_email_custom_content, lines: 10 %>
50
+ <p class="help-text"><%= t(".registration_email_help") %></p>
51
+ </div>
52
+
53
+ <div class="row column">
54
+ <%= form.translated :editor, :registration_terms %>
46
55
  </div>
47
56
  </div>
48
57
  </div>