decidim-meetings 0.25.2 → 0.26.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/meetings/content_blocks/highlighted_meetings/heading.erb +1 -1
  3. data/app/cells/decidim/meetings/content_blocks/highlighted_meetings_cell.rb +1 -1
  4. data/app/cells/decidim/meetings/content_blocks/{upcoming_events → upcoming_meetings}/show.erb +6 -6
  5. data/app/cells/decidim/meetings/content_blocks/{upcoming_events_cell.rb → upcoming_meetings_cell.rb} +11 -10
  6. data/app/cells/decidim/meetings/highlighted_meetings_for_component_cell.rb +1 -1
  7. data/app/cells/decidim/meetings/join_meeting_button_cell.rb +1 -1
  8. data/app/cells/decidim/meetings/meeting_list_item_cell.rb +10 -0
  9. data/app/cells/decidim/meetings/meeting_m_cell.rb +48 -0
  10. data/app/cells/decidim/meetings/meeting_s_cell.rb +22 -0
  11. data/app/cells/decidim/meetings/meetings_map_cell.rb +6 -0
  12. data/app/cells/decidim/meetings/online_meeting_cell.rb +23 -8
  13. data/app/cells/decidim/meetings/online_meeting_link/show.erb +2 -2
  14. data/app/cells/decidim/meetings/online_meeting_link_cell.rb +1 -5
  15. data/app/cells/decidim/meetings/public_participants_list_cell.rb +1 -0
  16. data/app/cells/decidim/meetings/question_responses_cell.rb +1 -1
  17. data/app/commands/decidim/meetings/admin/create_meeting.rb +11 -1
  18. data/app/commands/decidim/meetings/admin/publish_meeting.rb +3 -1
  19. data/app/commands/decidim/meetings/admin/update_meeting.rb +8 -2
  20. data/app/commands/decidim/meetings/create_meeting.rb +14 -2
  21. data/app/commands/decidim/meetings/update_meeting.rb +5 -2
  22. data/app/commands/decidim/meetings/withdraw_meeting.rb +39 -0
  23. data/app/controllers/concerns/decidim/meetings/filterable.rb +33 -0
  24. data/app/controllers/decidim/meetings/directory/application_controller.rb +16 -0
  25. data/app/controllers/decidim/meetings/directory/meetings_controller.rb +31 -20
  26. data/app/controllers/decidim/meetings/live_events_controller.rb +7 -1
  27. data/app/controllers/decidim/meetings/meetings_controller.rb +19 -17
  28. data/app/events/decidim/meetings/close_meeting_event.rb +1 -3
  29. data/app/events/decidim/meetings/create_meeting_event.rb +2 -4
  30. data/app/events/decidim/meetings/meeting_event.rb +37 -0
  31. data/app/events/decidim/meetings/meeting_registrations_enabled_event.rb +1 -3
  32. data/app/events/decidim/meetings/meeting_registrations_over_percentage_event.rb +2 -4
  33. data/app/events/decidim/meetings/registration_code_validated_event.rb +2 -4
  34. data/app/events/decidim/meetings/upcoming_meeting_event.rb +1 -3
  35. data/app/events/decidim/meetings/update_meeting_event.rb +1 -3
  36. data/app/forms/decidim/meetings/admin/close_meeting_form.rb +1 -1
  37. data/app/forms/decidim/meetings/admin/meeting_copy_form.rb +4 -13
  38. data/app/forms/decidim/meetings/admin/meeting_form.rb +34 -46
  39. data/app/forms/decidim/meetings/base_meeting_form.rb +59 -0
  40. data/app/forms/decidim/meetings/close_meeting_form.rb +6 -6
  41. data/app/forms/decidim/meetings/meeting_form.rb +30 -46
  42. data/app/helpers/decidim/meetings/application_helper.rb +13 -1
  43. data/app/helpers/decidim/meetings/directory/application_helper.rb +150 -0
  44. data/app/helpers/decidim/meetings/meeting_cells_helper.rb +4 -1
  45. data/app/helpers/decidim/meetings/meetings_helper.rb +3 -0
  46. data/app/models/decidim/meetings/meeting.rb +53 -1
  47. data/app/packs/src/decidim/meetings/admin/meetings_form.js +27 -11
  48. data/app/packs/src/decidim/meetings/meetings_form.js +19 -0
  49. data/app/packs/src/decidim/meetings/meetings_polls.js +31 -26
  50. data/app/permissions/decidim/meetings/permissions.rb +9 -1
  51. data/app/presenters/decidim/meetings/meeting_edition_presenter.rb +14 -0
  52. data/app/presenters/decidim/meetings/meeting_presenter.rb +13 -6
  53. data/app/services/decidim/meetings/directory/meeting_search.rb +53 -0
  54. data/app/services/decidim/meetings/meeting_iframe_embedder.rb +1 -1
  55. data/app/services/decidim/meetings/meeting_search.rb +15 -2
  56. data/app/views/decidim/meetings/admin/meeting_copies/_form.html.erb +16 -1
  57. data/app/views/decidim/meetings/admin/meeting_copies/new.html.erb +1 -1
  58. data/app/views/decidim/meetings/admin/meetings/_form.html.erb +15 -3
  59. data/app/views/decidim/meetings/directory/meetings/_filters.html.erb +34 -0
  60. data/app/views/decidim/meetings/directory/meetings/index.html.erb +1 -18
  61. data/app/views/decidim/meetings/meetings/_filters.html.erb +2 -0
  62. data/app/views/decidim/meetings/meetings/_filters_small_view.html.erb +3 -3
  63. data/app/views/decidim/meetings/meetings/_form.html.erb +14 -3
  64. data/app/views/decidim/meetings/meetings/_linked_meetings.html.erb +1 -1
  65. data/app/views/decidim/meetings/meetings/_meetings.html.erb +18 -0
  66. data/app/views/decidim/meetings/meetings/index.html.erb +1 -0
  67. data/app/views/decidim/meetings/meetings/show.html.erb +10 -4
  68. data/config/locales/ar.yml +2 -7
  69. data/config/locales/ca.yml +3 -15
  70. data/config/locales/cs.yml +41 -13
  71. data/config/locales/de.yml +2 -9
  72. data/config/locales/el.yml +2 -7
  73. data/config/locales/en.yml +41 -13
  74. data/config/locales/es-MX.yml +2 -15
  75. data/config/locales/es-PY.yml +2 -15
  76. data/config/locales/es.yml +40 -12
  77. data/config/locales/eu.yml +19 -15
  78. data/config/locales/fi-plain.yml +40 -13
  79. data/config/locales/fi.yml +41 -13
  80. data/config/locales/fr-CA.yml +32 -15
  81. data/config/locales/fr.yml +42 -24
  82. data/config/locales/ga-IE.yml +2 -7
  83. data/config/locales/gl.yml +28 -7
  84. data/config/locales/hu.yml +2 -7
  85. data/config/locales/id-ID.yml +2 -7
  86. data/config/locales/is-IS.yml +0 -7
  87. data/config/locales/it.yml +3 -15
  88. data/config/locales/ja.yml +44 -17
  89. data/config/locales/lb-LU.yml +210 -0
  90. data/config/locales/lb.yml +2 -6
  91. data/config/locales/lv.yml +2 -7
  92. data/config/locales/nl.yml +33 -9
  93. data/config/locales/no.yml +2 -9
  94. data/config/locales/pl.yml +14 -9
  95. data/config/locales/pt-BR.yml +3 -10
  96. data/config/locales/pt.yml +2 -15
  97. data/config/locales/ro-RO.yml +31 -15
  98. data/config/locales/ru.yml +0 -7
  99. data/config/locales/sk.yml +2 -7
  100. data/config/locales/sv.yml +23 -15
  101. data/config/locales/tr-TR.yml +2 -9
  102. data/config/locales/uk.yml +0 -9
  103. data/config/locales/val-ES.yml +1 -0
  104. data/config/locales/zh-CN.yml +2 -9
  105. data/db/migrate/20210519133705_add_comments_availability_columns_to_meetings_table.rb +14 -0
  106. data/db/migrate/20210727085318_add_state_field_to_meeting.rb +7 -0
  107. data/db/migrate/20210903143040_add_iframe_access_level_to_decidim_meetings.rb +7 -0
  108. data/db/migrate/20210922140454_transform_show_embedded_iframe_column.rb +15 -0
  109. data/db/migrate/20210928095036_rename_upcoming_events_content_block_to_upcoming_meetings.rb +13 -0
  110. data/lib/decidim/api/meeting_type.rb +2 -1
  111. data/lib/decidim/meetings/component.rb +13 -5
  112. data/lib/decidim/meetings/directory_engine.rb +3 -3
  113. data/lib/decidim/meetings/engine.rb +4 -1
  114. data/lib/decidim/meetings/meeting_serializer.rb +2 -2
  115. data/lib/decidim/meetings/test/factories.rb +24 -3
  116. data/lib/decidim/meetings/test/notifications_handling.rb +39 -0
  117. data/lib/decidim/meetings/test/translated_event.rb +22 -0
  118. data/lib/decidim/meetings/version.rb +1 -1
  119. data/lib/decidim/meetings.rb +5 -0
  120. metadata +36 -19
@@ -3,55 +3,49 @@
3
3
  module Decidim
4
4
  module Meetings
5
5
  # This class holds a Form to create/update meetings for Participants and UserGroups.
6
- class MeetingForm < Decidim::Form
6
+ class MeetingForm < ::Decidim::Meetings::BaseMeetingForm
7
7
  attribute :title, String
8
8
  attribute :description, String
9
9
  attribute :location, String
10
10
  attribute :location_hints, String
11
11
 
12
- attribute :address, String
13
- attribute :latitude, Float
14
- attribute :longitude, Float
15
- attribute :start_time, Decidim::Attributes::TimeWithZone
16
- attribute :end_time, Decidim::Attributes::TimeWithZone
17
12
  attribute :decidim_scope_id, Integer
18
13
  attribute :decidim_category_id, Integer
19
14
  attribute :user_group_id, Integer
20
- attribute :online_meeting_url, String
21
- attribute :type_of_meeting, String
22
15
  attribute :registration_type, String
23
16
  attribute :registrations_enabled, Boolean, default: false
24
17
  attribute :registration_url, String
25
18
  attribute :available_slots, Integer, default: 0
26
19
  attribute :registration_terms, String
27
- attribute :show_embedded_iframe, Boolean, default: false
20
+ attribute :iframe_embed_type, String, default: "none"
21
+ attribute :iframe_access_level, String
28
22
 
23
+ validates :iframe_embed_type, inclusion: { in: Decidim::Meetings::Meeting.participants_iframe_embed_types }
29
24
  validates :title, presence: true
30
25
  validates :description, presence: true
31
26
  validates :type_of_meeting, presence: true
32
27
  validates :location, presence: true, if: ->(form) { form.in_person_meeting? || form.hybrid_meeting? }
33
- validates :address, presence: true, if: ->(form) { form.needs_address? }
34
- validates :address, geocoding: true, if: ->(form) { form.has_address? && !form.geocoded? && form.needs_address? }
35
28
  validates :online_meeting_url, presence: true, url: true, if: ->(form) { form.online_meeting? || form.hybrid_meeting? }
36
29
  validates :registration_type, presence: true
37
30
  validates :available_slots, numericality: { greater_than_or_equal_to: 0 }, presence: true, if: ->(form) { form.on_this_platform? }
38
31
  validates :registration_terms, presence: true, if: ->(form) { form.on_this_platform? }
39
32
  validates :registration_url, presence: true, url: true, if: ->(form) { form.on_different_platform? }
40
- validates :start_time, presence: true, date: { before: :end_time }
41
- validates :end_time, presence: true, date: { after: :start_time }
42
-
43
- validates :current_component, presence: true
44
33
  validates :category, presence: true, if: ->(form) { form.decidim_category_id.present? }
45
34
  validates :scope, presence: true, if: ->(form) { form.decidim_scope_id.present? }
46
35
  validates :decidim_scope_id, scope_belongs_to_component: true, if: ->(form) { form.decidim_scope_id.present? }
47
36
  validates :clean_type_of_meeting, presence: true
37
+ validates(
38
+ :iframe_access_level,
39
+ inclusion: { in: Decidim::Meetings::Meeting.iframe_access_levels },
40
+ if: ->(form) { %w(embed_in_meeting_page open_in_new_tab).include?(form.iframe_embed_type) }
41
+ )
48
42
  validate :embeddable_meeting_url
49
43
 
50
44
  delegate :categories, to: :current_component
51
45
 
52
46
  def map_model(model)
53
47
  self.decidim_category_id = model.categorization.decidim_category_id if model.categorization
54
- presenter = MeetingPresenter.new(model)
48
+ presenter = MeetingEditionPresenter.new(model)
55
49
  self.title = presenter.title(all_locales: false)
56
50
  self.description = presenter.description(all_locales: false)
57
51
  self.location = presenter.location(all_locales: false)
@@ -82,34 +76,6 @@ module Decidim
82
76
  @category ||= categories.find_by(id: decidim_category_id)
83
77
  end
84
78
 
85
- def geocoding_enabled?
86
- Decidim::Map.available?(:geocoding)
87
- end
88
-
89
- def has_address?
90
- geocoding_enabled? && address.present?
91
- end
92
-
93
- def needs_address?
94
- in_person_meeting? || hybrid_meeting?
95
- end
96
-
97
- def geocoded?
98
- latitude.present? && longitude.present?
99
- end
100
-
101
- def online_meeting?
102
- type_of_meeting == "online"
103
- end
104
-
105
- def in_person_meeting?
106
- type_of_meeting == "in_person"
107
- end
108
-
109
- def hybrid_meeting?
110
- type_of_meeting == "hybrid"
111
- end
112
-
113
79
  def clean_type_of_meeting
114
80
  type_of_meeting.presence
115
81
  end
@@ -123,6 +89,24 @@ module Decidim
123
89
  end
124
90
  end
125
91
 
92
+ def iframe_access_level_select
93
+ Decidim::Meetings::Meeting.iframe_access_levels.map do |level, _value|
94
+ [
95
+ I18n.t("iframe_access_level.#{level}", scope: "decidim.meetings"),
96
+ level
97
+ ]
98
+ end
99
+ end
100
+
101
+ def iframe_embed_type_select
102
+ Decidim::Meetings::Meeting.participants_iframe_embed_types.map do |type, _value|
103
+ [
104
+ I18n.t("iframe_embed_type.#{type}", scope: "decidim.meetings"),
105
+ type
106
+ ]
107
+ end
108
+ end
109
+
126
110
  def on_this_platform?
127
111
  registration_type == "on_this_platform"
128
112
  end
@@ -145,9 +129,9 @@ module Decidim
145
129
  end
146
130
 
147
131
  def embeddable_meeting_url
148
- if online_meeting_url.present? && show_embedded_iframe
132
+ if online_meeting_url.present? && %w(embed_in_meeting_page open_in_live_event_page).include?(iframe_embed_type)
149
133
  embedder_service = Decidim::Meetings::MeetingIframeEmbedder.new(online_meeting_url)
150
- errors.add(:show_embedded_iframe, :not_embeddable) unless embedder_service.embeddable?
134
+ errors.add(:iframe_embed_type, :not_embeddable) unless embedder_service.embeddable?
151
135
  end
152
136
  end
153
137
  end
@@ -8,10 +8,10 @@ module Decidim
8
8
  include PaginateHelper
9
9
  include Decidim::MapHelper
10
10
  include Decidim::Meetings::MapHelper
11
- include Decidim::Meetings::MeetingsHelper
12
11
  include Decidim::Comments::CommentsHelper
13
12
  include Decidim::SanitizeHelper
14
13
  include Decidim::CheckBoxesTreeHelper
14
+ include Decidim::RichTextEditorHelper
15
15
 
16
16
  def filter_origin_values
17
17
  origin_values = []
@@ -57,6 +57,18 @@ module Decidim
57
57
  ["my_meetings", t("decidim.meetings.meetings.filters.my_meetings")]
58
58
  ]
59
59
  end
60
+
61
+ # If the meeting is official or the rich text editor is enabled on the
62
+ # frontend, the meeting body is considered as safe content; that's unless
63
+ # the meeting comes from a collaborative_draft or a participatory_text.
64
+ def safe_content?
65
+ rich_text_editor_in_public_views? || @meeting.official?
66
+ end
67
+
68
+ # If the content is safe, HTML tags are sanitized, otherwise, they are stripped.
69
+ def render_meeting_body(meeting)
70
+ render_sanitized_content(meeting, :description)
71
+ end
60
72
  end
61
73
  end
62
74
  end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Meetings
5
+ # Custom helpers, scoped to the meetings engine.
6
+ #
7
+ module Directory
8
+ module ApplicationHelper
9
+ include PaginateHelper
10
+ include Decidim::MapHelper
11
+ include Decidim::Meetings::MapHelper
12
+ include Decidim::Meetings::MeetingsHelper
13
+ include Decidim::Comments::CommentsHelper
14
+ include Decidim::SanitizeHelper
15
+ include Decidim::CheckBoxesTreeHelper
16
+
17
+ def filter_type_values
18
+ type_values = []
19
+ Decidim::Meetings::Meeting::TYPE_OF_MEETING.each do |type|
20
+ type_values << TreePoint.new(type, t("decidim.meetings.meetings.filters.type_values.#{type}"))
21
+ end
22
+
23
+ TreeNode.new(
24
+ TreePoint.new("", t("decidim.meetings.meetings.filters.type_values.all")),
25
+ type_values
26
+ )
27
+ end
28
+
29
+ def filter_date_values
30
+ TreeNode.new(
31
+ TreePoint.new("", t("decidim.meetings.meetings.filters.date_values.all")),
32
+ [
33
+ TreePoint.new("upcoming", t("decidim.meetings.meetings.filters.date_values.upcoming")),
34
+ TreePoint.new("past", t("decidim.meetings.meetings.filters.date_values.past"))
35
+ ]
36
+ )
37
+ end
38
+
39
+ def directory_filter_scopes_values
40
+ main_scopes = current_organization.scopes.top_level
41
+
42
+ scopes_values = main_scopes.includes(:scope_type, :children).flat_map do |scope|
43
+ TreeNode.new(
44
+ TreePoint.new(scope.id.to_s, translated_attribute(scope.name, current_organization)),
45
+ scope_children_to_tree(scope)
46
+ )
47
+ end
48
+
49
+ scopes_values.prepend(TreePoint.new("global", t("decidim.scopes.global")))
50
+
51
+ TreeNode.new(
52
+ TreePoint.new("", t("decidim.meetings.application_helper.filter_scope_values.all")),
53
+ scopes_values
54
+ )
55
+ end
56
+
57
+ def scope_children_to_tree(scope)
58
+ return unless scope.children.any?
59
+
60
+ scope.children.includes(:scope_type, :children).flat_map do |child|
61
+ TreeNode.new(
62
+ TreePoint.new(child.id.to_s, translated_attribute(child.name, current_organization)),
63
+ scope_children_to_tree(child)
64
+ )
65
+ end
66
+ end
67
+
68
+ def directory_meeting_spaces_values
69
+ participatory_spaces = current_organization.public_participatory_spaces
70
+
71
+ spaces = participatory_spaces.collect(&:model_name).uniq.map do |participatory_space|
72
+ TreePoint.new(participatory_space.name.underscore, participatory_space.human(count: 2))
73
+ end
74
+
75
+ TreeNode.new(
76
+ TreePoint.new("", t("decidim.meetings.application_helper.filter_meeting_space_values.all")),
77
+ spaces
78
+ )
79
+ end
80
+
81
+ def directory_filter_categories_values
82
+ participatory_spaces = current_organization.public_participatory_spaces
83
+ list_of_ps = participatory_spaces.flat_map do |current_participatory_space|
84
+ next unless current_participatory_space.respond_to?(:categories)
85
+
86
+ sorted_main_categories = current_participatory_space.categories.first_class.includes(:subcategories).sort_by do |category|
87
+ [category.weight, translated_attribute(category.name, current_organization)]
88
+ end
89
+
90
+ categories_values = categories_values(sorted_main_categories)
91
+
92
+ next if categories_values.empty?
93
+
94
+ key_point = current_participatory_space.class.name.gsub("::", "__") + current_participatory_space.id.to_s
95
+
96
+ TreeNode.new(
97
+ TreePoint.new(key_point, translated_attribute(current_participatory_space.title, current_organization)),
98
+ categories_values
99
+ )
100
+ end
101
+
102
+ list_of_ps.compact!
103
+ TreeNode.new(
104
+ TreePoint.new("", t("decidim.meetings.application_helper.filter_category_values.all")),
105
+ list_of_ps
106
+ )
107
+ end
108
+
109
+ def directory_filter_origin_values
110
+ origin_values = []
111
+ origin_values << TreePoint.new("official", t("decidim.meetings.meetings.filters.origin_values.official"))
112
+ origin_values << TreePoint.new("citizens", t("decidim.meetings.meetings.filters.origin_values.citizens"))
113
+ origin_values << TreePoint.new("user_group", t("decidim.meetings.meetings.filters.origin_values.user_groups")) if current_organization.user_groups_enabled?
114
+
115
+ TreeNode.new(
116
+ TreePoint.new("", t("decidim.meetings.meetings.filters.origin_values.all")),
117
+ origin_values
118
+ )
119
+ end
120
+
121
+ # Options to filter meetings by activity.
122
+ def activity_filter_values
123
+ [
124
+ ["all", t("decidim.meetings.meetings.filters.all")],
125
+ ["my_meetings", t("decidim.meetings.meetings.filters.my_meetings")]
126
+ ]
127
+ end
128
+
129
+ protected
130
+
131
+ def categories_values(sorted_main_categories)
132
+ sorted_main_categories.flat_map do |category|
133
+ sorted_descendant_categories = category.descendants.includes(:subcategories).sort_by do |subcategory|
134
+ [subcategory.weight, translated_attribute(subcategory.name, current_organization)]
135
+ end
136
+
137
+ subcategories = sorted_descendant_categories.flat_map do |subcategory|
138
+ TreePoint.new(subcategory.id.to_s, translated_attribute(subcategory.name, current_organization))
139
+ end
140
+
141
+ TreeNode.new(
142
+ TreePoint.new(category.id.to_s, translated_attribute(category.name, current_organization)),
143
+ subcategories
144
+ )
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -9,8 +9,11 @@ module Decidim
9
9
  include Decidim::Meetings::MeetingsHelper
10
10
  include Decidim::SanitizeHelper
11
11
  include Decidim::Meetings::Engine.routes.url_helpers
12
+
13
+ delegate :title, :state, :withdrawn?, to: :model
14
+
12
15
  def description
13
- decidim_sanitize meeting_description(model)
16
+ decidim_sanitize_editor meeting_description(model)
14
17
  end
15
18
  end
16
19
  end
@@ -5,6 +5,7 @@ module Decidim
5
5
  # Custom helpers used in meetings views
6
6
  module MeetingsHelper
7
7
  include Decidim::ApplicationHelper
8
+ include Decidim::Meetings::ApplicationHelper
8
9
  include Decidim::TranslationsHelper
9
10
  include Decidim::ResourceHelper
10
11
 
@@ -33,6 +34,8 @@ module Decidim
33
34
  "alert"
34
35
  when "transparent"
35
36
  "secondary"
37
+ when "withdraw"
38
+ "alert"
36
39
  end
37
40
  end
38
41
 
@@ -14,6 +14,7 @@ module Decidim
14
14
  include Decidim::HasCategory
15
15
  include Decidim::Followable
16
16
  include Decidim::Comments::CommentableWithComponent
17
+ include Decidim::Comments::HasAvailabilityAttributes
17
18
  include Decidim::Searchable
18
19
  include Decidim::Traceable
19
20
  include Decidim::Loggable
@@ -44,6 +45,9 @@ module Decidim
44
45
  source: :user
45
46
  )
46
47
 
48
+ enum iframe_access_level: [:all, :signed_in, :registered], _prefix: true
49
+ enum iframe_embed_type: [:none, :embed_in_meeting_page, :open_in_live_event_page, :open_in_new_tab], _prefix: true
50
+
47
51
  component_manifest_name "meetings"
48
52
 
49
53
  validates :title, presence: true
@@ -53,6 +57,8 @@ module Decidim
53
57
  scope :published, -> { where.not(published_at: nil) }
54
58
  scope :past, -> { where(arel_table[:end_time].lteq(Time.current)) }
55
59
  scope :upcoming, -> { where(arel_table[:end_time].gteq(Time.current)) }
60
+ scope :withdrawn, -> { where(state: "withdrawn") }
61
+ scope :except_withdrawn, -> { where.not(state: "withdrawn").or(where(state: nil)) }
56
62
 
57
63
  scope :visible_meeting_for, lambda { |user|
58
64
  (all.published.distinct if user&.admin?) ||
@@ -95,7 +101,7 @@ module Decidim
95
101
  "
96
102
  end
97
103
 
98
- where(query, false, true, user.id, user.id, *user_role_queries.compact.map { user.id }).published.distinct
104
+ where(Arel.sql(query).to_s, false, true, user.id, user.id, *user_role_queries.compact.map { user.id }).published.distinct
99
105
  else
100
106
  published.visible
101
107
  end
@@ -121,6 +127,10 @@ module Decidim
121
127
  # we create a salt for the meeting only on new meetings to prevent changing old IDs for existing (Ether)PADs
122
128
  before_create :set_default_salt
123
129
 
130
+ def self.participants_iframe_embed_types
131
+ iframe_embed_types.except(:open_in_live_event_page)
132
+ end
133
+
124
134
  # Return registrations of a particular meeting made by users representing a group
125
135
  def user_group_registrations
126
136
  registrations.where.not(decidim_user_group_id: nil)
@@ -153,6 +163,10 @@ module Decidim
153
163
  end_time < Time.current
154
164
  end
155
165
 
166
+ def emendation?
167
+ false
168
+ end
169
+
156
170
  def has_available_slots?
157
171
  return true if available_slots.zero?
158
172
 
@@ -171,6 +185,11 @@ module Decidim
171
185
  component.settings.maps_enabled?
172
186
  end
173
187
 
188
+ # Public: Overrides the `accepts_new_comments?` CommentableWithComponent concern method.
189
+ def accepts_new_comments?
190
+ commentable? && !component.current_settings.comments_blocked && comments_allowed?
191
+ end
192
+
174
193
  # Public: Overrides the `allow_resource_permissions?` Resourceable concern method.
175
194
  def allow_resource_permissions?
176
195
  component.settings.resources_permissions_enabled
@@ -199,6 +218,17 @@ module Decidim
199
218
  Decidim::Meetings::Meeting.visible_meeting_for(user).exists?(id: id)
200
219
  end
201
220
 
221
+ def iframe_access_level_allowed_for_user?(user)
222
+ case iframe_access_level
223
+ when "all"
224
+ true
225
+ when "signed_in"
226
+ user.present?
227
+ else
228
+ has_registration_for?(user)
229
+ end
230
+ end
231
+
202
232
  # Return the duration of the meeting in minutes
203
233
  def meeting_duration
204
234
  @meeting_duration ||= ((end_time - start_time) / 1.minute).abs
@@ -210,6 +240,21 @@ module Decidim
210
240
  !private_meeting? || transparent?
211
241
  end
212
242
 
243
+ # Public: Checks if the author has withdrawn the meeting.
244
+ #
245
+ # Returns Boolean.
246
+ def withdrawn?
247
+ state == "withdrawn"
248
+ end
249
+
250
+ # Checks whether the user can withdraw the given meeting.
251
+ #
252
+ # user - the user to check for withdrawability.
253
+ # past meetings cannot be withdrawn
254
+ def withdrawable_by?(user)
255
+ user && !withdrawn? && !past? && authored_by?(user)
256
+ end
257
+
213
258
  # Overwrites method from Paddable to add custom rules in order to know
214
259
  # when to display a pad or not.
215
260
  def pad_is_visible?
@@ -280,6 +325,13 @@ module Decidim
280
325
  !!attendees_count && attendees_count.positive?
281
326
  end
282
327
 
328
+ def live?
329
+ start_time &&
330
+ end_time &&
331
+ Time.current >= (start_time - 10.minutes) &&
332
+ Time.current <= end_time
333
+ end
334
+
283
335
  def self.sort_by_translated_title_asc
284
336
  field = Arel::Nodes::InfixOperation.new("->>", arel_table[:title], Arel::Nodes.build_quoted(I18n.locale))
285
337
  order(Arel::Nodes::InfixOperation.new("", field, Arel.sql("ASC")))
@@ -2,6 +2,7 @@ import AutoButtonsByPositionComponent from "src/decidim/admin/auto_buttons_by_po
2
2
  import AutoLabelByPositionComponent from "src/decidim/admin/auto_label_by_position.component"
3
3
  import createSortList from "src/decidim/admin/sort_list.component"
4
4
  import createDynamicFields from "src/decidim/admin/dynamic_fields.component"
5
+ import createFieldDependentInputs from "src/decidim/admin/field_dependent_inputs.component"
5
6
  import attachGeocoding from "src/decidim/geocoding/attach_input"
6
7
 
7
8
  $(() => {
@@ -118,15 +119,14 @@ $(() => {
118
119
  });
119
120
 
120
121
  $meetingRegistrationType.trigger("change");
121
- }
122
122
 
123
- const $meetingForm = $(".meetings_form");
124
- if ($meetingForm.length > 0) {
125
- const $meetingTypeOfMeeting = $meetingForm.find("#meeting_type_of_meeting");
126
- const $meetingOnlineFields = $meetingForm.find(".field[data-meeting-type='online']");
127
- const $meetingInPersonFields = $meetingForm.find(".field[data-meeting-type='in_person']");
123
+ const $meetingTypeOfMeeting = $form.find("#meeting_type_of_meeting");
124
+ const $meetingOnlineFields = $form.find(".field[data-meeting-type='online']");
125
+ const $meetingInPersonFields = $form.find(".field[data-meeting-type='in_person']");
126
+ const $meetingOnlineAccessLevelFields = $form.find(".field[data-meeting-type='online-access-level']");
127
+ const $meetingIframeEmbedType = $form.find("#meeting_iframe_embed_type");
128
128
 
129
- const toggleDependsOnSelect = ($target, $showDiv, type) => {
129
+ const toggleTypeDependsOnSelect = ($target, $showDiv, type) => {
130
130
  const value = $target.val();
131
131
  if (value === "hybrid") {
132
132
  $showDiv.show();
@@ -140,11 +140,27 @@ $(() => {
140
140
 
141
141
  $meetingTypeOfMeeting.on("change", (ev) => {
142
142
  const $target = $(ev.target);
143
- toggleDependsOnSelect($target, $meetingOnlineFields, "online");
144
- toggleDependsOnSelect($target, $meetingInPersonFields, "in_person");
143
+ const embedTypeValue = $("#meeting_iframe_embed_type select").val();
144
+
145
+ toggleTypeDependsOnSelect($target, $meetingOnlineFields, "online");
146
+ toggleTypeDependsOnSelect($target, $meetingInPersonFields, "in_person");
147
+ if (embedTypeValue === "none") {
148
+ $meetingOnlineAccessLevelFields.hide();
149
+ } else {
150
+ toggleTypeDependsOnSelect($target, $meetingOnlineAccessLevelFields, "online");
151
+ }
145
152
  });
146
153
 
147
- toggleDependsOnSelect($meetingTypeOfMeeting, $meetingOnlineFields, "online");
148
- toggleDependsOnSelect($meetingTypeOfMeeting, $meetingInPersonFields, "in_person");
154
+ toggleTypeDependsOnSelect($meetingTypeOfMeeting, $meetingOnlineFields, "online");
155
+ toggleTypeDependsOnSelect($meetingTypeOfMeeting, $meetingInPersonFields, "in_person");
156
+ createFieldDependentInputs({
157
+ controllerField: $meetingIframeEmbedType,
158
+ wrapperSelector: ".iframe-fields",
159
+ dependentFieldsSelector: ".iframe-fields--access-level",
160
+ dependentInputSelector: "input",
161
+ enablingCondition: ($field) => {
162
+ return $field.find("select").val() !== "none"
163
+ }
164
+ });
149
165
  }
150
166
  })
@@ -1,4 +1,5 @@
1
1
  import attachGeocoding from "src/decidim/geocoding/attach_input"
2
+ import createFieldDependentInputs from "src/decidim/admin/field_dependent_inputs.component"
2
3
 
3
4
  $(() => {
4
5
  // Adds the latitude/longitude inputs after the geocoding is done
@@ -12,6 +13,7 @@ $(() => {
12
13
  const $meetingTypeOfMeeting = $form.find("#meeting_type_of_meeting");
13
14
  const $meetingOnlineFields = $form.find(".field[data-meeting-type='online']");
14
15
  const $meetingInPersonFields = $form.find(".field[data-meeting-type='in_person']");
16
+ const $meetingOnlineAccessLevelFields = $form.find(".field[data-meeting-type='online-access-level']");
15
17
 
16
18
  const toggleDependsOnSelect = ($target, $showDiv, type) => {
17
19
  const value = $target.val();
@@ -27,8 +29,15 @@ $(() => {
27
29
 
28
30
  $meetingTypeOfMeeting.on("change", (ev) => {
29
31
  const $target = $(ev.target);
32
+ const embedTypeValue = $("#meeting_iframe_embed_type").val();
33
+
30
34
  toggleDependsOnSelect($target, $meetingOnlineFields, "online");
31
35
  toggleDependsOnSelect($target, $meetingInPersonFields, "in_person");
36
+ if (embedTypeValue === "none") {
37
+ $meetingOnlineAccessLevelFields.hide();
38
+ } else {
39
+ toggleDependsOnSelect($target, $meetingOnlineAccessLevelFields, "online");
40
+ }
32
41
  });
33
42
 
34
43
  toggleDependsOnSelect($meetingTypeOfMeeting, $meetingOnlineFields, "online");
@@ -50,5 +59,15 @@ $(() => {
50
59
  toggleDependsOnSelect($meetingRegistrationType, $meetingAvailableSlots, "on_this_platform");
51
60
  toggleDependsOnSelect($meetingRegistrationType, $meetingRegistrationTerms, "on_this_platform");
52
61
  toggleDependsOnSelect($meetingRegistrationType, $meetingRegistrationUrl, "on_different_platform");
62
+
63
+ createFieldDependentInputs({
64
+ controllerField: $("#meeting_iframe_embed_type"),
65
+ wrapperSelector: ".iframe-fields",
66
+ dependentFieldsSelector: ".iframe-fields--access-level",
67
+ dependentInputSelector: "input",
68
+ enablingCondition: ($field) => {
69
+ return $field.val() !== "none"
70
+ }
71
+ });
53
72
  }
54
73
  });
@@ -5,37 +5,42 @@ $(() => {
5
5
  // Mount polls component for users
6
6
  const $container = $("[data-decidim-meetings-poll]");
7
7
  const $counter = $("#visible-questions-count");
8
- const poll = new MeetingsPollComponent($container, $container.data("decidim-meetings-poll"), $counter);
9
8
 
10
- $(".meeting-polls__action-list").on("click", (event) => {
11
- event.preventDefault();
9
+ if ($container.length) {
10
+ const poll = new MeetingsPollComponent($container, $container.data("decidim-meetings-poll"), $counter);
12
11
 
13
- if (poll.isMounted()) {
14
- $(event.target).removeClass(OPEN_CLASS);
15
- $container.removeClass(OPEN_CLASS);
16
- poll.unmountComponent();
17
- } else {
18
- $(event.target).addClass(OPEN_CLASS);
19
- $container.addClass(OPEN_CLASS);
20
- poll.mountComponent();
21
- }
22
- });
12
+ $(".meeting-polls__action-list").on("click", (event) => {
13
+ event.preventDefault();
14
+
15
+ if (poll.isMounted()) {
16
+ $(event.target).removeClass(OPEN_CLASS);
17
+ $container.removeClass(OPEN_CLASS);
18
+ poll.unmountComponent();
19
+ } else {
20
+ $(event.target).addClass(OPEN_CLASS);
21
+ $container.addClass(OPEN_CLASS);
22
+ poll.mountComponent();
23
+ }
24
+ });
25
+ }
23
26
 
24
27
  // Mount polls component for admins
25
28
  const $adminContainer = $("[data-decidim-admin-meetings-poll]");
26
- const adminPoll = new MeetingsPollComponent($adminContainer, $adminContainer.data("decidim-admin-meetings-poll"));
27
29
 
28
- $(".meeting-polls__action-administrate").on("click", (event) => {
29
- event.preventDefault();
30
+ if ($adminContainer.length) {
31
+ const adminPoll = new MeetingsPollComponent($adminContainer, $adminContainer.data("decidim-admin-meetings-poll"));
32
+ $(".meeting-polls__action-administrate").on("click", (event) => {
33
+ event.preventDefault();
30
34
 
31
- if (adminPoll.isMounted()) {
32
- $(event.target).removeClass(OPEN_CLASS);
33
- $adminContainer.removeClass(OPEN_CLASS);
34
- adminPoll.unmountComponent();
35
- } else {
36
- $(event.target).addClass(OPEN_CLASS);
37
- $adminContainer.addClass(OPEN_CLASS);
38
- adminPoll.mountComponent();
39
- }
40
- });
35
+ if (adminPoll.isMounted()) {
36
+ $(event.target).removeClass(OPEN_CLASS);
37
+ $adminContainer.removeClass(OPEN_CLASS);
38
+ adminPoll.unmountComponent();
39
+ } else {
40
+ $(event.target).addClass(OPEN_CLASS);
41
+ $adminContainer.addClass(OPEN_CLASS);
42
+ adminPoll.mountComponent();
43
+ }
44
+ });
45
+ }
41
46
  });
@@ -33,6 +33,8 @@ module Decidim
33
33
  toggle_allow(can_create_meetings?)
34
34
  when :update
35
35
  toggle_allow(can_update_meeting?)
36
+ when :withdraw
37
+ toggle_allow(can_withdraw_meeting?)
36
38
  when :close
37
39
  toggle_allow(can_close_meeting?)
38
40
  when :register
@@ -79,10 +81,16 @@ module Decidim
79
81
  !meeting.closed?
80
82
  end
81
83
 
84
+ def can_withdraw_meeting?
85
+ component_settings&.creation_enabled_for_participants? &&
86
+ meeting.authored_by?(user) &&
87
+ !meeting.withdrawn? &&
88
+ !meeting.past?
89
+ end
90
+
82
91
  def can_close_meeting?
83
92
  component_settings&.creation_enabled_for_participants? &&
84
93
  meeting.authored_by?(user) &&
85
- !meeting.closed? &&
86
94
  meeting.past?
87
95
  end
88
96