decidim-meetings 0.29.1 → 0.30.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/meetings/cancel_registration_meeting_button/cancelation_modal.erb +1 -1
  3. data/app/cells/decidim/meetings/cancel_registration_meeting_button/show.erb +3 -1
  4. data/app/cells/decidim/meetings/cancel_registration_meeting_button_cell.rb +1 -1
  5. data/app/cells/decidim/meetings/dates_and_map/show.erb +6 -4
  6. data/app/cells/decidim/meetings/dates_and_map_cell.rb +1 -1
  7. data/app/cells/decidim/meetings/highlighted_meetings_for_component/show.erb +1 -1
  8. data/app/cells/decidim/meetings/join_meeting_button/show.erb +5 -2
  9. data/app/cells/decidim/meetings/meeting_card_metadata_cell.rb +3 -3
  10. data/app/cells/decidim/meetings/meeting_l_cell.rb +17 -0
  11. data/app/commands/decidim/meetings/admin/copy_meeting.rb +1 -2
  12. data/app/commands/decidim/meetings/admin/create_meeting.rb +8 -2
  13. data/app/commands/decidim/meetings/admin/update_meeting.rb +8 -2
  14. data/app/commands/decidim/meetings/create_meeting.rb +2 -2
  15. data/app/commands/decidim/meetings/update_meeting.rb +2 -3
  16. data/app/controllers/concerns/decidim/meetings/admin/filterable.rb +9 -5
  17. data/app/controllers/concerns/decidim/meetings/component_filterable.rb +1 -2
  18. data/app/controllers/decidim/meetings/admin/meetings_controller.rb +14 -22
  19. data/app/controllers/decidim/meetings/admin/registration_form_controller.rb +8 -0
  20. data/app/controllers/decidim/meetings/directory/meetings_controller.rb +2 -4
  21. data/app/controllers/decidim/meetings/meetings_controller.rb +40 -6
  22. data/app/forms/decidim/meetings/admin/close_meeting_form.rb +1 -1
  23. data/app/forms/decidim/meetings/admin/meeting_agenda_items_form.rb +1 -1
  24. data/app/forms/decidim/meetings/admin/meeting_form.rb +17 -31
  25. data/app/forms/decidim/meetings/admin/meeting_registrations_form.rb +2 -2
  26. data/app/forms/decidim/meetings/base_meeting_form.rb +6 -0
  27. data/app/forms/decidim/meetings/meeting_form.rb +2 -30
  28. data/app/helpers/decidim/meetings/admin/application_helper.rb +11 -1
  29. data/app/helpers/decidim/meetings/application_helper.rb +35 -1
  30. data/app/helpers/decidim/meetings/directory/application_helper.rb +9 -48
  31. data/app/helpers/decidim/meetings/meetings_helper.rb +5 -0
  32. data/app/models/decidim/meetings/invite.rb +10 -0
  33. data/app/models/decidim/meetings/meeting.rb +23 -1
  34. data/app/models/decidim/meetings/meeting_link.rb +25 -0
  35. data/app/packs/entrypoints/decidim_meetings_admin.js +1 -0
  36. data/app/packs/src/decidim/meetings/admin/meetings_components_form.js +77 -0
  37. data/app/packs/src/decidim/meetings/admin/meetings_form.js +8 -0
  38. data/app/packs/stylesheets/decidim/meetings/_item.scss +5 -17
  39. data/app/permissions/decidim/meetings/admin/permissions.rb +1 -1
  40. data/app/permissions/decidim/meetings/permissions.rb +20 -11
  41. data/app/presenters/decidim/meetings/admin_log/meeting_presenter.rb +1 -1
  42. data/app/presenters/decidim/meetings/meeting_presenter.rb +14 -6
  43. data/app/queries/decidim/meetings/filtered_meetings.rb +2 -2
  44. data/app/queries/decidim/meetings/metrics/meeting_followers_metric_measure.rb +2 -2
  45. data/app/queries/decidim/meetings/metrics/meetings_metric_manage.rb +6 -6
  46. data/app/serializers/decidim/meetings/base_download_your_data_serializer.rb +32 -0
  47. data/app/serializers/decidim/meetings/download_your_data_invite_serializer.rb +6 -26
  48. data/app/serializers/decidim/meetings/download_your_data_meeting_serializer.rb +15 -0
  49. data/app/serializers/decidim/meetings/download_your_data_registration_serializer.rb +6 -24
  50. data/app/views/decidim/meetings/admin/meetings/_component.html.erb +15 -0
  51. data/app/views/decidim/meetings/admin/meetings/_form.html.erb +7 -9
  52. data/app/views/decidim/meetings/admin/meetings/_linked_spaces.html.erb +53 -0
  53. data/app/views/decidim/meetings/admin/meetings/_meeting-tr.html.erb +42 -0
  54. data/app/views/decidim/meetings/admin/meetings/_meeting_actions.html.erb +70 -0
  55. data/app/views/decidim/meetings/admin/meetings/_meetings-thead.html.erb +26 -0
  56. data/app/views/decidim/meetings/admin/meetings/index.html.erb +16 -142
  57. data/app/views/decidim/meetings/admin/meetings/manage_trash.html.erb +23 -0
  58. data/app/views/decidim/meetings/admin/registration_form/edit_questions.html.erb +44 -0
  59. data/app/views/decidim/meetings/admin/registrations/edit.html.erb +1 -0
  60. data/app/views/decidim/meetings/directory/meetings/index.html.erb +1 -2
  61. data/app/views/decidim/meetings/live_events/show.html.erb +5 -5
  62. data/app/views/decidim/meetings/meetings/_form.html.erb +4 -8
  63. data/app/views/decidim/meetings/meetings/_meeting.html.erb +51 -26
  64. data/app/views/decidim/meetings/meetings/_meeting_actions.html.erb +34 -0
  65. data/app/views/decidim/meetings/meetings/_meeting_aside.html.erb +20 -59
  66. data/app/views/decidim/meetings/meetings/_meeting_poll_actions.html.erb +2 -5
  67. data/app/views/decidim/meetings/meetings/_schema_org_event_meeting.html.erb +3 -0
  68. data/app/views/decidim/meetings/meetings/index.html.erb +10 -2
  69. data/app/views/decidim/meetings/meetings/show.html.erb +5 -5
  70. data/app/views/decidim/meetings/polls/answers/index.html.erb +5 -5
  71. data/app/views/decidim/meetings/registration_mailer/confirmation.html.erb +1 -1
  72. data/app/views/decidim/meetings/shared/_filters.html.erb +1 -13
  73. data/app/views/decidim/meetings/shared/_index.html.erb +1 -1
  74. data/app/views/decidim/meetings/shared/_index.js.erb +5 -5
  75. data/app/views/decidim/meetings/shared/_meetings_aside.html.erb +2 -2
  76. data/app/views/decidim/participatory_spaces/_conference_venues.html.erb +1 -1
  77. data/config/locales/ar.yml +17 -15
  78. data/config/locales/bg.yml +5 -26
  79. data/config/locales/bn-BD.yml +1 -0
  80. data/config/locales/bs-BA.yml +8 -0
  81. data/config/locales/ca.yml +137 -25
  82. data/config/locales/cs.yml +139 -28
  83. data/config/locales/de.yml +172 -92
  84. data/config/locales/el.yml +1 -20
  85. data/config/locales/en.yml +133 -21
  86. data/config/locales/es-MX.yml +137 -25
  87. data/config/locales/es-PY.yml +137 -25
  88. data/config/locales/es.yml +137 -25
  89. data/config/locales/eu.yml +167 -55
  90. data/config/locales/fi-plain.yml +138 -26
  91. data/config/locales/fi.yml +150 -38
  92. data/config/locales/fr-CA.yml +79 -26
  93. data/config/locales/fr.yml +79 -26
  94. data/config/locales/ga-IE.yml +0 -10
  95. data/config/locales/gl.yml +1 -8
  96. data/config/locales/hu.yml +1 -17
  97. data/config/locales/id-ID.yml +1 -5
  98. data/config/locales/is-IS.yml +0 -4
  99. data/config/locales/it.yml +1 -12
  100. data/config/locales/ja.yml +96 -26
  101. data/config/locales/lb.yml +0 -8
  102. data/config/locales/lt.yml +1 -24
  103. data/config/locales/lv.yml +1 -5
  104. data/config/locales/nl.yml +1 -15
  105. data/config/locales/no.yml +1 -14
  106. data/config/locales/pl.yml +5 -22
  107. data/config/locales/pt-BR.yml +1 -22
  108. data/config/locales/pt.yml +1 -12
  109. data/config/locales/ro-RO.yml +32 -16
  110. data/config/locales/ru.yml +1 -5
  111. data/config/locales/sk.yml +1 -5
  112. data/config/locales/sv.yml +74 -26
  113. data/config/locales/tr-TR.yml +1 -8
  114. data/config/locales/uk.yml +0 -4
  115. data/config/locales/zh-CN.yml +1 -8
  116. data/config/locales/zh-TW.yml +1 -19
  117. data/db/migrate/20181107175558_add_questionnaire_to_existing_meetings.rb +1 -1
  118. data/db/migrate/20200827153856_add_commentable_counter_cache_to_meetings.rb +1 -1
  119. data/db/migrate/20201016065302_fix_meetings_registration_terms.rb +1 -1
  120. data/db/migrate/20210310120731_add_followable_counter_cache_to_meetings.rb +1 -1
  121. data/db/migrate/20240712104245_create_decidim_meetings_meeting_link.rb +12 -0
  122. data/db/migrate/20240828103603_add_deleted_at_to_decidim_meetings_meetings.rb +8 -0
  123. data/decidim-meetings.gemspec +2 -2
  124. data/lib/decidim/api/agenda_item_type.rb +6 -7
  125. data/lib/decidim/api/agenda_type.rb +3 -4
  126. data/lib/decidim/api/meeting_type.rb +44 -40
  127. data/lib/decidim/api/meetings_type.rb +5 -8
  128. data/lib/decidim/api/service_type.rb +1 -1
  129. data/lib/decidim/meetings/admin_engine.rb +9 -1
  130. data/lib/decidim/meetings/component.rb +14 -4
  131. data/lib/decidim/meetings/download_your_data_user_answers_serializer.rb +13 -7
  132. data/lib/decidim/meetings/engine.rb +2 -0
  133. data/lib/decidim/meetings/meeting_serializer.rb +86 -38
  134. data/lib/decidim/meetings/schema_org_event_meeting_serializer.rb +151 -0
  135. data/lib/decidim/meetings/seeds.rb +2 -4
  136. data/lib/decidim/meetings/test/factories.rb +14 -0
  137. data/lib/decidim/meetings/test/translated_event.rb +3 -3
  138. data/lib/decidim/meetings/version.rb +1 -1
  139. data/lib/decidim/meetings.rb +1 -0
  140. metadata +35 -22
  141. data/app/cells/decidim/meetings/meetings_map/show.erb +0 -16
  142. data/app/cells/decidim/meetings/meetings_map_cell.rb +0 -32
  143. data/app/commands/decidim/meetings/admin/destroy_meeting.rb +0 -21
  144. data/app/helpers/decidim/meetings/map_helper.rb +0 -21
  145. data/app/views/decidim/meetings/meetings/_actions.html.erb +0 -6
@@ -9,8 +9,6 @@ module Decidim
9
9
  attribute :location, String
10
10
  attribute :location_hints, String
11
11
 
12
- attribute :decidim_scope_id, Integer
13
- attribute :decidim_category_id, Integer
14
12
  attribute :user_group_id, Integer
15
13
  attribute :registration_type, String
16
14
  attribute :registrations_enabled, Boolean, default: false
@@ -21,8 +19,8 @@ module Decidim
21
19
  attribute :iframe_access_level, String
22
20
 
23
21
  validates :iframe_embed_type, inclusion: { in: Decidim::Meetings::Meeting.participants_iframe_embed_types }
24
- validates :title, presence: true
25
- validates :description, presence: true
22
+ validates :title, presence: true, etiquette: true
23
+ validates :description, presence: true, etiquette: true
26
24
  validates :type_of_meeting, presence: true
27
25
  validates :location, presence: true, if: ->(form) { form.in_person_meeting? || form.hybrid_meeting? }
28
26
  validates :online_meeting_url, presence: true, url: true, if: ->(form) { form.online_meeting? || form.hybrid_meeting? }
@@ -30,9 +28,6 @@ module Decidim
30
28
  validates :available_slots, numericality: { greater_than_or_equal_to: 0 }, presence: true, if: ->(form) { form.on_this_platform? }
31
29
  validates :registration_terms, presence: true, if: ->(form) { form.on_this_platform? }
32
30
  validates :registration_url, presence: true, url: true, if: ->(form) { form.on_different_platform? }
33
- validates :category, presence: true, if: ->(form) { form.decidim_category_id.present? }
34
- validates :scope, presence: true, if: ->(form) { form.decidim_scope_id.present? }
35
- validates :decidim_scope_id, scope_belongs_to_component: true, if: ->(form) { form.decidim_scope_id.present? }
36
31
  validates :clean_type_of_meeting, presence: true
37
32
  validates(
38
33
  :iframe_access_level,
@@ -41,10 +36,7 @@ module Decidim
41
36
  )
42
37
  validate :embeddable_meeting_url
43
38
 
44
- delegate :categories, to: :current_component
45
-
46
39
  def map_model(model)
47
- self.decidim_category_id = model.categorization.decidim_category_id if model.categorization
48
40
  presenter = MeetingEditionPresenter.new(model)
49
41
  self.title = presenter.title(all_locales: false)
50
42
  self.description = presenter.editor_description(all_locales: false)
@@ -56,26 +48,6 @@ module Decidim
56
48
 
57
49
  alias component current_component
58
50
 
59
- # Finds the Scope from the given decidim_scope_id, uses the component scope if missing.
60
- #
61
- # Returns a Decidim::Scope
62
- def scope
63
- @scope ||= @attributes["decidim_scope_id"].value ? current_component.scopes.find_by(id: @attributes["decidim_scope_id"].value) : current_component.scope
64
- end
65
-
66
- # Scope identifier
67
- #
68
- # Returns the scope identifier related to the meeting
69
- def decidim_scope_id
70
- super || scope&.id
71
- end
72
-
73
- def category
74
- return unless current_component
75
-
76
- @category ||= categories.find_by(id: decidim_category_id)
77
- end
78
-
79
51
  def clean_type_of_meeting
80
52
  type_of_meeting.presence
81
53
  end
@@ -7,7 +7,6 @@ module Decidim
7
7
  #
8
8
  module ApplicationHelper
9
9
  include Decidim::MapHelper
10
- include Decidim::Admin::ResourceScopeHelper
11
10
  include Decidim::PaginateHelper
12
11
 
13
12
  def tabs_id_for_service(service)
@@ -21,6 +20,17 @@ module Decidim
21
20
  def tabs_id_for_agenda_item_child(agenda_item)
22
21
  "meeting_agenda_item_#{agenda_item.to_param_child}"
23
22
  end
23
+
24
+ def find_meeting_components_for_select
25
+ spaces = current_organization.public_participatory_spaces
26
+ meeting_components = Decidim::Component
27
+ .where(manifest_name: "meetings", participatory_space_id: spaces.pluck(:id))
28
+ .where.not(id: current_component.id)
29
+
30
+ meeting_components.map do |component|
31
+ [component.hierarchy_title, component.id]
32
+ end.sort_by(&:first)
33
+ end
24
34
  end
25
35
  end
26
36
  end
@@ -7,7 +7,6 @@ module Decidim
7
7
  module ApplicationHelper
8
8
  include PaginateHelper
9
9
  include Decidim::MapHelper
10
- include Decidim::Meetings::MapHelper
11
10
  include Decidim::Comments::CommentsHelper
12
11
  include Decidim::SanitizeHelper
13
12
  include Decidim::CheckBoxesTreeHelper
@@ -39,6 +38,41 @@ module Decidim
39
38
  flat_filter_values(:all, :upcoming, :past, scope: "decidim.meetings.meetings.filters.date_values")
40
39
  end
41
40
 
41
+ # rubocop:disable Metrics/ParameterLists
42
+ # rubocop:disable Metrics/CyclomaticComplexity
43
+ def filter_sections(date: false, type: false, origin: false, taxonomies: false, space_type: false, activity: false)
44
+ @filter_sections ||= begin
45
+ items = []
46
+ if date
47
+ items.append(method: :with_any_date, collection: filter_date_values, label: t("decidim.meetings.meetings.filters.date"), id: "date",
48
+ type: :radio_buttons)
49
+ end
50
+ items.append(method: :with_any_type, collection: filter_type_values, label: t("decidim.meetings.meetings.filters.type"), id: "type") if type
51
+ if taxonomies
52
+ available_taxonomy_filters.each do |taxonomy_filter|
53
+ items.append(method: "with_any_taxonomies[#{taxonomy_filter.root_taxonomy_id}]",
54
+ collection: filter_taxonomy_values_for(taxonomy_filter),
55
+ label: decidim_sanitize_translated(taxonomy_filter.name),
56
+ id: "taxonomy-#{taxonomy_filter.root_taxonomy_id}")
57
+ end
58
+ end
59
+ items.append(method: :with_any_origin, collection: filter_origin_values, label: t("decidim.meetings.meetings.filters.origin"), id: "origin") if origin
60
+ if space_type
61
+ items.append(method: :with_any_space, collection: directory_meeting_spaces_values, label: t("decidim.meetings.directory.meetings.index.space_type"),
62
+ id: "space_type")
63
+ end
64
+ if activity
65
+ items.append(method: :activity, collection: activity_filter_values, label: t("decidim.meetings.meetings.filters.activity"), id: "activity",
66
+ type: :radio_buttons)
67
+ end
68
+ items.reject { |item| item[:collection].blank? }
69
+ end
70
+ end
71
+ # rubocop:enable Metrics/ParameterLists
72
+ # rubocop:enable Metrics/CyclomaticComplexity
73
+
74
+ delegate :available_taxonomy_filters, to: :current_component
75
+
42
76
  # Options to filter meetings by activity.
43
77
  def activity_filter_values
44
78
  flat_filter_values(:all, :my_meetings, scope: "decidim.meetings.meetings.filters")
@@ -8,7 +8,6 @@ module Decidim
8
8
  module ApplicationHelper
9
9
  include PaginateHelper
10
10
  include Decidim::MapHelper
11
- include Decidim::Meetings::MapHelper
12
11
  include Decidim::Meetings::MeetingsHelper
13
12
  include Decidim::Comments::CommentsHelper
14
13
  include Decidim::SanitizeHelper
@@ -27,6 +26,15 @@ module Decidim
27
26
  )
28
27
  end
29
28
 
29
+ def available_taxonomy_filters
30
+ @available_taxonomy_filters ||= begin
31
+ participatory_spaces = current_organization.public_participatory_spaces
32
+ components = Decidim::Component.where(manifest_name: "meetings", participatory_space: participatory_spaces)
33
+ filter_ids = components.map(&:settings).pluck(:taxonomy_filters).flatten.compact
34
+ Decidim::TaxonomyFilter.for(current_organization).where(id: filter_ids)
35
+ end
36
+ end
37
+
30
38
  def filter_date_values
31
39
  %w(all upcoming past).map { |k| [k, t(k, scope: "decidim.meetings.meetings.filters.date_values")] }
32
40
  end
@@ -43,57 +51,10 @@ module Decidim
43
51
  filter_tree_from_array(spaces)
44
52
  end
45
53
 
46
- def directory_filter_categories_values
47
- participatory_spaces = current_organization.public_participatory_spaces
48
- list_of_ps = participatory_spaces.flat_map do |current_participatory_space|
49
- next unless current_participatory_space.respond_to?(:categories)
50
-
51
- sorted_main_categories = current_participatory_space.categories.first_class.includes(:subcategories).sort_by do |category|
52
- [category.weight, translated_attribute(category.name, current_organization)]
53
- end
54
-
55
- categories_values = categories_values(sorted_main_categories)
56
-
57
- next if categories_values.empty?
58
-
59
- key_point = current_participatory_space.class.name.gsub("::", "__") + current_participatory_space.id.to_s
60
-
61
- TreeNode.new(
62
- TreePoint.new(key_point, translated_attribute(current_participatory_space.title, current_organization)),
63
- categories_values
64
- )
65
- end
66
-
67
- list_of_ps.compact!
68
- TreeNode.new(
69
- TreePoint.new("", t("decidim.meetings.application_helper.filter_category_values.all")),
70
- list_of_ps
71
- )
72
- end
73
-
74
54
  # Options to filter meetings by activity.
75
55
  def activity_filter_values
76
56
  %w(all my_meetings).map { |k| [k, t(k, scope: "decidim.meetings.meetings.filters")] }
77
57
  end
78
-
79
- protected
80
-
81
- def categories_values(sorted_main_categories)
82
- sorted_main_categories.flat_map do |category|
83
- sorted_descendant_categories = category.descendants.includes(:subcategories).sort_by do |subcategory|
84
- [subcategory.weight, translated_attribute(subcategory.name, current_organization)]
85
- end
86
-
87
- subcategories = sorted_descendant_categories.flat_map do |subcategory|
88
- TreePoint.new(subcategory.id.to_s, translated_attribute(subcategory.name, current_organization))
89
- end
90
-
91
- TreeNode.new(
92
- TreePoint.new(category.id.to_s, translated_attribute(category.name, current_organization)),
93
- subcategories
94
- )
95
- end
96
- end
97
58
  end
98
59
  end
99
60
  end
@@ -142,6 +142,11 @@ module Decidim
142
142
  base_url = "https://calendar.google.com/calendar/u/0/r/eventedit"
143
143
  "#{base_url}?#{params.to_param}"
144
144
  end
145
+
146
+ def render_schema_org_event_meeting(meeting)
147
+ exported_meeting = Decidim::Exporters::JSON.new([meeting], Decidim::Meetings::SchemaOrgEventMeetingSerializer).export.read
148
+ JSON.pretty_generate(JSON.parse(exported_meeting).first)
149
+ end
145
150
  end
146
151
  end
147
152
  end
@@ -33,6 +33,16 @@ module Decidim
33
33
  update!(rejected_at: Time.current, accepted_at: nil)
34
34
  end
35
35
  alias decline! reject!
36
+
37
+ def self.ransackable_attributes(auth_object = nil)
38
+ return [] unless auth_object&.admin?
39
+
40
+ %w(accepted_at rejected_at sent_at)
41
+ end
42
+
43
+ def self.ransackable_associations(_auth_object = nil)
44
+ %w(user)
45
+ end
36
46
  end
37
47
  end
38
48
  end
@@ -12,12 +12,14 @@ module Decidim
12
12
  include Decidim::HasReference
13
13
  include Decidim::ScopableResource
14
14
  include Decidim::HasCategory
15
+ include Decidim::Taxonomizable
15
16
  include Decidim::Followable
16
17
  include Decidim::Comments::CommentableWithComponent
17
18
  include Decidim::Comments::HasAvailabilityAttributes
18
19
  include Decidim::Searchable
19
20
  include Decidim::Traceable
20
21
  include Decidim::Loggable
22
+ include Decidim::DownloadYourData
21
23
  include Decidim::Forms::HasQuestionnaire
22
24
  include Decidim::Paddable
23
25
  include Decidim::ActsAsAuthor
@@ -26,6 +28,7 @@ module Decidim
26
28
  include Decidim::TranslatableResource
27
29
  include Decidim::Publicable
28
30
  include Decidim::FilterableResource
31
+ include Decidim::SoftDeletable
29
32
 
30
33
  TYPE_OF_MEETING = { in_person: 0, online: 10, hybrid: 20 }.freeze
31
34
  REGISTRATION_TYPES = { registration_disabled: 0, on_this_platform: 10, on_different_platform: 20 }.freeze
@@ -45,6 +48,8 @@ module Decidim
45
48
  foreign_key: :decidim_user_id,
46
49
  source: :user
47
50
  )
51
+ has_many :meeting_links, dependent: :destroy, class_name: "Decidim::Meetings::MeetingLink", foreign_key: "decidim_meeting_id"
52
+ has_many :components, through: :meeting_links, class_name: "Decidim::Component", foreign_key: "decidim_component_id"
48
53
 
49
54
  enum iframe_access_level: [:all, :signed_in, :registered], _prefix: true
50
55
  enum iframe_embed_type: [:none, :embed_in_meeting_page, :open_in_live_event_page, :open_in_new_tab], _prefix: true
@@ -57,6 +62,7 @@ module Decidim
57
62
 
58
63
  geocoded_by :address
59
64
 
65
+ scope :closed, -> { where.not(closed_at: nil) }
60
66
  scope :published, -> { where.not(published_at: nil) }
61
67
  scope :past, -> { where(arel_table[:end_time].lteq(Time.current)) }
62
68
  scope :upcoming, -> { where(arel_table[:end_time].gteq(Time.current)) }
@@ -151,6 +157,10 @@ module Decidim
151
157
  # we create a salt for the meeting only on new meetings to prevent changing old IDs for existing (Ether)PADs
152
158
  before_create :set_default_salt
153
159
 
160
+ def self.export_serializer
161
+ Decidim::Meetings::DownloadYourDataMeetingSerializer
162
+ end
163
+
154
164
  def self.participants_iframe_embed_types
155
165
  iframe_embed_types.except(:open_in_live_event_page)
156
166
  end
@@ -370,7 +380,19 @@ module Decidim
370
380
  end
371
381
 
372
382
  def self.ransackable_scopes(_auth_object = nil)
373
- [:with_any_type, :with_any_date, :with_any_space, :with_any_origin, :with_any_scope, :with_any_category, :with_any_global_category]
383
+ [:with_any_type, :with_any_date, :with_any_space, :with_any_origin, :with_any_taxonomies, :with_any_global_category]
384
+ end
385
+
386
+ def self.ransackable_attributes(auth_object = nil)
387
+ base = %w(description id_string search_text title)
388
+
389
+ return base unless auth_object&.admin?
390
+
391
+ base + %w(is_upcoming closed_at)
392
+ end
393
+
394
+ def self.ransackable_associations(_auth_object = nil)
395
+ %w(taxonomies)
374
396
  end
375
397
 
376
398
  def self.ransack(params = {}, options = {})
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Meetings
5
+ class MeetingLink < Meetings::ApplicationRecord
6
+ include Decidim::HasComponent
7
+
8
+ belongs_to :meeting, foreign_key: "decidim_meeting_id", class_name: "Decidim::Meetings::Meeting"
9
+ belongs_to :component, foreign_key: "decidim_component_id", class_name: "Decidim::Component"
10
+
11
+ # Finds all the meetings linked to the given component
12
+ # filtering out meetings that belong to private not transparent spaces.
13
+ def self.find_meetings(component:)
14
+ meetings = Meeting
15
+ .joins(:meeting_links)
16
+ .where("decidim_meetings_meeting_links.component": component)
17
+ .filter do |meeting|
18
+ !meeting.component.private_non_transparent_space?
19
+ end
20
+
21
+ Meeting.where(id: meetings.map(&:id))
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,5 +1,6 @@
1
1
  import "src/decidim/meetings/admin/agendas"
2
2
  import "src/decidim/meetings/admin/destroy_meeting_alert"
3
3
  import "src/decidim/meetings/admin/meetings_form"
4
+ import "src/decidim/meetings/admin/meetings_components_form"
4
5
  import "src/decidim/meetings/admin/registrations_form"
5
6
  import "src/decidim/meetings/admin/registrations_invite_form"
@@ -0,0 +1,77 @@
1
+ import TomSelect from "tom-select/dist/cjs/tom-select.popular";
2
+ import createTooltip from "src/decidim/tooltips"
3
+
4
+ /**
5
+ * This module manages the Linked Spaces section from the
6
+ * admin meeting edit form.
7
+ *
8
+ * It allows to add and remove components to the meeting and
9
+ * setup a TomSelect for the components selector.
10
+ */
11
+
12
+ const handleRemoveButton = (button) => {
13
+ button.addEventListener("click", function() {
14
+ button.closest("tr").remove();
15
+ });
16
+ };
17
+
18
+ const handleAddButton = () => {
19
+ const select = document.querySelector("select[name='add_component_select']");
20
+ const componentId = select.value;
21
+ const componentTitle = select.options[select.selectedIndex].text;
22
+
23
+ if (!componentId) {
24
+ return;
25
+ }
26
+
27
+ const table = document.querySelector(".js-components");
28
+ const body = document.querySelector(".js-components tbody");
29
+ const template = document.querySelector("#meeting_component_template");
30
+ const clone = template.content.cloneNode(true);
31
+ const title = clone.querySelector(".js-component-title");
32
+ const id = clone.querySelector("input");
33
+ const button = clone.querySelector(".js-remove-component");
34
+
35
+ title.textContent = componentTitle;
36
+ id.value = componentId;
37
+ if (button) {
38
+ handleRemoveButton(button);
39
+ }
40
+
41
+ body.appendChild(clone);
42
+
43
+ const tooltips = body.querySelectorAll("[data-tooltip]")
44
+
45
+ if (tooltips.length) {
46
+ createTooltip(tooltips[tooltips.length - 1]);
47
+ }
48
+
49
+ select.value = "";
50
+ table.classList.remove("hidden");
51
+ };
52
+
53
+ const setupTomSelect = () => {
54
+ const componentsSelect = document.querySelector(
55
+ "#add_component_select"
56
+ );
57
+
58
+ const config = {
59
+ plugins: ["dropdown_input"]
60
+ };
61
+
62
+ if (componentsSelect) {
63
+ return new TomSelect(componentsSelect, config);
64
+ }
65
+ return null;
66
+ }
67
+
68
+ document.addEventListener("DOMContentLoaded", () => {
69
+ document.querySelectorAll(".js-remove-component").forEach(handleRemoveButton);
70
+ const addButton = document.querySelector(".js-add-component")
71
+
72
+ if (addButton) {
73
+ addButton.addEventListener("click", handleAddButton);
74
+ }
75
+
76
+ setupTomSelect();
77
+ });
@@ -86,10 +86,16 @@ $(() => {
86
86
  if ($form.length > 0) {
87
87
  const $privateMeeting = $form.find("#private_meeting");
88
88
  const $transparent = $form.find("#transparent");
89
+ const $warning = $form.find(".js-private-warning");
90
+ const $assign = $form.find(".js-add-component");
89
91
 
90
92
  const toggleDisabledHiddenFields = () => {
91
93
  const enabledPrivateSpace = $privateMeeting.find("input[type='checkbox']").prop("checked");
94
+ const enabledTransparent = $transparent.find("input[type='checkbox']").prop("checked");
95
+
92
96
  $transparent.find("input[type='checkbox']").attr("disabled", "disabled");
97
+ $warning?.toggleClass("hidden", !enabledPrivateSpace || enabledTransparent);
98
+ $assign?.attr("disabled", enabledPrivateSpace && !enabledTransparent);
93
99
 
94
100
  if (enabledPrivateSpace) {
95
101
  $transparent.find("input[type='checkbox']").attr("disabled", !enabledPrivateSpace);
@@ -97,6 +103,8 @@ $(() => {
97
103
  };
98
104
 
99
105
  $privateMeeting.on("change", toggleDisabledHiddenFields);
106
+ $transparent.on("change", toggleDisabledHiddenFields);
107
+
100
108
  toggleDisabledHiddenFields();
101
109
 
102
110
  attachGeocoding($form.find("#meeting_address"));
@@ -6,29 +6,17 @@
6
6
  }
7
7
 
8
8
  &__calendar {
9
- @apply w-14 flex flex-col justify-start rounded bg-background text-center;
9
+ @apply order-first flex flex-col min-w-48 rounded bg-background text-center md:w-14;
10
10
 
11
11
  &:only-child &-month {
12
12
  @apply rounded-t;
13
13
  }
14
14
 
15
15
  &-container {
16
- @apply grid grid-cols-[auto_1fr] md:grid-cols-[auto_1fr_1fr] items-center gap-0 md:gap-x-5 border-4 border-background rounded md:h-[140px] overflow-hidden;
16
+ @apply flex flex-wrap flex-col md:flex-row gap-2 border-4 border-background rounded;
17
17
 
18
- > *:nth-child(2) {
19
- @apply p-4 md:p-0;
20
-
21
- &:not(:last-child) {
22
- @apply col-span-2 md:col-auto order-2 md:order-1;
23
- }
24
-
25
- &:last-child {
26
- @apply md:col-span-2 pr-4;
27
- }
28
- }
29
-
30
- > *:nth-child(3) {
31
- @apply order-1 md:order-2 h-[135px] md:h-full;
18
+ ul {
19
+ @apply flex-1 p-2;
32
20
  }
33
21
  }
34
22
 
@@ -55,7 +43,7 @@
55
43
  }
56
44
 
57
45
  &__lg {
58
- @apply w-fit justify-center [&>*]:px-2 min-w-24 h-[8.5rem];
46
+ @apply flex min-w-48;
59
47
  }
60
48
 
61
49
  &__lg &-month {
@@ -41,7 +41,7 @@ module Decidim
41
41
  return disallow! if meeting && !meeting.official?
42
42
 
43
43
  case permission_action.action
44
- when :close, :copy, :destroy, :export_registrations, :update, :read_invites
44
+ when :close, :copy, :export_registrations, :update, :read_invites
45
45
  toggle_allow(meeting.present?)
46
46
  when :invite_attendee
47
47
  toggle_allow(meeting.present? && meeting.registrations_enabled?)
@@ -4,12 +4,17 @@ module Decidim
4
4
  module Meetings
5
5
  class Permissions < Decidim::DefaultPermissions
6
6
  def permissions
7
- return permission_action unless user
8
-
9
7
  # Delegate the admin permission checks to the admin permissions class
10
8
  return Decidim::Meetings::Admin::Permissions.new(user, permission_action, context).permissions if permission_action.scope == :admin
11
9
  return permission_action if permission_action.scope != :public
12
10
 
11
+ if permission_action.subject == :meeting && permission_action.action == :read
12
+ toggle_allow(!meeting&.hidden? && meeting&.current_user_can_visit_meeting?(user))
13
+ return permission_action
14
+ end
15
+
16
+ return permission_action unless user
17
+
13
18
  case permission_action.subject
14
19
  when :answer
15
20
  case permission_action.action
@@ -79,13 +84,20 @@ module Decidim
79
84
  end
80
85
 
81
86
  def can_create_meetings?
82
- component_settings&.creation_enabled_for_participants? && public_space_or_member?
87
+ (component_settings&.creation_enabled_for_participants? && can_participate?) || initiative_authorship?
83
88
  end
84
89
 
85
- def public_space_or_member?
90
+ def can_participate?
91
+ context[:current_component].participatory_space.can_participate?(user)
92
+ end
93
+
94
+ def initiative_authorship?
95
+ return false unless Decidim.module_installed?("initiatives")
96
+
86
97
  participatory_space = context[:current_component].participatory_space
87
98
 
88
- participatory_space.private_space? ? space_member?(participatory_space, user) : true
99
+ participatory_space.is_a?(Decidim::Initiative) &&
100
+ participatory_space.has_authorship?(user)
89
101
  end
90
102
 
91
103
  # Neither platform admins, nor space admins should be able to create meetings from the public side.
@@ -96,21 +108,18 @@ module Decidim
96
108
  end
97
109
 
98
110
  def can_update_meeting?
99
- component_settings&.creation_enabled_for_participants? &&
100
- meeting.authored_by?(user) &&
111
+ meeting.authored_by?(user) &&
101
112
  !meeting.closed?
102
113
  end
103
114
 
104
115
  def can_withdraw_meeting?
105
- component_settings&.creation_enabled_for_participants? &&
106
- meeting.authored_by?(user) &&
116
+ meeting.authored_by?(user) &&
107
117
  !meeting.withdrawn? &&
108
118
  !meeting.past?
109
119
  end
110
120
 
111
121
  def can_close_meeting?
112
- component_settings&.creation_enabled_for_participants? &&
113
- meeting.authored_by?(user) &&
122
+ meeting.authored_by?(user) &&
114
123
  meeting.past?
115
124
  end
116
125
 
@@ -39,7 +39,7 @@ module Decidim
39
39
 
40
40
  def action_string
41
41
  case action
42
- when "close", "create", "delete", "export_registrations", "update"
42
+ when "close", "create", "delete", "export_registrations", "update", "soft_delete", "restore"
43
43
  "decidim.meetings.admin_log.meeting.#{action}"
44
44
  else
45
45
  super
@@ -10,6 +10,8 @@ module Decidim
10
10
  include ActionView::Helpers::UrlHelper
11
11
  include Decidim::SanitizeHelper
12
12
 
13
+ alias super_title title
14
+
13
15
  def meeting
14
16
  __getobj__
15
17
  end
@@ -18,6 +20,12 @@ module Decidim
18
20
  Decidim::ResourceLocatorPresenter.new(meeting).path
19
21
  end
20
22
 
23
+ def taxonomy_names(html_escape: false, all_locales: false)
24
+ meeting.taxonomies.map do |taxonomy|
25
+ super_title(taxonomy.name, false, html_escape, all_locales)
26
+ end
27
+ end
28
+
21
29
  def display_mention
22
30
  link_to title, meeting_path
23
31
  end
@@ -25,7 +33,7 @@ module Decidim
25
33
  def title(links: false, html_escape: false, all_locales: false)
26
34
  return unless meeting
27
35
 
28
- super meeting.title, links, html_escape, all_locales
36
+ super(meeting.title, links, html_escape, all_locales)
29
37
  end
30
38
 
31
39
  def description(links: false, extras: true, strip_tags: false, all_locales: false)
@@ -74,7 +82,7 @@ module Decidim
74
82
  return unless meeting
75
83
 
76
84
  handle_locales(meeting.registration_email_custom_content, all_locales) do |content|
77
- renderer = Decidim::ContentRenderers::HashtagRenderer.new(sanitized(content))
85
+ renderer = Decidim::ContentRenderers::HashtagRenderer.new(decidim_sanitize_editor_admin(content))
78
86
  renderer.render(links:).html_safe
79
87
  end
80
88
  end
@@ -103,6 +111,10 @@ module Decidim
103
111
  ""
104
112
  end
105
113
 
114
+ def space_title
115
+ translated_attribute component.participatory_space.title
116
+ end
117
+
106
118
  def profile_path
107
119
  resource_locator(meeting).path
108
120
  end
@@ -135,10 +147,6 @@ module Decidim
135
147
 
136
148
  proposals.map.with_index { |proposal, index| "#{index + 1}) #{proposal.title}\n" }
137
149
  end
138
-
139
- def sanitized(content)
140
- decidim_sanitize_editor(content)
141
- end
142
150
  end
143
151
  end
144
152
  end
@@ -28,8 +28,8 @@ module Decidim
28
28
  # by a range of dates.
29
29
  def query
30
30
  meetings = Decidim::Meetings::Meeting.not_hidden.published.where(component: @components)
31
- meetings = meetings.where("created_at >= ?", @start_at) if @start_at.present?
32
- meetings = meetings.where("created_at <= ?", @end_at) if @end_at.present?
31
+ meetings = meetings.where(created_at: @start_at..) if @start_at.present?
32
+ meetings = meetings.where(created_at: ..@end_at) if @end_at.present?
33
33
  meetings
34
34
  end
35
35
  end