decidim-proposals 0.29.2 → 0.30.0.rc2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/cells/decidim/proposals/highlighted_proposals_for_component/show.erb +1 -1
- data/app/cells/decidim/proposals/highlighted_proposals_for_component_cell.rb +1 -1
- data/app/cells/decidim/proposals/participatory_text_proposal_cell.rb +1 -1
- data/app/cells/decidim/proposals/proposal_g/show.erb +13 -0
- data/app/cells/decidim/proposals/proposal_g_cell.rb +13 -0
- data/app/cells/decidim/proposals/proposal_history_cell.rb +107 -0
- data/app/cells/decidim/proposals/proposal_l/show.erb +37 -0
- data/app/cells/decidim/proposals/proposal_l_cell.rb +9 -0
- data/app/cells/decidim/proposals/proposal_metadata_cell.rb +2 -2
- data/app/cells/decidim/proposals/proposal_vote/show.erb +75 -0
- data/app/cells/decidim/proposals/proposal_vote_cell.rb +43 -0
- data/app/commands/decidim/proposals/accept_coauthorship.rb +62 -0
- data/app/commands/decidim/proposals/admin/assign_proposals_to_valuator.rb +14 -0
- data/app/commands/decidim/proposals/admin/create_proposal.rb +6 -14
- data/app/commands/decidim/proposals/admin/create_proposal_note.rb +20 -11
- data/app/commands/decidim/proposals/admin/import_proposals.rb +0 -5
- data/app/commands/decidim/proposals/admin/merge_proposals.rb +2 -2
- data/app/commands/decidim/proposals/admin/proposal_notes_methods.rb +48 -0
- data/app/commands/decidim/proposals/admin/reply_proposal_note.rb +92 -0
- data/app/commands/decidim/proposals/admin/split_proposals.rb +2 -2
- data/app/commands/decidim/proposals/admin/update_proposal.rb +10 -16
- data/app/commands/decidim/proposals/admin/update_proposal_taxonomies.rb +34 -0
- data/app/commands/decidim/proposals/cancel_coauthorship.rb +32 -0
- data/app/commands/decidim/proposals/create_collaborative_draft.rb +1 -2
- data/app/commands/decidim/proposals/create_proposal.rb +1 -2
- data/app/commands/decidim/proposals/invite_coauthor.rb +45 -0
- data/app/commands/decidim/proposals/publish_collaborative_draft.rb +1 -2
- data/app/commands/decidim/proposals/reject_coauthorship.rb +54 -0
- data/app/commands/decidim/proposals/update_collaborative_draft.rb +1 -2
- data/app/commands/decidim/proposals/update_proposal.rb +1 -2
- data/app/controllers/concerns/decidim/proposals/admin/filterable.rb +5 -1
- data/app/controllers/concerns/decidim/proposals/admin/needs_interpolations.rb +40 -0
- data/app/controllers/decidim/proposals/admin/proposal_answers_controller.rb +55 -2
- data/app/controllers/decidim/proposals/admin/proposal_notes_controller.rb +18 -0
- data/app/controllers/decidim/proposals/admin/proposals_controller.rb +41 -85
- data/app/controllers/decidim/proposals/collaborative_drafts_controller.rb +2 -4
- data/app/controllers/decidim/proposals/invite_coauthors_controller.rb +87 -0
- data/app/controllers/decidim/proposals/proposals_controller.rb +7 -32
- data/app/events/decidim/proposals/accepted_coauthorship_event.rb +8 -0
- data/app/events/decidim/proposals/admin/proposal_assigned_to_valuator_event.rb +27 -0
- data/app/events/decidim/proposals/admin/proposal_note_created_event.rb +5 -0
- data/app/events/decidim/proposals/coauthor_accepted_invite_event.rb +49 -0
- data/app/events/decidim/proposals/coauthor_invited_event.rb +45 -0
- data/app/events/decidim/proposals/coauthor_rejected_invite_event.rb +8 -0
- data/app/events/decidim/proposals/rejected_coauthorship_event.rb +8 -0
- data/app/events/decidim/proposals/update_proposal_taxonomies_event.rb +9 -0
- data/app/forms/decidim/proposals/admin/proposal_answer_form.rb +1 -0
- data/app/forms/decidim/proposals/admin/proposal_base_form.rb +3 -31
- data/app/forms/decidim/proposals/admin/proposal_form.rb +11 -6
- data/app/forms/decidim/proposals/admin/proposals_import_form.rb +0 -5
- data/app/forms/decidim/proposals/collaborative_draft_form.rb +0 -8
- data/app/forms/decidim/proposals/proposal_form.rb +5 -32
- data/app/helpers/decidim/proposals/admin/proposal_bulk_actions_helper.rb +25 -0
- data/app/helpers/decidim/proposals/admin/proposals_helper.rb +0 -1
- data/app/helpers/decidim/proposals/application_helper.rb +24 -14
- data/app/helpers/decidim/proposals/collaborative_draft_helper.rb +7 -7
- data/app/helpers/decidim/proposals/map_helper.rb +0 -18
- data/app/helpers/decidim/proposals/proposal_votes_helper.rb +15 -2
- data/app/jobs/decidim/proposals/admin/proposal_answer_job.rb +20 -0
- data/app/models/decidim/proposals/collaborative_draft.rb +10 -1
- data/app/models/decidim/proposals/proposal.rb +66 -5
- data/app/models/decidim/proposals/proposal_note.rb +11 -0
- data/app/models/decidim/proposals/proposal_state.rb +1 -1
- data/app/packs/entrypoints/decidim_proposals.js +1 -0
- data/app/packs/entrypoints/decidim_proposals_geocoding.js +2 -0
- data/app/packs/src/decidim/proposals/admin/proposals.js +16 -1
- data/app/packs/src/decidim/proposals/exit_handler.js +73 -0
- data/app/packs/stylesheets/decidim/proposals/proposals.scss +248 -3
- data/app/permissions/decidim/proposals/admin/permissions.rb +2 -5
- data/app/permissions/decidim/proposals/permissions.rb +42 -0
- data/app/presenters/decidim/proposals/admin_log/proposal_presenter.rb +1 -1
- data/app/presenters/decidim/proposals/proposal_presenter.rb +1 -1
- data/app/queries/decidim/proposals/filtered_proposals.rb +2 -2
- data/app/queries/decidim/proposals/metrics/accepted_proposals_metric_manage.rb +2 -2
- data/app/queries/decidim/proposals/metrics/endorsements_metric_manage.rb +10 -10
- data/app/queries/decidim/proposals/metrics/proposal_followers_metric_measure.rb +4 -4
- data/app/queries/decidim/proposals/metrics/proposal_participants_metric_measure.rb +6 -6
- data/app/queries/decidim/proposals/metrics/proposals_metric_manage.rb +6 -6
- data/app/queries/decidim/proposals/metrics/votes_metric_manage.rb +6 -6
- data/app/services/decidim/proposals/proposal_builder.rb +1 -1
- data/app/views/decidim/proposals/admin/proposal_notes/_form.html.erb +3 -3
- data/app/views/decidim/proposals/admin/proposal_notes/_proposal_note.html.erb +28 -0
- data/app/views/decidim/proposals/admin/proposal_notes/_proposal_note_reply.html.erb +9 -0
- data/app/views/decidim/proposals/admin/proposal_notes/_proposal_notes.html.erb +4 -28
- data/app/views/decidim/proposals/admin/proposal_states/_form.html.erb +1 -1
- data/app/views/decidim/proposals/admin/proposals/_actions.html.erb +21 -0
- data/app/views/decidim/proposals/admin/proposals/_bulk-actions.html.erb +3 -2
- data/app/views/decidim/proposals/admin/proposals/_form.html.erb +17 -24
- data/app/views/decidim/proposals/admin/proposals/_proposal-tr.html.erb +12 -28
- data/app/views/decidim/proposals/admin/proposals/_proposals-thead.html.erb +45 -0
- data/app/views/decidim/proposals/admin/proposals/bulk_actions/_apply_answer_template.html.erb +22 -0
- data/app/views/decidim/proposals/admin/proposals/bulk_actions/_dropdown.html.erb +15 -11
- data/app/views/decidim/proposals/admin/proposals/bulk_actions/_taxonomy_change.html.erb +23 -0
- data/app/views/decidim/proposals/admin/proposals/index.html.erb +17 -48
- data/app/views/decidim/proposals/admin/proposals/manage_trash.html.erb +18 -0
- data/app/views/decidim/proposals/admin/proposals/publish_answers.js.erb +1 -1
- data/app/views/decidim/proposals/admin/proposals/show.html.erb +14 -26
- data/app/views/decidim/proposals/admin/proposals/update_attribute.js.erb +1 -1
- data/app/views/decidim/proposals/admin/proposals_imports/new.html.erb +0 -3
- data/app/views/decidim/proposals/collaborative_drafts/_collaborative_actions.html.erb +9 -0
- data/app/views/decidim/proposals/collaborative_drafts/_collaborative_draft_aside.html.erb +0 -15
- data/app/views/decidim/proposals/collaborative_drafts/_edit_form_fields.html.erb +4 -6
- data/app/views/decidim/proposals/collaborative_drafts/index.html.erb +6 -2
- data/app/views/decidim/proposals/collaborative_drafts/show.html.erb +27 -11
- data/app/views/decidim/proposals/proposal_votes/update_buttons_and_counters.js.erb +29 -9
- data/app/views/decidim/proposals/proposals/_actions.html.erb +4 -7
- data/app/views/decidim/proposals/proposals/_edit_form_fields.html.erb +17 -22
- data/app/views/decidim/proposals/proposals/_exit_modal.html.erb +17 -0
- data/app/views/decidim/proposals/proposals/_notification_alert_box.html.erb +1 -0
- data/app/views/decidim/proposals/proposals/_proposal_actions.html.erb +19 -0
- data/app/views/decidim/proposals/proposals/_proposal_aside.html.erb +9 -32
- data/app/views/decidim/proposals/proposals/_proposal_voting_rules.html.erb +33 -0
- data/app/views/decidim/proposals/proposals/_remaining_votes_count.html.erb +2 -2
- data/app/views/decidim/proposals/proposals/_remaining_votes_notification.html.erb +12 -0
- data/app/views/decidim/proposals/proposals/_update_proposal_voting_rules.html.erb +6 -0
- data/app/views/decidim/proposals/proposals/_vote_button.html.erb +12 -8
- data/app/views/decidim/proposals/proposals/_votes_count.html.erb +2 -1
- data/app/views/decidim/proposals/proposals/_voting_rules.html.erb +1 -7
- data/app/views/decidim/proposals/proposals/index.html.erb +10 -18
- data/app/views/decidim/proposals/proposals/index.js.erb +1 -1
- data/app/views/decidim/proposals/proposals/participatory_texts/_proposal_vote_button.html.erb +3 -1
- data/app/views/decidim/proposals/proposals/show.html.erb +35 -15
- data/config/locales/ar.yml +18 -72
- data/config/locales/bg.yml +7 -89
- data/config/locales/bs-BA.yml +0 -13
- data/config/locales/ca.yml +212 -72
- data/config/locales/cs.yml +213 -73
- data/config/locales/de.yml +215 -75
- data/config/locales/el.yml +8 -82
- data/config/locales/en.yml +209 -69
- data/config/locales/es-MX.yml +213 -73
- data/config/locales/es-PY.yml +213 -73
- data/config/locales/es.yml +215 -75
- data/config/locales/eu.yml +217 -78
- data/config/locales/fi-plain.yml +216 -75
- data/config/locales/fi.yml +216 -75
- data/config/locales/fr-CA.yml +118 -87
- data/config/locales/fr.yml +118 -87
- data/config/locales/ga-IE.yml +0 -19
- data/config/locales/gl.yml +8 -43
- data/config/locales/hu.yml +6 -66
- data/config/locales/id-ID.yml +8 -40
- data/config/locales/is-IS.yml +0 -14
- data/config/locales/it.yml +8 -53
- data/config/locales/ja.yml +162 -87
- data/config/locales/lt.yml +8 -83
- data/config/locales/lv.yml +8 -50
- data/config/locales/nl.yml +6 -55
- data/config/locales/no.yml +8 -42
- data/config/locales/pl.yml +6 -88
- data/config/locales/pt-BR.yml +6 -74
- data/config/locales/pt.yml +8 -54
- data/config/locales/ro-RO.yml +10 -54
- data/config/locales/ru.yml +0 -18
- data/config/locales/sk.yml +8 -50
- data/config/locales/sr-CS.yml +0 -14
- data/config/locales/sv.yml +128 -85
- data/config/locales/tr-TR.yml +8 -51
- data/config/locales/uk.yml +0 -18
- data/config/locales/zh-CN.yml +8 -51
- data/config/locales/zh-TW.yml +8 -84
- data/db/migrate/20171220084719_add_published_at_to_proposals.rb +1 -1
- data/db/migrate/20181016132225_add_organization_as_author.rb +1 -1
- data/db/migrate/20200120215928_move_proposal_endorsements_to_core_endorsements.rb +1 -1
- data/db/migrate/20200827154156_add_commentable_counter_cache_to_proposals.rb +3 -3
- data/db/migrate/20210310102839_add_followable_counter_cache_to_proposals.rb +1 -1
- data/db/migrate/20240110203504_create_default_proposal_states.rb +1 -1
- data/db/migrate/20240404202756_add_valuation_assignments_count_to_decidim_proposals_proposals.rb +1 -1
- data/db/migrate/20240617091140_add_email_on_assigned_proposals_to_users.rb +7 -0
- data/db/migrate/20240617170052_add_parent_relation_to_decidim_proposal_notes.rb +7 -0
- data/db/migrate/20240828103755_add_deleted_at_to_decidim_proposals_proposals.rb +8 -0
- data/decidim-proposals.gemspec +1 -1
- data/lib/decidim/api/functions/proposal_finder_helper.rb +12 -0
- data/lib/decidim/api/functions/proposal_list_helper.rb +12 -0
- data/lib/decidim/api/proposal_type.rb +17 -25
- data/lib/decidim/api/proposals_type.rb +4 -19
- data/lib/decidim/proposals/admin_engine.rb +12 -3
- data/lib/decidim/proposals/admin_filter.rb +3 -6
- data/lib/decidim/proposals/component.rb +4 -5
- data/lib/decidim/proposals/download_your_data_proposal_serializer.rb +15 -0
- data/lib/decidim/proposals/engine.rb +5 -0
- data/lib/decidim/proposals/import/proposal_creator.rb +4 -4
- data/lib/decidim/proposals/proposal_serializer.rb +12 -29
- data/lib/decidim/proposals/seeds.rb +21 -17
- data/lib/decidim/proposals/test/factories.rb +2 -1
- data/lib/decidim/proposals/version.rb +1 -1
- data/lib/decidim/proposals.rb +4 -0
- metadata +65 -29
- data/app/commands/decidim/proposals/admin/update_proposal_category.rb +0 -70
- data/app/commands/decidim/proposals/admin/update_proposal_scope.rb +0 -75
- data/app/events/decidim/proposals/admin/update_proposal_category_event.rb +0 -11
- data/app/events/decidim/proposals/admin/update_proposal_scope_event.rb +0 -11
- data/app/views/decidim/proposals/admin/proposals/bulk_actions/_recategorize.html.erb +0 -15
- data/app/views/decidim/proposals/admin/proposals/bulk_actions/_scope-change.html.erb +0 -21
- data/app/views/decidim/proposals/collaborative_drafts/_actions.html.erb +0 -7
@@ -7,6 +7,7 @@ module Decidim
|
|
7
7
|
include Decidim::TranslatableAttributes
|
8
8
|
include Decidim::AttachmentAttributes
|
9
9
|
include Decidim::HasUploadValidations
|
10
|
+
include Decidim::HasTaxonomyFormAttributes
|
10
11
|
|
11
12
|
mimic :proposal
|
12
13
|
|
@@ -17,29 +18,24 @@ module Decidim
|
|
17
18
|
attribute :address, String
|
18
19
|
attribute :latitude, Float
|
19
20
|
attribute :longitude, Float
|
20
|
-
attribute :category_id, Integer
|
21
|
-
attribute :scope_id, Integer
|
22
21
|
attribute :attachment, AttachmentForm
|
23
22
|
attribute :suggested_hashtags, Array[String]
|
24
23
|
|
25
24
|
attachments_attribute :documents
|
26
25
|
|
27
|
-
validates :title, :body, presence: true
|
26
|
+
validates :title, :body, presence: true
|
27
|
+
validates :title, :body, etiquette: true
|
28
28
|
validates :title, length: { in: 15..150 }
|
29
29
|
validates :body, proposal_length: {
|
30
30
|
minimum: 15,
|
31
31
|
maximum: ->(record) { record.component.settings.proposal_length }
|
32
32
|
}
|
33
33
|
validates :address, geocoding: true, if: ->(form) { form.has_address? && !form.geocoded? }
|
34
|
-
validates :category, presence: true, if: ->(form) { form.category_id.present? }
|
35
|
-
validates :scope, presence: true, if: ->(form) { form.scope_id.present? }
|
36
|
-
validates :scope_id, scope_belongs_to_component: true, if: ->(form) { form.scope_id.present? }
|
37
34
|
|
38
35
|
validate :body_is_not_bare_template
|
39
36
|
validate :notify_missing_attachment_if_errored
|
40
37
|
|
41
38
|
alias component current_component
|
42
|
-
delegate :categories, to: :current_component
|
43
39
|
|
44
40
|
def map_model(model)
|
45
41
|
self.title = translated_attribute(model.title)
|
@@ -50,34 +46,11 @@ module Decidim
|
|
50
46
|
self.body = presenter.editor_body(all_locales: body.is_a?(Hash))
|
51
47
|
|
52
48
|
self.user_group_id = model.user_groups.first&.id
|
53
|
-
self.category_id = model.categorization.decidim_category_id if model.categorization
|
54
|
-
|
55
|
-
# The scope attribute is with different key (decidim_scope_id), so it
|
56
|
-
# has to be manually mapped.
|
57
|
-
self.scope_id = model.scope.id if model.scope
|
58
|
-
|
59
49
|
self.documents = model.attachments
|
60
50
|
end
|
61
51
|
|
62
|
-
|
63
|
-
|
64
|
-
# Returns a Decidim::Category
|
65
|
-
def category
|
66
|
-
@category ||= categories.find_by(id: category_id)
|
67
|
-
end
|
68
|
-
|
69
|
-
# Finds the Scope from the given scope_id, uses participatory space scope if missing.
|
70
|
-
#
|
71
|
-
# Returns a Decidim::Scope
|
72
|
-
def scope
|
73
|
-
@scope ||= @attributes["scope_id"].value ? current_component.scopes.find_by(id: @attributes["scope_id"].value) : current_component.scope
|
74
|
-
end
|
75
|
-
|
76
|
-
# Scope identifier
|
77
|
-
#
|
78
|
-
# Returns the scope identifier related to the proposal
|
79
|
-
def scope_id
|
80
|
-
super || scope&.id
|
52
|
+
def participatory_space_manifest
|
53
|
+
@participatory_space_manifest ||= current_component.participatory_space.manifest.name
|
81
54
|
end
|
82
55
|
|
83
56
|
def geocoding_enabled?
|
@@ -8,6 +8,31 @@ module Decidim
|
|
8
8
|
Decidim::Proposals::Proposal.find(id)
|
9
9
|
end
|
10
10
|
|
11
|
+
# Public: Generates a select field with the templates of the given component.
|
12
|
+
#
|
13
|
+
# component - A component instance.
|
14
|
+
# prompt - An i18n string to show as prompt
|
15
|
+
#
|
16
|
+
# Returns a String.
|
17
|
+
def bulk_templates_select(component, prompt, id: nil)
|
18
|
+
options_for_select = find_templates_for_select(component)
|
19
|
+
select(:template, :template_id, options_for_select, prompt:, id:)
|
20
|
+
end
|
21
|
+
|
22
|
+
def find_templates_for_select(component)
|
23
|
+
return [] unless Decidim.module_installed? :templates
|
24
|
+
return @templates_for_select if @templates_for_select
|
25
|
+
|
26
|
+
templates = Decidim::Templates::Template.where(
|
27
|
+
target: :proposal_answer,
|
28
|
+
templatable: component
|
29
|
+
).order(:templatable_id)
|
30
|
+
|
31
|
+
@templates_for_select = templates.map do |template|
|
32
|
+
[translated_attribute(template.name), template.id]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
11
36
|
# find the valuators for the current space.
|
12
37
|
def find_valuators_for_select(participatory_space, current_user)
|
13
38
|
valuator_roles = participatory_space.user_roles(:valuator).order_by_name
|
@@ -30,6 +30,7 @@ module Decidim
|
|
30
30
|
|
31
31
|
def proposal_state_css_style(proposal)
|
32
32
|
return "" if proposal.emendation?
|
33
|
+
return "" if proposal.withdrawn?
|
33
34
|
|
34
35
|
proposal.proposal_state&.css_style
|
35
36
|
end
|
@@ -144,8 +145,16 @@ module Decidim
|
|
144
145
|
).count
|
145
146
|
end
|
146
147
|
|
148
|
+
def layout_item_classes
|
149
|
+
if show_voting_rules?
|
150
|
+
"layout-item lg:pt-4"
|
151
|
+
else
|
152
|
+
"layout-item"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
147
156
|
def show_voting_rules?
|
148
|
-
return false if !votes_enabled? ||
|
157
|
+
return false if !votes_enabled? || votes_blocked?
|
149
158
|
|
150
159
|
return true if vote_limit_enabled?
|
151
160
|
return true if threshold_per_proposal_enabled?
|
@@ -206,41 +215,38 @@ module Decidim
|
|
206
215
|
end
|
207
216
|
|
208
217
|
# rubocop:disable Metrics/CyclomaticComplexity
|
209
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
210
218
|
def filter_sections
|
211
219
|
@filter_sections ||= begin
|
212
220
|
items = []
|
213
221
|
if component_settings.proposal_answering_enabled && current_settings.proposal_answering_enabled
|
214
|
-
items.append(method: :with_any_state, collection: filter_proposals_state_values,
|
222
|
+
items.append(method: :with_any_state, collection: filter_proposals_state_values, label: t("decidim.proposals.proposals.filters.state"), id: "state")
|
215
223
|
end
|
216
|
-
|
217
|
-
items.append(method:
|
218
|
-
|
219
|
-
|
220
|
-
|
224
|
+
current_component.available_taxonomy_filters.each do |taxonomy_filter|
|
225
|
+
items.append(method: "with_any_taxonomies[#{taxonomy_filter.root_taxonomy_id}]",
|
226
|
+
collection: filter_taxonomy_values_for(taxonomy_filter),
|
227
|
+
label: decidim_sanitize_translated(taxonomy_filter.name),
|
228
|
+
id: "taxonomy-#{taxonomy_filter.root_taxonomy_id}")
|
221
229
|
end
|
222
230
|
if component_settings.official_proposals_enabled
|
223
|
-
items.append(method: :with_any_origin, collection: filter_origin_values,
|
231
|
+
items.append(method: :with_any_origin, collection: filter_origin_values, label: t("decidim.proposals.proposals.filters.origin"), id: "origin")
|
224
232
|
end
|
225
233
|
if current_user
|
226
|
-
items.append(method: :activity, collection: activity_filter_values,
|
234
|
+
items.append(method: :activity, collection: activity_filter_values, label: t("decidim.proposals.proposals.filters.activity"), id: "activity", type: :radio_buttons)
|
227
235
|
end
|
228
236
|
if @proposals.only_emendations.any?
|
229
|
-
items.append(method: :type, collection: filter_type_values,
|
237
|
+
items.append(method: :type, collection: filter_type_values, label: t("decidim.proposals.proposals.filters.amendment_type"), id: "amendment_type", type: :radio_buttons)
|
230
238
|
end
|
231
239
|
if linked_classes_for(Decidim::Proposals::Proposal).any?
|
232
240
|
items.append(
|
233
241
|
method: :related_to,
|
234
242
|
collection: linked_classes_filter_values_for(Decidim::Proposals::Proposal),
|
235
|
-
|
243
|
+
label: t("decidim.proposals.proposals.filters.related_to"),
|
236
244
|
id: "related_to",
|
237
245
|
type: :radio_buttons
|
238
246
|
)
|
239
247
|
end
|
240
248
|
end
|
241
|
-
# rubocop:enable Metrics/PerceivedComplexity
|
242
249
|
# rubocop:enable Metrics/CyclomaticComplexity
|
243
|
-
|
244
250
|
items.reject { |item| item[:collection].blank? }
|
245
251
|
end
|
246
252
|
|
@@ -248,6 +254,10 @@ module Decidim
|
|
248
254
|
i18n_key = controller_name == "collaborative_drafts" ? "decidim.proposals.collaborative_drafts.name" : "decidim.components.proposals.name"
|
249
255
|
(defined?(current_component) && translated_attribute(current_component&.name).presence) || t(i18n_key)
|
250
256
|
end
|
257
|
+
|
258
|
+
def templates_available?
|
259
|
+
Decidim.module_installed?(:templates) && defined?(Decidim::Templates::Template) && Decidim::Templates::Template.exists?(templatable: current_component)
|
260
|
+
end
|
251
261
|
end
|
252
262
|
end
|
253
263
|
end
|
@@ -43,20 +43,20 @@ module Decidim
|
|
43
43
|
items = [{
|
44
44
|
method: :with_any_state,
|
45
45
|
collection: filter_collaborative_drafts_state_values,
|
46
|
-
|
46
|
+
label: t("decidim.proposals.collaborative_drafts.filters.state"),
|
47
47
|
id: "state"
|
48
48
|
}]
|
49
|
-
|
50
|
-
items.append(method:
|
51
|
-
|
52
|
-
|
53
|
-
|
49
|
+
current_component.available_taxonomy_filters.each do |taxonomy_filter|
|
50
|
+
items.append(method: "with_any_taxonomies[#{taxonomy_filter.root_taxonomy_id}]",
|
51
|
+
collection: filter_taxonomy_values_for(taxonomy_filter),
|
52
|
+
label: decidim_sanitize_translated(taxonomy_filter.name),
|
53
|
+
id: "taxonomy-#{taxonomy_filter.root_taxonomy_id}")
|
54
54
|
end
|
55
55
|
if linked_classes_for(Decidim::Proposals::CollaborativeDraft).any?
|
56
56
|
items.append(
|
57
57
|
method: :related_to,
|
58
58
|
collection: linked_classes_filter_values_for(Decidim::Proposals::CollaborativeDraft),
|
59
|
-
|
59
|
+
label: t("decidim.proposals.collaborative_drafts.filters.related_to"),
|
60
60
|
id: "related_to",
|
61
61
|
type: :radio_buttons
|
62
62
|
)
|
@@ -5,24 +5,6 @@ module Decidim
|
|
5
5
|
# This helper include some methods for rendering proposals dynamic maps.
|
6
6
|
module MapHelper
|
7
7
|
include Decidim::ApplicationHelper
|
8
|
-
# Serialize a collection of geocoded proposals to be used by the dynamic map component
|
9
|
-
#
|
10
|
-
# geocoded_proposals - A collection of geocoded proposals
|
11
|
-
def proposals_data_for_map(geocoded_proposals)
|
12
|
-
geocoded_proposals.select(&:geocoded_and_valid?).map do |proposal|
|
13
|
-
proposal_data_for_map(proposal)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def proposal_data_for_map(proposal)
|
18
|
-
proposal
|
19
|
-
.slice(:latitude, :longitude, :address)
|
20
|
-
.merge(
|
21
|
-
title: decidim_html_escape(present(proposal).title),
|
22
|
-
link: proposal_path(proposal),
|
23
|
-
items: cell("decidim/proposals/proposal_metadata", proposal).send(:proposal_items_for_map).to_json
|
24
|
-
)
|
25
|
-
end
|
26
8
|
|
27
9
|
def proposal_preview_data_for_map(proposal)
|
28
10
|
{
|
@@ -47,14 +47,14 @@ module Decidim
|
|
47
47
|
#
|
48
48
|
# Returns true if enabled, false otherwise.
|
49
49
|
def votes_enabled?
|
50
|
-
current_settings.votes_enabled
|
50
|
+
current_settings.respond_to?(:votes_enabled) && current_settings.votes_enabled
|
51
51
|
end
|
52
52
|
|
53
53
|
# Public: Checks if voting is blocked in this step.
|
54
54
|
#
|
55
55
|
# Returns true if blocked, false otherwise.
|
56
56
|
def votes_blocked?
|
57
|
-
current_settings.votes_blocked
|
57
|
+
current_settings.respond_to?(:votes_blocked) && current_settings.votes_blocked
|
58
58
|
end
|
59
59
|
|
60
60
|
# Public: Checks if the current user is allowed to vote in this step.
|
@@ -76,6 +76,19 @@ module Decidim
|
|
76
76
|
votes_count = ProposalVote.where(author: user, proposal: proposals).size
|
77
77
|
component_settings.vote_limit - votes_count
|
78
78
|
end
|
79
|
+
|
80
|
+
# Return the remaining minimum votes for a user if the current component has a vote limit
|
81
|
+
#
|
82
|
+
# user - A User object
|
83
|
+
#
|
84
|
+
# Returns a number with the remaining minimum votes for that user
|
85
|
+
def remaining_minimum_votes_count_for(user)
|
86
|
+
return 0 unless vote_limit_enabled?
|
87
|
+
|
88
|
+
votes_count = Decidim::Proposals::ProposalVote.joins(:proposal).where(decidim_proposals_proposals: { decidim_component_id: current_component.id }).where(author: user).count
|
89
|
+
|
90
|
+
component_settings.minimum_votes_per_user - votes_count
|
91
|
+
end
|
79
92
|
end
|
80
93
|
end
|
81
94
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Proposals
|
5
|
+
module Admin
|
6
|
+
class ProposalAnswerJob < ApplicationJob
|
7
|
+
queue_as :default
|
8
|
+
|
9
|
+
def perform(proposal, attributes, context)
|
10
|
+
answer_form = ProposalAnswerForm.from_params(attributes).with_context(**context)
|
11
|
+
|
12
|
+
Admin::AnswerProposal.call(answer_form, proposal) do
|
13
|
+
on(:ok) { Rails.logger.info "Proposal #{proposal.id} answered successfully." }
|
14
|
+
on(:invalid) { Rails.logger.error "Proposal ID #{proposal.id} could not be updated. Errors: #{answer_form.errors.full_messages}" }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -5,6 +5,7 @@ module Decidim
|
|
5
5
|
class CollaborativeDraft < Proposals::ApplicationRecord
|
6
6
|
include Decidim::Resourceable
|
7
7
|
include Decidim::Coauthorable
|
8
|
+
include Decidim::Taxonomizable
|
8
9
|
include Decidim::HasComponent
|
9
10
|
include Decidim::ScopableResource
|
10
11
|
include Decidim::HasReference
|
@@ -64,7 +65,15 @@ module Decidim
|
|
64
65
|
ransacker_text_multi :search_text, [:title, :body]
|
65
66
|
|
66
67
|
def self.ransackable_scopes(_auth_object = nil)
|
67
|
-
[:with_any_state, :related_to, :
|
68
|
+
[:with_any_state, :related_to, :with_any_taxonomies]
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.ransackable_attributes(_auth_object = nil)
|
72
|
+
%w(id_string search_text title body)
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.ransackable_associations(_auth_object = nil)
|
76
|
+
%w(taxonomies)
|
68
77
|
end
|
69
78
|
end
|
70
79
|
end
|
@@ -7,6 +7,7 @@ module Decidim
|
|
7
7
|
include Decidim::Resourceable
|
8
8
|
include Decidim::Coauthorable
|
9
9
|
include Decidim::HasComponent
|
10
|
+
include Decidim::Taxonomizable
|
10
11
|
include Decidim::ScopableResource
|
11
12
|
include Decidim::HasReference
|
12
13
|
include Decidim::HasCategory
|
@@ -28,6 +29,7 @@ module Decidim
|
|
28
29
|
include Decidim::TranslatableResource
|
29
30
|
include Decidim::TranslatableAttributes
|
30
31
|
include Decidim::FilterableResource
|
32
|
+
include Decidim::SoftDeletable
|
31
33
|
|
32
34
|
def assign_state(token)
|
33
35
|
proposal_state = Decidim::Proposals::ProposalState.where(component:, token:).first
|
@@ -403,13 +405,21 @@ module Decidim
|
|
403
405
|
end
|
404
406
|
|
405
407
|
def self.ransackable_scopes(_auth_object = nil)
|
406
|
-
[:with_any_origin, :with_any_state, :state_eq, :voted_by, :coauthored_by, :related_to, :
|
408
|
+
[:with_any_origin, :with_any_state, :state_eq, :voted_by, :coauthored_by, :related_to, :with_any_taxonomies, :valuator_role_ids_has]
|
407
409
|
end
|
408
410
|
|
409
411
|
# Create i18n ransackers for :title and :body.
|
410
412
|
# Create the :search_text ransacker alias for searching from both of these.
|
411
413
|
ransacker_i18n_multi :search_text, [:title, :body]
|
412
414
|
|
415
|
+
def self.ransackable_attributes(_auth_object = nil)
|
416
|
+
%w(id_string search_text title body is_emendation comments_count proposal_votes_count published_at proposal_notes_count)
|
417
|
+
end
|
418
|
+
|
419
|
+
def self.ransackable_associations(_auth_object = nil)
|
420
|
+
%w(taxonomies proposal_state)
|
421
|
+
end
|
422
|
+
|
413
423
|
ransacker :state_published do
|
414
424
|
Arel.sql("CASE
|
415
425
|
WHEN EXISTS (
|
@@ -455,7 +465,7 @@ module Decidim
|
|
455
465
|
end
|
456
466
|
|
457
467
|
def self.export_serializer
|
458
|
-
Decidim::Proposals::
|
468
|
+
Decidim::Proposals::DownloadYourDataProposalSerializer
|
459
469
|
end
|
460
470
|
|
461
471
|
def self.download_your_data_images(user)
|
@@ -472,8 +482,18 @@ module Decidim
|
|
472
482
|
return true if draft?
|
473
483
|
return true if component.settings.proposal_edit_time == "infinite"
|
474
484
|
|
475
|
-
|
476
|
-
|
485
|
+
time_value, time_unit = component.settings.edit_time
|
486
|
+
|
487
|
+
limit_time = case time_unit
|
488
|
+
when "minutes"
|
489
|
+
updated_at + time_value.minutes
|
490
|
+
when "hours"
|
491
|
+
updated_at + time_value.hours
|
492
|
+
else
|
493
|
+
updated_at + time_value.days
|
494
|
+
end
|
495
|
+
|
496
|
+
Time.current < limit_time
|
477
497
|
end
|
478
498
|
|
479
499
|
def process_amendment_state_change!
|
@@ -486,10 +506,51 @@ module Decidim
|
|
486
506
|
end
|
487
507
|
end
|
488
508
|
|
509
|
+
def user_has_actions?(user)
|
510
|
+
return false if authors.include?(user)
|
511
|
+
return false if user&.blocked?
|
512
|
+
return false if user&.deleted?
|
513
|
+
return false unless user&.confirmed?
|
514
|
+
|
515
|
+
true
|
516
|
+
end
|
517
|
+
|
518
|
+
def actions_for_comment(comment, current_user)
|
519
|
+
return if comment.commentable != self
|
520
|
+
return unless authors.include?(current_user)
|
521
|
+
return unless user_has_actions?(comment.author)
|
522
|
+
|
523
|
+
if coauthor_invitations_for(comment.author).any?
|
524
|
+
[
|
525
|
+
{
|
526
|
+
label: I18n.t("decidim.proposals.actions.cancel_coauthor_invitation"),
|
527
|
+
url: EngineRouter.main_proxy(component).cancel_proposal_invite_coauthors_path(proposal_id: id, id: comment.author.id),
|
528
|
+
icon: "user-forbid-line",
|
529
|
+
method: :delete,
|
530
|
+
data: { confirm: I18n.t("decidim.proposals.actions.cancel_coauthor_invitation_confirm") }
|
531
|
+
}
|
532
|
+
]
|
533
|
+
else
|
534
|
+
[
|
535
|
+
{
|
536
|
+
label: I18n.t("decidim.proposals.actions.mark_as_coauthor"),
|
537
|
+
url: EngineRouter.main_proxy(component).proposal_invite_coauthors_path(proposal_id: id, id: comment.author.id),
|
538
|
+
icon: "user-add-line",
|
539
|
+
method: :post,
|
540
|
+
data: { confirm: I18n.t("decidim.proposals.actions.mark_as_coauthor_confirm") }
|
541
|
+
}
|
542
|
+
]
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
def coauthor_invitations_for(user)
|
547
|
+
Decidim::Notification.where(event_class: "Decidim::Proposals::CoauthorInvitedEvent", resource: self, user:)
|
548
|
+
end
|
549
|
+
|
489
550
|
private
|
490
551
|
|
491
552
|
def copied_from_other_component?
|
492
|
-
linked_resources(:proposals,
|
553
|
+
linked_resources(:proposals, %w(splitted_from_component merged_from_component copied_from_component)).any?
|
493
554
|
end
|
494
555
|
end
|
495
556
|
end
|
@@ -9,12 +9,23 @@ module Decidim
|
|
9
9
|
|
10
10
|
belongs_to :proposal, foreign_key: "decidim_proposal_id", class_name: "Decidim::Proposals::Proposal", counter_cache: true
|
11
11
|
belongs_to :author, foreign_key: "decidim_author_id", class_name: "Decidim::User"
|
12
|
+
has_many :replies, foreign_key: "parent_id", class_name: "Decidim::Proposals::ProposalNote", inverse_of: :parent, dependent: :destroy
|
13
|
+
belongs_to :parent, class_name: "Decidim::Proposals::ProposalNote", inverse_of: :replies, optional: true
|
12
14
|
|
15
|
+
scope :not_reply, -> { where(parent_id: nil) }
|
13
16
|
default_scope { order(created_at: :asc) }
|
14
17
|
|
15
18
|
def self.log_presenter_class_for(_log)
|
16
19
|
Decidim::Proposals::AdminLog::ProposalNotePresenter
|
17
20
|
end
|
21
|
+
|
22
|
+
def reply?
|
23
|
+
parent.present?
|
24
|
+
end
|
25
|
+
|
26
|
+
def formatted_body
|
27
|
+
Decidim::ContentProcessor.render_without_format(body)
|
28
|
+
end
|
18
29
|
end
|
19
30
|
end
|
20
31
|
end
|
@@ -38,7 +38,7 @@ module Decidim
|
|
38
38
|
protected
|
39
39
|
|
40
40
|
def generate_token
|
41
|
-
self.token = ensure_unique_token(translated_attribute(title).parameterize(separator: "_"))
|
41
|
+
self.token = ensure_unique_token(token.presence || translated_attribute(title).parameterize(separator: "_"))
|
42
42
|
end
|
43
43
|
|
44
44
|
def ensure_unique_token(token)
|
@@ -13,13 +13,20 @@ $(() => {
|
|
13
13
|
return $(".table-list [data-published-state=false] .js-check-all-proposal:checked").length
|
14
14
|
}
|
15
15
|
|
16
|
+
const selectedProposalsAllowsAnswerCount = function() {
|
17
|
+
return $(".table-list [data-allow-answer=true] .js-check-all-proposal:checked").length
|
18
|
+
}
|
19
|
+
|
16
20
|
const selectedProposalsCountUpdate = function() {
|
17
21
|
const selectedProposals = selectedProposalsCount();
|
18
22
|
const selectedProposalsNotPublishedAnswer = selectedProposalsNotPublishedAnswerCount();
|
23
|
+
const allowAnswerProposals = selectedProposalsAllowsAnswerCount();
|
24
|
+
|
19
25
|
if (selectedProposals === 0) {
|
20
26
|
$("#js-selected-proposals-count").text("")
|
21
27
|
$("#js-assign-proposals-to-valuator-actions").addClass("hide");
|
22
28
|
$("#js-unassign-proposals-from-valuator-actions").addClass("hide");
|
29
|
+
$("#js-taxonomy-change-proposals-actions").addClass("hide");
|
23
30
|
} else {
|
24
31
|
$("#js-selected-proposals-count").text(selectedProposals);
|
25
32
|
}
|
@@ -36,6 +43,13 @@ $(() => {
|
|
36
43
|
} else {
|
37
44
|
$('button[data-action="publish-answers"]').parent().hide();
|
38
45
|
}
|
46
|
+
|
47
|
+
if (allowAnswerProposals > 0) {
|
48
|
+
$('button[data-action="apply-answer-template"]').parent().show();
|
49
|
+
$("#js-form-apply-answer-template-number").text(allowAnswerProposals);
|
50
|
+
} else {
|
51
|
+
$('button[data-action="apply-answer-template"]').parent().hide();
|
52
|
+
}
|
39
53
|
}
|
40
54
|
|
41
55
|
const showBulkActionsButton = function() {
|
@@ -91,7 +105,8 @@ $(() => {
|
|
91
105
|
let action = $(e.target).data("action");
|
92
106
|
const panelActions = [
|
93
107
|
"assign-proposals-to-valuator",
|
94
|
-
"unassign-proposals-from-valuator"
|
108
|
+
"unassign-proposals-from-valuator",
|
109
|
+
"taxonomy-change-proposals"
|
95
110
|
];
|
96
111
|
|
97
112
|
if (!action) {
|
@@ -0,0 +1,73 @@
|
|
1
|
+
const allowExitFrom = (el) => {
|
2
|
+
if (el.id === "exit-proposal-notification-link" || el.classList.contains("no-modal")) {
|
3
|
+
return true;
|
4
|
+
}
|
5
|
+
|
6
|
+
return false;
|
7
|
+
};
|
8
|
+
|
9
|
+
document.addEventListener("DOMContentLoaded", () => {
|
10
|
+
const exitNotification = document.getElementById("exit-proposal-notification");
|
11
|
+
const exitLink = document.getElementById("exit-proposal-notification-link");
|
12
|
+
if (!exitLink) {
|
13
|
+
return;
|
14
|
+
}
|
15
|
+
const defaultExitUrl = exitLink.href;
|
16
|
+
const defaultExitLinkText = exitLink.textContent;
|
17
|
+
const signOutPath = window.Decidim.config.get("sign_out_path");
|
18
|
+
let exitLinkText = defaultExitLinkText;
|
19
|
+
|
20
|
+
if (!exitNotification) {
|
21
|
+
// Do not apply when not inside the voting pipeline
|
22
|
+
return;
|
23
|
+
}
|
24
|
+
|
25
|
+
const openExitNotification = (url, method = null) => {
|
26
|
+
if (method && method !== "get") {
|
27
|
+
exitLink.setAttribute("data-method", method);
|
28
|
+
} else {
|
29
|
+
exitLink.removeAttribute("data-method");
|
30
|
+
}
|
31
|
+
|
32
|
+
exitLink.setAttribute("href", url);
|
33
|
+
exitLink.textContent = exitLinkText;
|
34
|
+
window.Decidim.currentDialogs["exit-proposal-notification"].open();
|
35
|
+
};
|
36
|
+
|
37
|
+
const handleClicks = (link) => {
|
38
|
+
link.addEventListener("click", (event) => {
|
39
|
+
exitLinkText = defaultExitLinkText;
|
40
|
+
|
41
|
+
if (
|
42
|
+
!allowExitFrom(link) &&
|
43
|
+
((window.Decidim.currentDialogs["exit-proposal-notification"].dialog.querySelector("[data-dialog-container]")).dataset.minimumVotesReached !== "true") &&
|
44
|
+
((window.Decidim.currentDialogs["exit-proposal-notification"].dialog.querySelector("[data-dialog-container]")).dataset.minimumVotesCount > 0)
|
45
|
+
) {
|
46
|
+
event.preventDefault();
|
47
|
+
openExitNotification(link.getAttribute("href"), link.dataset.method);
|
48
|
+
}
|
49
|
+
});
|
50
|
+
};
|
51
|
+
|
52
|
+
document.querySelectorAll("a").forEach(handleClicks);
|
53
|
+
// Custom handling for the header sign-out link
|
54
|
+
const signOutLink = document.querySelector(`[href='${signOutPath}']`);
|
55
|
+
if (signOutLink) {
|
56
|
+
signOutLink.addEventListener("click", (event) => {
|
57
|
+
event.preventDefault();
|
58
|
+
event.stopPropagation();
|
59
|
+
|
60
|
+
exitLinkText = signOutLink.textContent;
|
61
|
+
openExitNotification(signOutLink.getAttribute("href"), signOutLink.dataset.method);
|
62
|
+
});
|
63
|
+
}
|
64
|
+
|
65
|
+
// Custom handling for links that open the exit notification dialog
|
66
|
+
const dialogOpenLinks = document.querySelectorAll("a[data-dialog-open='exit-proposal-notification']");
|
67
|
+
dialogOpenLinks.forEach((link) => {
|
68
|
+
link.addEventListener("click", () => {
|
69
|
+
exitLinkText = defaultExitLinkText;
|
70
|
+
openExitNotification(defaultExitUrl);
|
71
|
+
});
|
72
|
+
});
|
73
|
+
});
|