decidim-proposals 0.22.0 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/decidim/proposals/add_proposal.js.es6 +6 -0
  3. data/app/assets/javascripts/decidim/proposals/admin/proposals_form.js.es6 +3 -0
  4. data/app/cells/decidim/proposals/collaborative_draft_cell.rb +1 -1
  5. data/app/cells/decidim/proposals/participatory_text_proposal/buttons.erb +2 -2
  6. data/app/cells/decidim/proposals/participatory_text_proposal_cell.rb +1 -1
  7. data/app/cells/decidim/proposals/proposal_cell.rb +1 -1
  8. data/app/cells/decidim/proposals/proposal_m_cell.rb +2 -2
  9. data/app/commands/decidim/proposals/admin/create_proposal.rb +4 -2
  10. data/app/commands/decidim/proposals/admin/publish_participatory_text.rb +6 -1
  11. data/app/commands/decidim/proposals/admin/update_participatory_text.rb +10 -2
  12. data/app/commands/decidim/proposals/admin/update_proposal.rb +4 -2
  13. data/app/commands/decidim/proposals/create_proposal.rb +6 -2
  14. data/app/commands/decidim/proposals/publish_collaborative_draft.rb +2 -2
  15. data/app/commands/decidim/proposals/update_proposal.rb +25 -9
  16. data/app/controllers/decidim/proposals/admin/proposals_controller.rb +2 -2
  17. data/app/controllers/decidim/proposals/collaborative_drafts_controller.rb +1 -10
  18. data/app/controllers/decidim/proposals/proposals_controller.rb +3 -17
  19. data/app/controllers/decidim/proposals/{proposal_widgets_controller.rb → widgets_controller.rb} +2 -2
  20. data/app/forms/decidim/proposals/admin/import_participatory_text_form.rb +3 -1
  21. data/app/forms/decidim/proposals/admin/participatory_text_proposal_form.rb +8 -1
  22. data/app/forms/decidim/proposals/admin/proposal_base_form.rb +20 -13
  23. data/app/forms/decidim/proposals/admin/proposal_form.rb +9 -2
  24. data/app/forms/decidim/proposals/proposal_form.rb +21 -12
  25. data/app/helpers/decidim/proposals/admin/proposals_helper.rb +2 -0
  26. data/app/helpers/decidim/proposals/application_helper.rb +13 -8
  27. data/app/helpers/decidim/proposals/proposals_helper.rb +1 -1
  28. data/app/models/decidim/proposals/collaborative_draft.rb +2 -2
  29. data/app/models/decidim/proposals/participatory_text.rb +3 -0
  30. data/app/models/decidim/proposals/proposal.rb +13 -40
  31. data/app/presenters/decidim/proposals/admin_log/value_types/proposal_title_body_presenter.rb +6 -1
  32. data/app/presenters/decidim/proposals/official_author_presenter.rb +1 -29
  33. data/app/presenters/decidim/proposals/proposal_presenter.rb +43 -6
  34. data/app/queries/decidim/proposals/similar_proposals.rb +4 -4
  35. data/app/services/decidim/proposals/collaborative_draft_search.rb +6 -16
  36. data/app/services/decidim/proposals/diff_renderer.rb +15 -5
  37. data/app/services/decidim/proposals/proposal_builder.rb +8 -2
  38. data/app/services/decidim/proposals/proposal_search.rb +7 -58
  39. data/app/types/decidim/proposals/proposal_type.rb +2 -2
  40. data/app/views/decidim/proposals/admin/proposals/_form.html.erb +6 -6
  41. data/app/views/decidim/proposals/admin/proposals/_proposal-tr.html.erb +2 -11
  42. data/app/views/decidim/proposals/admin/proposals/index.html.erb +2 -2
  43. data/app/views/decidim/proposals/admin/proposals/show.html.erb +1 -1
  44. data/app/views/decidim/proposals/collaborative_drafts/_edit_form_fields.html.erb +5 -5
  45. data/app/views/decidim/proposals/collaborative_drafts/_filters.html.erb +1 -1
  46. data/app/views/decidim/proposals/collaborative_drafts/show.html.erb +1 -1
  47. data/app/views/decidim/proposals/proposals/_edit_form_fields.html.erb +46 -18
  48. data/app/views/decidim/proposals/proposals/_filters.html.erb +1 -1
  49. data/app/views/decidim/proposals/proposals/index.html.erb +0 -2
  50. data/app/views/decidim/proposals/proposals/show.html.erb +3 -3
  51. data/config/locales/am-ET.yml +1 -0
  52. data/config/locales/bg.yml +237 -0
  53. data/config/locales/ca.yml +12 -4
  54. data/config/locales/cs.yml +9 -1
  55. data/config/locales/da.yml +1 -0
  56. data/config/locales/de.yml +3 -0
  57. data/config/locales/en.yml +8 -0
  58. data/config/locales/eo.yml +1 -0
  59. data/config/locales/es-MX.yml +8 -0
  60. data/config/locales/es-PY.yml +8 -0
  61. data/config/locales/es.yml +22 -14
  62. data/config/locales/et.yml +1 -0
  63. data/config/locales/eu.yml +0 -15
  64. data/config/locales/fi-plain.yml +8 -0
  65. data/config/locales/fi.yml +9 -1
  66. data/config/locales/fr-CA.yml +30 -0
  67. data/config/locales/fr.yml +36 -6
  68. data/config/locales/gl.yml +0 -15
  69. data/config/locales/hr.yml +1 -0
  70. data/config/locales/id-ID.yml +0 -15
  71. data/config/locales/is.yml +274 -0
  72. data/config/locales/it.yml +6 -0
  73. data/config/locales/ja-JP.yml +5 -26
  74. data/config/locales/ja.yml +889 -0
  75. data/config/locales/ko-KR.yml +1 -0
  76. data/config/locales/ko.yml +1 -0
  77. data/config/locales/lt.yml +1 -0
  78. data/config/locales/{lv-LV.yml → lv.yml} +0 -0
  79. data/config/locales/mt.yml +1 -0
  80. data/config/locales/nl.yml +9 -1
  81. data/config/locales/om-ET.yml +1 -0
  82. data/config/locales/pl.yml +377 -374
  83. data/config/locales/pt-BR.yml +0 -15
  84. data/config/locales/pt.yml +1 -0
  85. data/config/locales/ro-RO.yml +1 -0
  86. data/config/locales/so-SO.yml +1 -0
  87. data/config/locales/sv.yml +49 -26
  88. data/config/locales/ti-ER.yml +1 -0
  89. data/config/locales/tr-TR.yml +0 -15
  90. data/config/locales/vi-VN.yml +1 -0
  91. data/config/locales/vi.yml +1 -0
  92. data/config/locales/zh-CN.yml +885 -0
  93. data/config/locales/zh-TW.yml +1 -0
  94. data/db/migrate/20200120215928_move_proposal_endorsements_to_core_endorsements.rb +2 -0
  95. data/db/migrate/20200120230130_drop_proposal_endorsements.rb +8 -0
  96. data/db/migrate/20200708091228_move_proposals_fields_to_i18n.rb +80 -0
  97. data/db/migrate/20200827154156_add_commentable_counter_cache_to_proposals.rb +12 -0
  98. data/db/migrate/20200915151348_fix_proposals_data_to_ensure_title_and_body_are_hashes.rb +37 -0
  99. data/db/migrate/20201002085508_fix_proposals_data.rb +37 -0
  100. data/lib/decidim/content_renderers/proposal_renderer.rb +3 -1
  101. data/lib/decidim/proposals/component.rb +9 -6
  102. data/lib/decidim/proposals/engine.rb +1 -1
  103. data/lib/decidim/proposals/markdown_to_proposals.rb +2 -2
  104. data/lib/decidim/proposals/proposal_serializer.rb +3 -3
  105. data/lib/decidim/proposals/test/capybara_proposals_picker.rb +2 -2
  106. data/lib/decidim/proposals/test/factories.rb +44 -18
  107. data/lib/decidim/proposals/version.rb +1 -1
  108. metadata +46 -22
@@ -4,8 +4,15 @@ module Decidim
4
4
  module Proposals
5
5
  module Admin
6
6
  # A form object to be used when admin users want to create a proposal.
7
- class ProposalForm < Admin::ProposalBaseForm
8
- validates :title, length: { in: 15..150 }
7
+ class ProposalForm < Decidim::Proposals::Admin::ProposalBaseForm
8
+ translatable_attribute :title, String do |field, _locale|
9
+ validates field, length: { in: 15..150 }, if: proc { |resource| resource.send(field).present? }
10
+ end
11
+ translatable_attribute :body, String
12
+
13
+ validates :title, :body, translatable_presence: true
14
+
15
+ validate :notify_missing_attachment_if_errored
9
16
  end
10
17
  end
11
18
  end
@@ -4,6 +4,8 @@ module Decidim
4
4
  module Proposals
5
5
  # A form object to be used when public users want to create a proposal.
6
6
  class ProposalForm < Decidim::Proposals::ProposalWizardCreateStepForm
7
+ include Decidim::TranslatableAttributes
8
+
7
9
  mimic :proposal
8
10
 
9
11
  attribute :address, String
@@ -14,14 +16,16 @@ module Decidim
14
16
  attribute :has_address, Boolean
15
17
  attribute :attachment, AttachmentForm
16
18
  attribute :suggested_hashtags, Array[String]
19
+ attribute :photos, Array[String]
20
+ attribute :add_photos, Array
21
+ attribute :documents, Array[String]
22
+ attribute :add_documents, Array
17
23
 
18
- validates :address, geocoding: true, if: ->(form) { Decidim.geocoder.present? && form.has_address? }
24
+ validates :address, geocoding: true, if: ->(form) { form.has_address? && !form.geocoded? }
19
25
  validates :address, presence: true, if: ->(form) { form.has_address? }
20
26
  validates :category, presence: true, if: ->(form) { form.category_id.present? }
21
27
  validates :scope, presence: true, if: ->(form) { form.scope_id.present? }
22
-
23
- validate :scope_belongs_to_participatory_space_scope
24
-
28
+ validates :scope_id, scope_belongs_to_component: true, if: ->(form) { form.scope_id.present? }
25
29
  validate :notify_missing_attachment_if_errored
26
30
 
27
31
  delegate :categories, to: :current_component
@@ -29,7 +33,8 @@ module Decidim
29
33
  def map_model(model)
30
34
  super
31
35
 
32
- @suggested_hashtags = Decidim::ContentRenderers::HashtagRenderer.new(model.body).extra_hashtags.map(&:name).map(&:downcase)
36
+ body = translated_attribute(model.body)
37
+ @suggested_hashtags = Decidim::ContentRenderers::HashtagRenderer.new(body).extra_hashtags.map(&:name).map(&:downcase)
33
38
 
34
39
  # The scope attribute is with different key (decidim_scope_id), so it
35
40
  # has to be manually mapped.
@@ -43,11 +48,11 @@ module Decidim
43
48
  @category ||= categories.find_by(id: category_id)
44
49
  end
45
50
 
46
- # Finds the Scope from the given decidim_scope_id, uses participatory space scope if missing.
51
+ # Finds the Scope from the given scope_id, uses participatory space scope if missing.
47
52
  #
48
53
  # Returns a Decidim::Scope
49
54
  def scope
50
- @scope ||= @scope_id ? current_participatory_space.scopes.find_by(id: @scope_id) : current_participatory_space.scope
55
+ @scope ||= @scope_id ? current_component.scopes.find_by(id: @scope_id) : current_component.scope
51
56
  end
52
57
 
53
58
  # Scope identifier
@@ -57,8 +62,16 @@ module Decidim
57
62
  @scope_id || scope&.id
58
63
  end
59
64
 
65
+ def geocoding_enabled?
66
+ Decidim::Map.available?(:geocoding) && current_component.settings.geocoding_enabled?
67
+ end
68
+
60
69
  def has_address?
61
- current_component.settings.geocoding_enabled? && has_address
70
+ geocoding_enabled? && has_address
71
+ end
72
+
73
+ def geocoded?
74
+ latitude.present? && longitude.present?
62
75
  end
63
76
 
64
77
  def extra_hashtags
@@ -84,10 +97,6 @@ module Decidim
84
97
 
85
98
  private
86
99
 
87
- def scope_belongs_to_participatory_space_scope
88
- errors.add(:scope_id, :invalid) if current_participatory_space.out_of_scope?(scope)
89
- end
90
-
91
100
  # This method will add an error to the `attachment` field only if there's
92
101
  # any error in any other field. This is needed because when the form has
93
102
  # an error, the attachment is lost, so we need a way to inform the user of
@@ -7,6 +7,8 @@ module Decidim
7
7
  # in order to use them in select forms for Proposals.
8
8
  #
9
9
  module ProposalsHelper
10
+ include Decidim::Admin::ResourceScopeHelper
11
+
10
12
  # Public: A formatted collection of Meetings to be used
11
13
  # in forms.
12
14
  def meetings_as_authors_selected
@@ -139,18 +139,10 @@ module Decidim
139
139
  render partial: "decidim/proposals/proposals/participatory_texts/proposal_vote_button.html", locals: { proposal: model, from_proposals_list: from_proposals_list }
140
140
  end
141
141
 
142
- def endorsers_for(proposal)
143
- proposal.endorsements.for_listing.map { |identity| present(identity.normalized_author) }
144
- end
145
-
146
142
  def form_has_address?
147
143
  @form.address.present? || @form.has_address
148
144
  end
149
145
 
150
- def authors_for(collaborative_draft)
151
- collaborative_draft.identities.map { |identity| present(identity) }
152
- end
153
-
154
146
  def show_voting_rules?
155
147
  return false unless votes_enabled?
156
148
 
@@ -178,6 +170,19 @@ module Decidim
178
170
  base += [["voted", t(".voted")]] if current_settings.votes_enabled?
179
171
  base
180
172
  end
173
+
174
+ def filter_origin_values
175
+ origin_values = []
176
+ origin_values << TreePoint.new("official", t("decidim.proposals.application_helper.filter_origin_values.official")) if component_settings.official_proposals_enabled
177
+ origin_values << TreePoint.new("citizens", t("decidim.proposals.application_helper.filter_origin_values.citizens"))
178
+ origin_values << TreePoint.new("user_group", t("decidim.proposals.application_helper.filter_origin_values.user_groups")) if current_organization.user_groups_enabled?
179
+ origin_values << TreePoint.new("meeting", t("decidim.proposals.application_helper.filter_origin_values.meetings"))
180
+
181
+ TreeNode.new(
182
+ TreePoint.new("", t("decidim.proposals.application_helper.filter_origin_values.all")),
183
+ origin_values
184
+ )
185
+ end
181
186
  end
182
187
  end
183
188
  end
@@ -44,7 +44,7 @@ module Decidim
44
44
  [
45
45
  Decidim::CheckBoxesTreeHelper::TreePoint.new("accepted", t("decidim.proposals.application_helper.filter_state_values.accepted")),
46
46
  Decidim::CheckBoxesTreeHelper::TreePoint.new("evaluating", t("decidim.proposals.application_helper.filter_state_values.evaluating")),
47
- Decidim::CheckBoxesTreeHelper::TreePoint.new("not_answered", t("decidim.proposals.application_helper.filter_state_values.not_answered")),
47
+ Decidim::CheckBoxesTreeHelper::TreePoint.new("state_not_published", t("decidim.proposals.application_helper.filter_state_values.not_answered")),
48
48
  Decidim::CheckBoxesTreeHelper::TreePoint.new("rejected", t("decidim.proposals.application_helper.filter_state_values.rejected"))
49
49
  ]
50
50
  )
@@ -6,7 +6,7 @@ module Decidim
6
6
  include Decidim::Resourceable
7
7
  include Decidim::Coauthorable
8
8
  include Decidim::HasComponent
9
- include Decidim::ScopableComponent
9
+ include Decidim::ScopableResource
10
10
  include Decidim::HasReference
11
11
  include Decidim::HasCategory
12
12
  include Decidim::Reportable
@@ -28,7 +28,7 @@ module Decidim
28
28
  class_name: "Decidim::User",
29
29
  foreign_key: :decidim_user_id
30
30
 
31
- geocoded_by :address, http_headers: ->(collaborative_draft) { { "Referer" => collaborative_draft.component.organization.host } }
31
+ geocoded_by :address
32
32
 
33
33
  scope :open, -> { where(state: "open") }
34
34
  scope :withdrawn, -> { where(state: "withdrawn") }
@@ -8,6 +8,9 @@ module Decidim
8
8
  include Decidim::HasComponent
9
9
  include Decidim::Traceable
10
10
  include Decidim::Loggable
11
+ include Decidim::TranslatableResource
12
+
13
+ translatable_fields :title, :description
11
14
  end
12
15
  end
13
16
  end
@@ -7,7 +7,7 @@ module Decidim
7
7
  include Decidim::Resourceable
8
8
  include Decidim::Coauthorable
9
9
  include Decidim::HasComponent
10
- include Decidim::ScopableComponent
10
+ include Decidim::ScopableResource
11
11
  include Decidim::HasReference
12
12
  include Decidim::HasCategory
13
13
  include Decidim::Reportable
@@ -19,13 +19,16 @@ module Decidim
19
19
  include Decidim::Loggable
20
20
  include Decidim::Fingerprintable
21
21
  include Decidim::DataPortability
22
- include Decidim::Hashtaggable
23
22
  include Decidim::Proposals::ParticipatoryTextSection
24
23
  include Decidim::Amendable
25
24
  include Decidim::NewsletterParticipant
26
25
  include Decidim::Randomable
27
26
  include Decidim::Endorsable
28
27
  include Decidim::Proposals::Valuatable
28
+ include Decidim::TranslatableResource
29
+ include Decidim::TranslatableAttributes
30
+
31
+ translatable_fields :title, :body
29
32
 
30
33
  POSSIBLE_STATES = %w(not_answered evaluating accepted rejected withdrawn).freeze
31
34
 
@@ -49,7 +52,7 @@ module Decidim
49
52
 
50
53
  validates :title, :body, presence: true
51
54
 
52
- geocoded_by :address, http_headers: ->(proposal) { { "Referer" => proposal.component.organization.host } }
55
+ geocoded_by :address
53
56
 
54
57
  scope :answered, -> { where.not(answered_at: nil) }
55
58
  scope :not_answered, -> { where(answered_at: nil) }
@@ -66,27 +69,6 @@ module Decidim
66
69
  scope :drafts, -> { where(published_at: nil) }
67
70
  scope :except_drafts, -> { where.not(published_at: nil) }
68
71
  scope :published, -> { where.not(published_at: nil) }
69
- scope :official_origin, lambda {
70
- where.not(coauthorships_count: 0)
71
- .joins(:coauthorships)
72
- .where(decidim_coauthorships: { decidim_author_type: "Decidim::Organization" })
73
- }
74
- scope :citizens_origin, lambda {
75
- where.not(coauthorships_count: 0)
76
- .joins(:coauthorships)
77
- .where.not(decidim_coauthorships: { decidim_author_type: "Decidim::Organization" })
78
- }
79
- scope :user_group_origin, lambda {
80
- where.not(coauthorships_count: 0)
81
- .joins(:coauthorships)
82
- .where(decidim_coauthorships: { decidim_author_type: "Decidim::UserBaseEntity" })
83
- .where.not(decidim_coauthorships: { decidim_user_group_id: nil })
84
- }
85
- scope :meeting_origin, lambda {
86
- where.not(coauthorships_count: 0)
87
- .joins(:coauthorships)
88
- .where(decidim_coauthorships: { decidim_author_type: "Decidim::Meetings::Meeting" })
89
- }
90
72
  scope :sort_by_valuation_assignments_count_asc, lambda {
91
73
  order(sort_by_valuation_assignments_count_nulls_last_query + "ASC NULLS FIRST")
92
74
  }
@@ -107,8 +89,8 @@ module Decidim
107
89
  searchable_fields({
108
90
  scope_id: :decidim_scope_id,
109
91
  participatory_space: { component: :participatory_space },
110
- D: :search_body,
111
- A: :search_title,
92
+ D: :body,
93
+ A: :title,
112
94
  datetime: :published_at
113
95
  },
114
96
  index_on_create: ->(proposal) { proposal.official? },
@@ -146,7 +128,7 @@ module Decidim
146
128
 
147
129
  endorsements_participants_ids = Decidim::Endorsement.where(resource: proposals)
148
130
  .where(decidim_author_type: "Decidim::UserBaseEntity")
149
- .map(&:decidim_author_id).flatten.compact.uniq
131
+ .pluck(:decidim_author_id).to_a.compact.uniq
150
132
 
151
133
  (endorsements_participants_ids + participants_has_voted_ids + coauthors_recipients_ids).flatten.compact.uniq
152
134
  end
@@ -300,19 +282,6 @@ module Decidim
300
282
  published_at.nil?
301
283
  end
302
284
 
303
- # method for sort_link by number of comments
304
- ransacker :commentable_comments_count do
305
- query = <<-SQL
306
- (SELECT COUNT(decidim_comments_comments.id)
307
- FROM decidim_comments_comments
308
- WHERE decidim_comments_comments.decidim_commentable_id = decidim_proposals_proposals.id
309
- AND decidim_comments_comments.decidim_commentable_type = 'Decidim::Proposals::Proposal'
310
- GROUP BY decidim_comments_comments.decidim_commentable_id
311
- )
312
- SQL
313
- Arel.sql(query)
314
- end
315
-
316
285
  # Defines the base query so that ransack can actually sort by this value
317
286
  def self.sort_by_valuation_assignments_count_nulls_last_query
318
287
  <<-SQL
@@ -359,6 +328,10 @@ module Decidim
359
328
  Arel.sql("CASE WHEN state = 'withdrawn' THEN 'withdrawn' WHEN state_published_at IS NULL THEN NULL ELSE state END")
360
329
  end
361
330
 
331
+ ransacker :title do
332
+ Arel.sql(%{("decidim_proposals_proposals"."title")::text})
333
+ end
334
+
362
335
  ransacker :id_string do
363
336
  Arel.sql(%{cast("decidim_proposals_proposals"."id" as text)})
364
337
  end
@@ -5,10 +5,15 @@ module Decidim
5
5
  module AdminLog
6
6
  module ValueTypes
7
7
  class ProposalTitleBodyPresenter < Decidim::Log::ValueTypes::DefaultPresenter
8
+ include Decidim::TranslatableAttributes
9
+
8
10
  def present
9
11
  return unless value
10
12
 
11
- renderer = Decidim::ContentRenderers::HashtagRenderer.new(value)
13
+ translated_value = translated_attribute(value)
14
+ return if translated_value.blank?
15
+
16
+ renderer = Decidim::ContentRenderers::HashtagRenderer.new(translated_value)
12
17
  renderer.render(links: false).html_safe
13
18
  end
14
19
  end
@@ -5,38 +5,10 @@ module Decidim
5
5
  #
6
6
  # A dummy presenter to abstract out the author of an official proposal.
7
7
  #
8
- class OfficialAuthorPresenter
8
+ class OfficialAuthorPresenter < Decidim::OfficialAuthorPresenter
9
9
  def name
10
10
  I18n.t("decidim.proposals.models.proposal.fields.official_proposal")
11
11
  end
12
-
13
- def nickname
14
- ""
15
- end
16
-
17
- def badge
18
- ""
19
- end
20
-
21
- def profile_path
22
- ""
23
- end
24
-
25
- def avatar_url
26
- ActionController::Base.helpers.asset_path("decidim/default-avatar.svg")
27
- end
28
-
29
- def deleted?
30
- false
31
- end
32
-
33
- def can_be_contacted?
34
- false
35
- end
36
-
37
- def has_tooltip?
38
- false
39
- end
40
12
  end
41
13
  end
42
14
  end
@@ -9,6 +9,7 @@ module Decidim
9
9
  include Rails.application.routes.mounted_helpers
10
10
  include ActionView::Helpers::UrlHelper
11
11
  include Decidim::SanitizeHelper
12
+ include Decidim::TranslatableAttributes
12
13
 
13
14
  def author
14
15
  @author ||= if official?
@@ -38,7 +39,7 @@ module Decidim
38
39
  #
39
40
  # Returns a String.
40
41
  def title(links: false, extras: true, html_escape: false)
41
- text = proposal.title
42
+ text = translated_attribute(proposal.title)
42
43
  text = decidim_html_escape(text) if html_escape
43
44
 
44
45
  renderer = Decidim::ContentRenderers::HashtagRenderer.new(text)
@@ -50,12 +51,9 @@ module Decidim
50
51
  end
51
52
 
52
53
  def body(links: false, extras: true, strip_tags: false)
53
- text = proposal.body
54
+ text = translated_attribute(proposal.body)
54
55
 
55
- if strip_tags
56
- text = text.gsub(%r{<\/p>}, "\n\n")
57
- text = strip_tags(text)
58
- end
56
+ text = strip_tags(sanitize_text(text)) if strip_tags
59
57
 
60
58
  renderer = Decidim::ContentRenderers::HashtagRenderer.new(text)
61
59
  text = renderer.render(links: links, extras: extras).html_safe
@@ -93,6 +91,45 @@ module Decidim
93
91
  def resource_manifest
94
92
  proposal.class.resource_manifest
95
93
  end
94
+
95
+ private
96
+
97
+ def sanitize_unordered_lists(text)
98
+ text.gsub(%r{(?=.*<\/ul>)(?!.*?<li>.*?<\/ol>.*?<\/ul>)<li>}) { |li| li + "• " }
99
+ end
100
+
101
+ def sanitize_ordered_lists(text)
102
+ i = 0
103
+
104
+ text.gsub(%r{(?=.*<\/ol>)(?!.*?<li>.*?<\/ul>.*?<\/ol>)<li>}) do |li|
105
+ i += 1
106
+
107
+ li + "#{i}. "
108
+ end
109
+ end
110
+
111
+ def add_line_feeds_to_paragraphs(text)
112
+ text.gsub("</p>") { |p| p + "\n\n" }
113
+ end
114
+
115
+ def add_line_feeds_to_list_items(text)
116
+ text.gsub("</li>") { |li| li + "\n" }
117
+ end
118
+
119
+ # Adds line feeds after the paragraph and list item closing tags.
120
+ #
121
+ # Returns a String.
122
+ def add_line_feeds(text)
123
+ add_line_feeds_to_paragraphs(add_line_feeds_to_list_items(text))
124
+ end
125
+
126
+ # Maintains the paragraphs and lists separations with their bullet points and
127
+ # list numberings where appropriate.
128
+ #
129
+ # Returns a String.
130
+ def sanitize_text(text)
131
+ add_line_feeds(sanitize_ordered_lists(sanitize_unordered_lists(text)))
132
+ end
96
133
  end
97
134
  end
98
135
  end
@@ -30,8 +30,8 @@ module Decidim
30
30
  .published
31
31
  .where(
32
32
  "GREATEST(#{title_similarity}, #{body_similarity}) >= ?",
33
- @proposal.title,
34
- @proposal.body,
33
+ translated_attribute(@proposal.title),
34
+ translated_attribute(@proposal.body),
35
35
  Decidim::Proposals.similarity_threshold
36
36
  )
37
37
  .limit(Decidim::Proposals.similarity_limit)
@@ -40,11 +40,11 @@ module Decidim
40
40
  private
41
41
 
42
42
  def title_similarity
43
- "similarity(title, ?)"
43
+ "similarity(title::text, ?)"
44
44
  end
45
45
 
46
46
  def body_similarity
47
- "similarity(body, ?)"
47
+ "similarity(body::text, ?)"
48
48
  end
49
49
  end
50
50
  end