decidim-debates 0.19.1 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/decidim/gamification/badges/commented_debates.svg +1 -78
  3. data/app/cells/decidim/debates/debate_activity_cell.rb +4 -0
  4. data/app/cells/decidim/debates/debate_m/data.erb +10 -8
  5. data/app/cells/decidim/debates/debate_m/footer.erb +6 -1
  6. data/app/cells/decidim/debates/debate_m/multiple_dates.erb +1 -1
  7. data/app/cells/decidim/debates/debate_m_cell.rb +27 -1
  8. data/app/commands/decidim/debates/admin/close_debate.rb +54 -0
  9. data/app/commands/decidim/debates/admin/create_debate.rb +4 -2
  10. data/app/commands/decidim/debates/admin/update_debate.rb +5 -2
  11. data/app/commands/decidim/debates/close_debate.rb +58 -0
  12. data/app/commands/decidim/debates/create_debate.rb +17 -12
  13. data/app/commands/decidim/debates/update_debate.rb +56 -0
  14. data/app/controllers/decidim/debates/admin/debate_closes_controller.rb +43 -0
  15. data/app/controllers/decidim/debates/admin/debates_controller.rb +4 -2
  16. data/app/controllers/decidim/debates/debates_controller.rb +70 -9
  17. data/app/controllers/decidim/debates/orderable.rb +41 -0
  18. data/app/controllers/decidim/debates/versions_controller.rb +16 -0
  19. data/app/controllers/decidim/debates/widgets_controller.rb +19 -0
  20. data/app/events/decidim/debates/close_debate_event.rb +15 -0
  21. data/app/events/decidim/debates/create_debate_event.rb +0 -2
  22. data/app/forms/decidim/debates/admin/close_debate_form.rb +34 -0
  23. data/app/forms/decidim/debates/admin/debate_form.rb +5 -1
  24. data/app/forms/decidim/debates/close_debate_form.rb +39 -0
  25. data/app/forms/decidim/debates/debate_form.rb +27 -1
  26. data/app/helpers/decidim/debates/admin/application_helper.rb +13 -0
  27. data/app/helpers/decidim/debates/application_helper.rb +55 -0
  28. data/app/models/decidim/debates/debate.rb +70 -8
  29. data/app/permissions/decidim/debates/admin/permissions.rb +3 -1
  30. data/app/permissions/decidim/debates/permissions.rb +28 -0
  31. data/app/presenters/decidim/debates/admin_log/debate_presenter.rb +6 -2
  32. data/app/presenters/decidim/debates/admin_log/value_types/debate_title_description_presenter.rb +20 -0
  33. data/app/presenters/decidim/debates/debate_presenter.rb +64 -3
  34. data/app/presenters/decidim/debates/log/resource_presenter.rb +18 -0
  35. data/app/presenters/decidim/debates/official_author_presenter.rb +1 -29
  36. data/app/queries/decidim/debates/metrics/debates_metric_manage.rb +2 -8
  37. data/app/services/decidim/debates/debate_search.rb +13 -23
  38. data/app/services/decidim/debates/diff_renderer.rb +27 -0
  39. data/app/types/decidim/debates/debate_type.rb +28 -0
  40. data/app/types/decidim/debates/debates_type.rb +32 -0
  41. data/app/views/decidim/debates/admin/debate_closes/edit.html.erb +17 -0
  42. data/app/views/decidim/debates/admin/debates/_form.html.erb +5 -5
  43. data/app/views/decidim/debates/admin/debates/index.html.erb +7 -1
  44. data/app/views/decidim/debates/debates/_close_debate_modal.html.erb +13 -0
  45. data/app/views/decidim/debates/debates/_debates.html.erb +7 -0
  46. data/app/views/decidim/debates/debates/_filters.html.erb +20 -6
  47. data/app/views/decidim/debates/debates/_form.html.erb +19 -0
  48. data/app/views/decidim/debates/debates/edit.html.erb +22 -0
  49. data/app/views/decidim/debates/debates/index.html.erb +5 -6
  50. data/app/views/decidim/debates/debates/new.html.erb +2 -23
  51. data/app/views/decidim/debates/debates/show.html.erb +86 -10
  52. data/app/views/decidim/debates/versions/index.html.erb +8 -0
  53. data/app/views/decidim/debates/versions/show.html.erb +10 -0
  54. data/config/locales/am-ET.yml +1 -0
  55. data/config/locales/ar.yml +2 -1
  56. data/config/locales/bg-BG.yml +7 -0
  57. data/config/locales/bg.yml +15 -0
  58. data/config/locales/ca.yml +73 -1
  59. data/config/locales/cs.yml +112 -40
  60. data/config/locales/da-DK.yml +1 -0
  61. data/config/locales/da.yml +1 -0
  62. data/config/locales/de.yml +51 -1
  63. data/config/locales/el-GR.yml +1 -0
  64. data/config/locales/el.yml +155 -0
  65. data/config/locales/en.yml +73 -1
  66. data/config/locales/eo.yml +1 -0
  67. data/config/locales/es-MX.yml +73 -1
  68. data/config/locales/es-PY.yml +73 -1
  69. data/config/locales/es.yml +73 -1
  70. data/config/locales/et-EE.yml +1 -0
  71. data/config/locales/et.yml +1 -0
  72. data/config/locales/eu.yml +2 -1
  73. data/config/locales/fi-plain.yml +73 -1
  74. data/config/locales/fi.yml +78 -6
  75. data/config/locales/fr-CA.yml +223 -0
  76. data/config/locales/fr.yml +73 -1
  77. data/config/locales/ga-IE.yml +1 -0
  78. data/config/locales/gl.yml +2 -1
  79. data/config/locales/hr-HR.yml +1 -0
  80. data/config/locales/hr.yml +1 -0
  81. data/config/locales/hu.yml +31 -6
  82. data/config/locales/id-ID.yml +2 -1
  83. data/config/locales/is-IS.yml +102 -0
  84. data/config/locales/is.yml +102 -0
  85. data/config/locales/it.yml +45 -10
  86. data/config/locales/ja-JP.yml +195 -0
  87. data/config/locales/ja.yml +221 -0
  88. data/config/locales/ko-KR.yml +1 -0
  89. data/config/locales/ko.yml +1 -0
  90. data/config/locales/lt-LT.yml +1 -0
  91. data/config/locales/lt.yml +1 -0
  92. data/config/locales/lv.yml +135 -0
  93. data/config/locales/mt-MT.yml +1 -0
  94. data/config/locales/mt.yml +1 -0
  95. data/config/locales/nl.yml +51 -3
  96. data/config/locales/no.yml +159 -9
  97. data/config/locales/om-ET.yml +1 -0
  98. data/config/locales/pl.yml +100 -45
  99. data/config/locales/pt-BR.yml +3 -4
  100. data/config/locales/pt.yml +56 -36
  101. data/config/locales/ro-RO.yml +173 -0
  102. data/config/locales/ru.yml +2 -1
  103. data/config/locales/sk-SK.yml +156 -0
  104. data/config/locales/sk.yml +157 -0
  105. data/config/locales/sl.yml +22 -0
  106. data/config/locales/so-SO.yml +1 -0
  107. data/config/locales/sr-CS.yml +6 -0
  108. data/config/locales/sv.yml +54 -1
  109. data/config/locales/ti-ER.yml +1 -0
  110. data/config/locales/tr-TR.yml +2 -1
  111. data/config/locales/uk.yml +2 -1
  112. data/config/locales/vi-VN.yml +1 -0
  113. data/config/locales/vi.yml +1 -0
  114. data/config/locales/zh-CN.yml +218 -0
  115. data/config/locales/zh-TW.yml +1 -0
  116. data/db/migrate/20200320105918_index_foreign_keys_in_decidim_debates_debates.rb +7 -0
  117. data/db/migrate/20200703134657_close_debates.rb +9 -0
  118. data/db/migrate/20200708072042_fix_debates_i18n_fields.rb +48 -0
  119. data/db/migrate/20200716143929_add_endorsable_to_debates.rb +8 -0
  120. data/db/migrate/20200827154116_add_commentable_counter_cache_to_debates.rb +14 -0
  121. data/db/migrate/20200902133452_add_cached_comment_metadata_to_debates.rb +23 -0
  122. data/lib/decidim/debates/admin_engine.rb +3 -1
  123. data/lib/decidim/debates/component.rb +51 -1
  124. data/lib/decidim/debates/engine.rb +7 -1
  125. data/lib/decidim/debates/test/factories.rb +62 -6
  126. data/lib/decidim/debates/version.rb +1 -1
  127. metadata +84 -13
@@ -5,6 +5,8 @@ module Decidim
5
5
  module Admin
6
6
  # This controller allows an admin to manage debates from a Participatory Space
7
7
  class DebatesController < Decidim::Debates::Admin::ApplicationController
8
+ helper Decidim::ApplicationHelper
9
+
8
10
  helper_method :debates
9
11
 
10
12
  def index
@@ -38,13 +40,13 @@ module Decidim
38
40
  def edit
39
41
  enforce_permission_to :update, :debate, debate: debate
40
42
 
41
- @form = form(DebateForm).from_model(debate)
43
+ @form = form(Decidim::Debates::Admin::DebateForm).from_model(debate)
42
44
  end
43
45
 
44
46
  def update
45
47
  enforce_permission_to :update, :debate, debate: debate
46
48
 
47
- @form = form(DebateForm).from_params(params, current_component: current_component)
49
+ @form = form(Decidim::Debates::Admin::DebateForm).from_params(params, current_component: current_component)
48
50
 
49
51
  UpdateDebate.call(@form, debate) do
50
52
  on(:ok) do
@@ -6,11 +6,14 @@ module Decidim
6
6
  class DebatesController < Decidim::Debates::ApplicationController
7
7
  helper Decidim::ApplicationHelper
8
8
  helper Decidim::Messaging::ConversationHelper
9
+ helper Decidim::WidgetUrlsHelper
9
10
  include FormFactory
10
11
  include FilterResource
11
12
  include Paginable
13
+ include Flaggable
14
+ include Decidim::Debates::Orderable
12
15
 
13
- helper_method :debates, :debate, :paginated_debates, :report_form
16
+ helper_method :debates, :debate, :form_presenter, :paginated_debates, :report_form, :close_debate_form
14
17
 
15
18
  def new
16
19
  enforce_permission_to :create, :debate
@@ -21,7 +24,7 @@ module Decidim
21
24
  def create
22
25
  enforce_permission_to :create, :debate
23
26
 
24
- @form = form(DebateForm).from_params(params, current_component: current_component)
27
+ @form = form(DebateForm).from_params(params)
25
28
 
26
29
  CreateDebate.call(@form) do
27
30
  on(:ok) do |debate|
@@ -36,25 +39,80 @@ module Decidim
36
39
  end
37
40
  end
38
41
 
42
+ def show
43
+ raise ActionController::RoutingError, "Not Found" if debate.blank?
44
+ end
45
+
46
+ def edit
47
+ enforce_permission_to :edit, :debate, debate: debate
48
+
49
+ @form = form(DebateForm).from_model(debate)
50
+ end
51
+
52
+ def update
53
+ enforce_permission_to :edit, :debate, debate: debate
54
+
55
+ @form = form(DebateForm).from_params(params)
56
+ @form.debate = debate
57
+
58
+ UpdateDebate.call(@form) do
59
+ on(:ok) do |debate|
60
+ flash[:notice] = I18n.t("debates.update.success", scope: "decidim.debates")
61
+ redirect_to Decidim::ResourceLocatorPresenter.new(debate).path
62
+ end
63
+
64
+ on(:invalid) do
65
+ flash.now[:alert] = I18n.t("debates.update.invalid", scope: "decidim.debates")
66
+ render :edit
67
+ end
68
+ end
69
+ end
70
+
71
+ def close
72
+ enforce_permission_to :close, :debate, debate: debate
73
+
74
+ @form = form(CloseDebateForm).from_params(params)
75
+ @form.debate = debate
76
+
77
+ CloseDebate.call(@form) do
78
+ on(:ok) do |debate|
79
+ flash[:notice] = I18n.t("debates.close.success", scope: "decidim.debates")
80
+ redirect_back fallback_location: Decidim::ResourceLocatorPresenter.new(debate).path
81
+ end
82
+
83
+ on(:invalid) do
84
+ flash[:alert] = I18n.t("debates.close.invalid", scope: "decidim.debates")
85
+ redirect_back fallback_location: Decidim::ResourceLocatorPresenter.new(debate).path
86
+ end
87
+ end
88
+ end
89
+
39
90
  private
40
91
 
92
+ def form_presenter
93
+ @form_presenter ||= present(@form, presenter_class: Decidim::Debates::DebatePresenter)
94
+ end
95
+
41
96
  def paginated_debates
42
- @paginated_debates ||= paginate(debates)
43
- .includes(:category)
97
+ @paginated_debates ||= paginate(debates).includes(:category)
44
98
  end
45
99
 
46
100
  def debates
47
- @debates ||= search.results
101
+ @debates ||= reorder(search.results)
48
102
  end
49
103
 
50
104
  def debate
51
- @debate ||= debates.find(params[:id])
105
+ @debate ||= debates.find_by(id: params[:id])
52
106
  end
53
107
 
54
108
  def report_form
55
109
  @report_form ||= form(Decidim::ReportForm).from_params(reason: "spam")
56
110
  end
57
111
 
112
+ def close_debate_form
113
+ @close_debate_form ||= form(CloseDebateForm).from_model(debate)
114
+ end
115
+
58
116
  def search_klass
59
117
  DebateSearch
60
118
  end
@@ -69,9 +127,12 @@ module Decidim
69
127
  def default_filter_params
70
128
  {
71
129
  search_text: "",
72
- order_start_time: "asc",
73
- origin: "all",
74
- category_id: ""
130
+ origin: %w(official citizens user_group),
131
+ activity: "all",
132
+ category_id: default_filter_category_params,
133
+ scope_id: default_filter_scope_params,
134
+ status: "all",
135
+ state: %w(open closed)
75
136
  }
76
137
  end
77
138
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Decidim
6
+ module Debates
7
+ # Common logic to sorting resources
8
+ module Orderable
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ include Decidim::Orderable
13
+
14
+ private
15
+
16
+ def available_orders
17
+ @available_orders ||= %w(random recent commented updated)
18
+ end
19
+
20
+ def default_order
21
+ "updated"
22
+ end
23
+
24
+ def reorder(debates)
25
+ case order
26
+ when "recent"
27
+ debates.order("created_at DESC")
28
+ when "commented"
29
+ debates.order("comments_count DESC")
30
+ when "updated"
31
+ debates.order("updated_at DESC")
32
+ when "random"
33
+ debates.order_randomly(random_seed)
34
+ else
35
+ debates
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ # Exposes Debates versions so users can see how a Debate has been updated
6
+ # through time.
7
+ class VersionsController < Decidim::Proposals::ApplicationController
8
+ include Decidim::ApplicationHelper
9
+ include Decidim::ResourceVersionsConcern
10
+
11
+ def versioned_resource
12
+ @versioned_resource ||= present(Debate.where(component: current_component).find(params[:debate_id]))
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ class WidgetsController < Decidim::WidgetsController
6
+ helper Debates::ApplicationHelper
7
+
8
+ private
9
+
10
+ def model
11
+ @model ||= Debate.where(component: params[:component_id]).find(params[:debate_id])
12
+ end
13
+
14
+ def iframe_url
15
+ @iframe_url ||= debate_widget_url(model)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ # frozen-string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ class CloseDebateEvent < Decidim::Events::SimpleEvent
6
+ def resource_text
7
+ translated_attribute(resource.conclusions)
8
+ end
9
+
10
+ def event_has_roles?
11
+ true
12
+ end
13
+ end
14
+ end
15
+ end
@@ -14,8 +14,6 @@ module Decidim
14
14
 
15
15
  i18n_attributes :space_title, :space_path
16
16
 
17
- delegate :author, to: :resource
18
-
19
17
  def resource_text
20
18
  translated_attribute(resource.description)
21
19
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ module Admin
6
+ # This class holds a Form to close debates from Decidim's admin views.
7
+ class CloseDebateForm < Decidim::Form
8
+ include TranslatableAttributes
9
+
10
+ mimic :debate
11
+
12
+ translatable_attribute :conclusions, String do |translated_attribute, locale|
13
+ validates translated_attribute, presence: true, if: ->(record) { record.default_locale?(locale) }
14
+ validates translated_attribute, length: { minimum: 10, maximum: 10_000 }, if: ->(record) { record.default_locale?(locale) }
15
+ end
16
+
17
+ attribute :debate, Debate
18
+
19
+ validates :debate, presence: true
20
+ validate :user_can_close_debate
21
+
22
+ def closed_at
23
+ debate&.closed_at || Time.current
24
+ end
25
+
26
+ private
27
+
28
+ def user_can_close_debate
29
+ errors.add(:debate, :invalid) unless debate.official?
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -24,7 +24,11 @@ module Decidim
24
24
  validates :category, presence: true, if: ->(form) { form.decidim_category_id.present? }
25
25
 
26
26
  def map_model(model)
27
- self.decidim_category_id = model.category.try(:id)
27
+ self.decidim_category_id = model.categorization.decidim_category_id if model.categorization
28
+ presenter = DebatePresenter.new(model)
29
+
30
+ self.title = presenter.title(all_locales: title.is_a?(Hash))
31
+ self.description = presenter.description(all_locales: description.is_a?(Hash))
28
32
  end
29
33
 
30
34
  def category
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ # This class holds a Form to close debates from Decidim's public views.
6
+ class CloseDebateForm < Decidim::Form
7
+ mimic :debate
8
+
9
+ attribute :conclusions, String
10
+ attribute :debate, Debate
11
+
12
+ validates :debate, presence: true
13
+ validates :conclusions, presence: true, length: { minimum: 10, maximum: 10_000 }
14
+ validate :user_can_close_debate
15
+
16
+ def closed_at
17
+ debate&.closed_at || Time.current
18
+ end
19
+
20
+ def map_model(debate)
21
+ super
22
+ self.debate = debate
23
+
24
+ # Debates can be translated in different languages from the admin but
25
+ # the public form doesn't allow it. When a user closes a debate the
26
+ # user locale is taken as the text locale.
27
+ self.conclusions = debate.conclusions&.values&.first
28
+ end
29
+
30
+ private
31
+
32
+ def user_can_close_debate
33
+ return if !debate || !debate.respond_to?(:closeable_by?)
34
+
35
+ errors.add(:debate, :invalid) unless debate.closeable_by?(current_user)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -10,15 +10,41 @@ module Decidim
10
10
  attribute :description, String
11
11
  attribute :category_id, Integer
12
12
  attribute :user_group_id, Integer
13
+ attribute :debate, Debate
13
14
 
14
15
  validates :title, presence: true
15
16
  validates :description, presence: true
16
-
17
17
  validates :category, presence: true, if: ->(form) { form.category_id.present? }
18
+ validate :editable_by_user
19
+
20
+ def map_model(debate)
21
+ super
22
+ self.debate = debate
23
+
24
+ # Debates can be translated in different languages from the admin but
25
+ # the public form doesn't allow it. When a user creates a debate the
26
+ # user locale is taken as the text locale.
27
+ self.title = debate.title.values.first
28
+ self.description = debate.description.values.first
29
+ self.user_group_id = debate.decidim_user_group_id
30
+
31
+ if debate.category.present?
32
+ self.category_id = debate.category.id
33
+ @category = debate.category
34
+ end
35
+ end
18
36
 
19
37
  def category
20
38
  @category ||= current_component.categories.find_by(id: category_id)
21
39
  end
40
+
41
+ private
42
+
43
+ def editable_by_user
44
+ return unless debate.respond_to?(:editable_by?)
45
+
46
+ errors.add(:debate, :invalid) unless debate.editable_by?(current_user)
47
+ end
22
48
  end
23
49
  end
24
50
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ module Admin
6
+ # Custom helpers, scoped to the debates admin engine.
7
+ #
8
+ module ApplicationHelper
9
+ include Decidim::Admin::ResourceScopeHelper
10
+ end
11
+ end
12
+ end
13
+ end
@@ -7,6 +7,61 @@ module Decidim
7
7
  module ApplicationHelper
8
8
  include PaginateHelper
9
9
  include Decidim::Comments::CommentsHelper
10
+ include Decidim::RichTextEditorHelper
11
+ include Decidim::EndorsableHelper
12
+ include Decidim::FollowableHelper
13
+ include Decidim::CheckBoxesTreeHelper
14
+
15
+ # If the debate is official or the rich text editor is enabled on the
16
+ # frontend, the debate description is considered as safe content.
17
+ def safe_content?
18
+ debate&.official? || rich_text_editor_in_public_views?
19
+ end
20
+
21
+ # If the content is safe, HTML tags are sanitized, otherwise, they are stripped.
22
+ def render_debate_description(debate)
23
+ description = present(debate).description(strip_tags: !safe_content?, links: true)
24
+
25
+ safe_content? ? decidim_sanitize(description) : simple_format(description)
26
+ end
27
+
28
+ # Returns :text_area or :editor based on current_component settings.
29
+ def text_editor_for_debate_description(form)
30
+ text_editor_for(form, :description)
31
+ end
32
+
33
+ # Returns a TreeNode to be used in the list filters to filter debates by
34
+ # its origin.
35
+ def filter_origin_values
36
+ origin_values = []
37
+ origin_values << TreePoint.new("official", t("decidim.debates.debates.filters.official"))
38
+ origin_values << TreePoint.new("citizens", t("decidim.debates.debates.filters.citizens"))
39
+ origin_values << TreePoint.new("user_group", t("decidim.debates.debates.filters.user_groups")) if current_organization.user_groups_enabled?
40
+
41
+ TreeNode.new(TreePoint.new("", t("decidim.debates.debates.filters.all")), origin_values)
42
+ end
43
+
44
+ # Options to filter Debates by activity.
45
+ def activity_filter_values
46
+ base = [
47
+ ["all", t("decidim.debates.debates.filters.all")],
48
+ ["my_debates", t("decidim.debates.debates.filters.my_debates")]
49
+ ]
50
+ base += [["commented", t("decidim.debates.debates.filters.commented")]]
51
+ base
52
+ end
53
+
54
+ # Returns a TreeNode to be used in the list filters to filter debates by
55
+ # its state.
56
+ def filter_debates_state_values
57
+ Decidim::CheckBoxesTreeHelper::TreeNode.new(
58
+ Decidim::CheckBoxesTreeHelper::TreePoint.new("", t("decidim.debates.debates.filters.all")),
59
+ [
60
+ Decidim::CheckBoxesTreeHelper::TreePoint.new("open", t("decidim.debates.debates.filters.state_values.open")),
61
+ Decidim::CheckBoxesTreeHelper::TreePoint.new("closed", t("decidim.debates.debates.filters.state_values.closed"))
62
+ ]
63
+ )
64
+ end
10
65
  end
11
66
  end
12
67
  end