decidim-meetings 0.25.0.rc4 → 0.26.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/cells/decidim/meetings/content_blocks/highlighted_meetings/heading.erb +1 -1
- data/app/cells/decidim/meetings/content_blocks/highlighted_meetings_cell.rb +1 -1
- data/app/cells/decidim/meetings/content_blocks/{upcoming_events → upcoming_meetings}/show.erb +6 -6
- data/app/cells/decidim/meetings/content_blocks/{upcoming_events_cell.rb → upcoming_meetings_cell.rb} +11 -10
- data/app/cells/decidim/meetings/highlighted_meetings_for_component_cell.rb +1 -1
- data/app/cells/decidim/meetings/join_meeting_button_cell.rb +1 -1
- data/app/cells/decidim/meetings/meeting_list_item_cell.rb +10 -0
- data/app/cells/decidim/meetings/meeting_m_cell.rb +48 -0
- data/app/cells/decidim/meetings/meeting_s_cell.rb +22 -0
- data/app/cells/decidim/meetings/meetings_map_cell.rb +6 -0
- data/app/cells/decidim/meetings/online_meeting_cell.rb +23 -8
- data/app/cells/decidim/meetings/online_meeting_link/show.erb +2 -2
- data/app/cells/decidim/meetings/online_meeting_link_cell.rb +1 -5
- data/app/cells/decidim/meetings/public_participants_list_cell.rb +1 -0
- data/app/cells/decidim/meetings/question_responses_cell.rb +1 -1
- data/app/commands/decidim/meetings/admin/create_meeting.rb +11 -1
- data/app/commands/decidim/meetings/admin/publish_meeting.rb +3 -1
- data/app/commands/decidim/meetings/admin/update_meeting.rb +8 -2
- data/app/commands/decidim/meetings/create_meeting.rb +14 -2
- data/app/commands/decidim/meetings/update_meeting.rb +5 -2
- data/app/commands/decidim/meetings/withdraw_meeting.rb +39 -0
- data/app/controllers/concerns/decidim/meetings/filterable.rb +33 -0
- data/app/controllers/decidim/meetings/directory/application_controller.rb +16 -0
- data/app/controllers/decidim/meetings/directory/meetings_controller.rb +31 -20
- data/app/controllers/decidim/meetings/live_events_controller.rb +7 -1
- data/app/controllers/decidim/meetings/meetings_controller.rb +19 -17
- data/app/events/decidim/meetings/close_meeting_event.rb +1 -3
- data/app/events/decidim/meetings/create_meeting_event.rb +2 -4
- data/app/events/decidim/meetings/meeting_event.rb +37 -0
- data/app/events/decidim/meetings/meeting_registrations_enabled_event.rb +1 -3
- data/app/events/decidim/meetings/meeting_registrations_over_percentage_event.rb +2 -4
- data/app/events/decidim/meetings/registration_code_validated_event.rb +2 -4
- data/app/events/decidim/meetings/upcoming_meeting_event.rb +1 -3
- data/app/events/decidim/meetings/update_meeting_event.rb +1 -3
- data/app/forms/decidim/meetings/admin/close_meeting_form.rb +1 -1
- data/app/forms/decidim/meetings/admin/meeting_copy_form.rb +4 -13
- data/app/forms/decidim/meetings/admin/meeting_form.rb +34 -46
- data/app/forms/decidim/meetings/base_meeting_form.rb +59 -0
- data/app/forms/decidim/meetings/close_meeting_form.rb +6 -6
- data/app/forms/decidim/meetings/meeting_form.rb +30 -46
- data/app/helpers/decidim/meetings/application_helper.rb +13 -1
- data/app/helpers/decidim/meetings/directory/application_helper.rb +150 -0
- data/app/helpers/decidim/meetings/meeting_cells_helper.rb +4 -1
- data/app/helpers/decidim/meetings/meetings_helper.rb +3 -0
- data/app/models/decidim/meetings/meeting.rb +53 -1
- data/app/packs/src/decidim/meetings/admin/meetings_form.js +27 -11
- data/app/packs/src/decidim/meetings/meetings_form.js +19 -0
- data/app/packs/src/decidim/meetings/meetings_polls.js +31 -26
- data/app/permissions/decidim/meetings/permissions.rb +9 -1
- data/app/presenters/decidim/meetings/meeting_edition_presenter.rb +14 -0
- data/app/presenters/decidim/meetings/meeting_presenter.rb +13 -6
- data/app/services/decidim/meetings/diff_renderer.rb +4 -4
- data/app/services/decidim/meetings/directory/meeting_search.rb +53 -0
- data/app/services/decidim/meetings/meeting_iframe_embedder.rb +1 -1
- data/app/services/decidim/meetings/meeting_search.rb +15 -2
- data/app/views/decidim/meetings/admin/meeting_copies/_form.html.erb +16 -1
- data/app/views/decidim/meetings/admin/meeting_copies/new.html.erb +1 -1
- data/app/views/decidim/meetings/admin/meetings/_form.html.erb +15 -3
- data/app/views/decidim/meetings/directory/meetings/_filters.html.erb +34 -0
- data/app/views/decidim/meetings/directory/meetings/index.html.erb +1 -18
- data/app/views/decidim/meetings/meetings/_filters.html.erb +2 -0
- data/app/views/decidim/meetings/meetings/_filters_small_view.html.erb +3 -3
- data/app/views/decidim/meetings/meetings/_form.html.erb +14 -3
- data/app/views/decidim/meetings/meetings/_linked_meetings.html.erb +1 -1
- data/app/views/decidim/meetings/meetings/_meetings.html.erb +18 -0
- data/app/views/decidim/meetings/meetings/index.html.erb +1 -0
- data/app/views/decidim/meetings/meetings/show.html.erb +12 -13
- data/config/locales/ar.yml +2 -9
- data/config/locales/ca.yml +87 -22
- data/config/locales/cs.yml +83 -13
- data/config/locales/de.yml +2 -11
- data/config/locales/el.yml +2 -9
- data/config/locales/en.yml +41 -13
- data/config/locales/es-MX.yml +96 -9
- data/config/locales/es-PY.yml +96 -9
- data/config/locales/es.yml +136 -8
- data/config/locales/eu.yml +230 -11
- data/config/locales/fi-plain.yml +82 -13
- data/config/locales/fi.yml +83 -13
- data/config/locales/fr-CA.yml +114 -11
- data/config/locales/fr.yml +118 -14
- data/config/locales/ga-IE.yml +213 -0
- data/config/locales/gl.yml +30 -11
- data/config/locales/hu.yml +2 -9
- data/config/locales/id-ID.yml +2 -9
- data/config/locales/it.yml +43 -13
- data/config/locales/ja.yml +86 -16
- data/config/locales/lb-LU.yml +210 -0
- data/config/locales/lb.yml +410 -0
- data/config/locales/lv.yml +2 -9
- data/config/locales/nl.yml +66 -13
- data/config/locales/no.yml +2 -9
- data/config/locales/pl.yml +36 -11
- data/config/locales/pt-BR.yml +4 -14
- data/config/locales/pt.yml +153 -9
- data/config/locales/ro-RO.yml +193 -101
- data/config/locales/ru.yml +0 -8
- data/config/locales/sk.yml +2 -9
- data/config/locales/sl.yml +0 -3
- data/config/locales/sv.yml +108 -9
- data/config/locales/tr-TR.yml +2 -9
- data/config/locales/val-ES.yml +1 -0
- data/config/locales/zh-CN.yml +2 -9
- data/db/migrate/20210519133705_add_comments_availability_columns_to_meetings_table.rb +14 -0
- data/db/migrate/20210727085318_add_state_field_to_meeting.rb +7 -0
- data/db/migrate/20210903143040_add_iframe_access_level_to_decidim_meetings.rb +7 -0
- data/db/migrate/20210922140454_transform_show_embedded_iframe_column.rb +15 -0
- data/db/migrate/20210928095036_rename_upcoming_events_content_block_to_upcoming_meetings.rb +13 -0
- data/lib/decidim/api/meeting_type.rb +2 -1
- data/lib/decidim/meetings/component.rb +17 -7
- data/lib/decidim/meetings/directory_engine.rb +3 -3
- data/lib/decidim/meetings/engine.rb +8 -1
- data/lib/decidim/meetings/meeting_serializer.rb +2 -2
- data/lib/decidim/meetings/test/factories.rb +24 -3
- data/lib/decidim/meetings/test/notifications_handling.rb +39 -0
- data/lib/decidim/meetings/test/translated_event.rb +22 -0
- data/lib/decidim/meetings/version.rb +1 -1
- data/lib/decidim/meetings.rb +5 -0
- metadata +38 -20
@@ -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::
|
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 :
|
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 =
|
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? &&
|
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(:
|
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
|
-
|
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
|
-
|
124
|
-
|
125
|
-
const $
|
126
|
-
const $
|
127
|
-
const $
|
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
|
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
|
-
|
144
|
-
|
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
|
-
|
148
|
-
|
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
|
-
$
|
11
|
-
|
9
|
+
if ($container.length) {
|
10
|
+
const poll = new MeetingsPollComponent($container, $container.data("decidim-meetings-poll"), $counter);
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
poll.
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
$
|
29
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
|