decidim-core 0.31.5 → 0.31.6

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.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/amendable/amend_button_card/show.erb +2 -2
  3. data/app/cells/decidim/author_cell.rb +0 -4
  4. data/app/cells/decidim/content_blocks/cta_settings_form/show.erb +1 -1
  5. data/app/cells/decidim/content_blocks/hero_settings_form/show.erb +2 -2
  6. data/app/cells/decidim/content_blocks/highlighted_content_banner_cell.rb +1 -1
  7. data/app/cells/decidim/content_blocks/highlighted_content_banner_settings_form/show.erb +1 -1
  8. data/app/cells/decidim/content_blocks/highlighted_participatory_spaces_cell.rb +1 -1
  9. data/app/cells/decidim/content_blocks/participatory_space_hero_settings_form/show.erb +1 -1
  10. data/app/cells/decidim/content_blocks/static_page/section_settings_form/show.erb +3 -1
  11. data/app/cells/decidim/content_blocks/static_page/summary_settings_form/show.erb +3 -1
  12. data/app/cells/decidim/content_blocks/static_page/two_pane_section_settings_form/show.erb +4 -2
  13. data/app/cells/decidim/resource_types_filter/show.erb +2 -2
  14. data/app/commands/decidim/destroy_account.rb +12 -1
  15. data/app/commands/decidim/multiple_attachments_methods.rb +28 -27
  16. data/app/controllers/concerns/decidim/devise_controllers.rb +10 -0
  17. data/app/jobs/decidim/process_inactive_participant_job.rb +0 -7
  18. data/app/mailers/decidim/delete_user_mailer.rb +14 -0
  19. data/app/mailers/decidim/participants_account_mailer.rb +0 -16
  20. data/app/models/decidim/moderation.rb +1 -1
  21. data/app/models/decidim/participatory_space_private_user.rb +1 -1
  22. data/app/models/decidim/user_base_entity.rb +17 -2
  23. data/app/models/decidim/user_moderation.rb +1 -1
  24. data/app/packs/src/decidim/controllers/form_validator/form_validator.js +6 -6
  25. data/app/packs/src/decidim/controllers/form_validator/form_validator.test.js +23 -1
  26. data/app/packs/src/decidim/controllers/mention/controller.js +296 -140
  27. data/app/packs/src/decidim/controllers/mention/input_mentions.test.js +120 -457
  28. data/app/packs/src/decidim/controllers/multiple_mentions/controller.js +68 -32
  29. data/app/packs/src/decidim/controllers/multiple_mentions/input_multiple_mentions.test.js +30 -23
  30. data/app/packs/src/decidim/direct_uploads/upload_field.js +4 -4
  31. data/app/packs/src/decidim/direct_uploads/upload_modal.js +11 -7
  32. data/app/packs/src/decidim/geocoding/reverse_geocoding.js +15 -5
  33. data/app/packs/src/decidim/geocoding/reverse_geocoding.test.js +197 -0
  34. data/app/packs/src/decidim/index.js +4 -3
  35. data/app/packs/src/decidim/sw/sw.js +1 -1
  36. data/app/packs/src/decidim/utilities/text.js +6 -6
  37. data/app/packs/stylesheets/decidim/_conversations.scss +14 -0
  38. data/app/packs/stylesheets/decidim/_editor_suggestions.scss +49 -0
  39. data/app/packs/stylesheets/decidim/_floating_help.scss +1 -1
  40. data/app/packs/stylesheets/decidim/_tom_select.scss +23 -0
  41. data/app/packs/stylesheets/decidim/application.scss +2 -0
  42. data/app/packs/stylesheets/decidim/editor.scss +2 -33
  43. data/app/packs/stylesheets/decidim/geocoding_addons.scss +10 -2
  44. data/app/presenters/decidim/menu_item_presenter.rb +9 -3
  45. data/app/presenters/decidim/stats_presenter.rb +1 -1
  46. data/app/uploaders/decidim/image_uploader.rb +1 -1
  47. data/app/views/decidim/delete_user_mailer/delete.html.erb +6 -0
  48. data/app/views/decidim/gamification/badges/index.html.erb +1 -1
  49. data/app/views/decidim/homepage/show.html.erb +1 -1
  50. data/app/views/decidim/last_activities/index.html.erb +1 -1
  51. data/app/views/decidim/messaging/conversations/_error_modal.html.erb +11 -19
  52. data/app/views/decidim/messaging/conversations/error.js.erb +12 -7
  53. data/app/views/decidim/newsletters/unsubscribe.html.erb +1 -1
  54. data/app/views/decidim/offline/show.html.erb +1 -1
  55. data/app/views/decidim/pages/index.html.erb +1 -1
  56. data/app/views/decidim/profiles/show.html.erb +1 -1
  57. data/app/views/decidim/shared/_resource_actions.html.erb +4 -4
  58. data/app/views/decidim/user_activities/index.html.erb +1 -1
  59. data/app/views/layouts/decidim/_wrapper.html.erb +2 -2
  60. data/app/views/layouts/decidim/header/_menu_breadcrumb_mobile_tablet.html.erb +1 -1
  61. data/app/views/layouts/decidim/shared/_layout_center.html.erb +1 -1
  62. data/app/views/layouts/decidim/shared/_layout_item.html.erb +1 -1
  63. data/app/views/layouts/decidim/shared/_layout_two_col.html.erb +1 -1
  64. data/config/locales/ar.yml +0 -5
  65. data/config/locales/bg.yml +0 -6
  66. data/config/locales/ca-IT.yml +9 -10
  67. data/config/locales/ca.yml +9 -10
  68. data/config/locales/cs.yml +2 -13
  69. data/config/locales/de.yml +5 -17
  70. data/config/locales/el.yml +0 -4
  71. data/config/locales/en.yml +8 -9
  72. data/config/locales/es-MX.yml +8 -9
  73. data/config/locales/es-PY.yml +8 -9
  74. data/config/locales/es.yml +8 -9
  75. data/config/locales/eu.yml +48 -49
  76. data/config/locales/fi-plain.yml +9 -9
  77. data/config/locales/fi.yml +9 -9
  78. data/config/locales/fr-CA.yml +9 -9
  79. data/config/locales/fr.yml +9 -9
  80. data/config/locales/gl.yml +0 -3
  81. data/config/locales/hu.yml +0 -5
  82. data/config/locales/id-ID.yml +0 -3
  83. data/config/locales/it.yml +1 -3
  84. data/config/locales/ja.yml +24 -21
  85. data/config/locales/lb.yml +0 -5
  86. data/config/locales/lt.yml +0 -5
  87. data/config/locales/lv.yml +0 -3
  88. data/config/locales/nl.yml +0 -5
  89. data/config/locales/no.yml +0 -5
  90. data/config/locales/pl.yml +2 -8
  91. data/config/locales/pt-BR.yml +2 -15
  92. data/config/locales/pt.yml +0 -5
  93. data/config/locales/ro-RO.yml +1 -14
  94. data/config/locales/sk.yml +1 -14
  95. data/config/locales/sv.yml +2 -11
  96. data/config/locales/tr-TR.yml +0 -6
  97. data/config/locales/zh-CN.yml +0 -5
  98. data/config/locales/zh-TW.yml +0 -5
  99. data/lib/decidim/api/functions/user_entity_list.rb +2 -0
  100. data/lib/decidim/attachment_attributes.rb +58 -9
  101. data/lib/decidim/command.rb +1 -1
  102. data/lib/decidim/content_renderers/base_renderer.rb +112 -0
  103. data/lib/decidim/content_renderers/blob_renderer.rb +4 -7
  104. data/lib/decidim/content_renderers/mention_resource_renderer.rb +10 -6
  105. data/lib/decidim/content_renderers/resource_renderer.rb +16 -7
  106. data/lib/decidim/content_renderers/user_renderer.rb +11 -9
  107. data/lib/decidim/core/content_blocks/registry_manager.rb +4 -4
  108. data/lib/decidim/core/engine.rb +8 -0
  109. data/lib/decidim/core/test/factories.rb +3 -0
  110. data/lib/decidim/core/test/shared_examples/admin_resource_gallery_examples.rb +10 -10
  111. data/lib/decidim/core/test/shared_examples/comments_examples.rb +6 -6
  112. data/lib/decidim/core/version.rb +1 -1
  113. data/lib/decidim/map/autocomplete.rb +4 -3
  114. data/lib/decidim/searchable.rb +5 -0
  115. data/lib/decidim/view_model.rb +1 -1
  116. data/lib/tasks/decidim_mailers_tasks.rake +31 -9
  117. metadata +11 -10
  118. data/app/commands/decidim/gallery_methods.rb +0 -107
  119. data/app/packs/src/decidim/vendor/tribute.js +0 -1890
  120. data/app/packs/stylesheets/decidim/_tribute.scss +0 -36
  121. data/app/views/decidim/participants_account_mailer/removal_notification.html.erb +0 -11
@@ -15,24 +15,39 @@ module Decidim
15
15
  #
16
16
  # name - The attribute's name
17
17
  #
18
- #
19
18
  # Example:
20
19
  #
21
- # attachment_attribute :photos
20
+ # attachments_attribute :photos
22
21
  # # This will create two attributes of the following types:
23
22
  # # attribute :photos, Array[Integer]
24
23
  # # attribute :add_photos, Array
25
- # # In addition, it will generate the getter method for the attribute
26
- # # returning an array of the Decidim::Attachment records.
24
+ # # In addition, it will generate:
25
+ # # - A setter that handles String (JSON/CSV), Integer, or Array inputs.
26
+ # # - A getter that falls back to add_photos when the attribute is blank.
27
+ # # - Private helpers for parsing and extracting IDs.
27
28
  #
28
29
  # Returns nothing.
29
- def attachments_attribute(name)
30
+ def attachments_attribute(name) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
30
31
  attribute name, Array[Integer]
31
32
  attribute :"add_#{name}", Array
32
33
 
33
- # Define the getter method that fetches the attachment records based on
34
- # their types. For Strings and Integers, assumes they are IDs and will
35
- # fetch the attachment record matching that ID.
34
+ # Setter: coerces String (JSON or comma-separated IDs) and bare Integer
35
+ # values into the expected Array[Integer] format before delegating to super.
36
+ define_method :"#{name}=" do |value|
37
+ case value
38
+ when String
39
+ parsed = send(:"parse_string_#{name}", value)
40
+ parsed.any? ? super(parsed) : super(value)
41
+ when Integer
42
+ super([value])
43
+ else
44
+ super(value)
45
+ end
46
+ end
47
+
48
+ # Getter: resolves stored integer IDs to Decidim::Attachment records,
49
+ # caching the result on the instance. Falls back to extracting IDs from
50
+ # add_<name> when the attribute itself is blank, then resolves those too.
36
51
  variable_name = "@#{name}_records"
37
52
  define_method name do
38
53
  return instance_variable_get(variable_name) if instance_variable_defined?(variable_name)
@@ -40,9 +55,15 @@ module Decidim
40
55
  original = @attributes[name.to_s].value_before_type_cast
41
56
  return original if original && !original.is_a?(Array)
42
57
 
58
+ ids = if super().blank? && send(:"add_#{name}").present?
59
+ send(:"extract_ids_from_add_#{name}")
60
+ else
61
+ super()
62
+ end
63
+
43
64
  instance_variable_set(
44
65
  variable_name,
45
- super().map do |attachment|
66
+ ids.map do |attachment|
46
67
  if attachment.is_a?(Integer)
47
68
  Decidim::Attachment.find_by(id: attachment)
48
69
  else
@@ -51,6 +72,34 @@ module Decidim
51
72
  end.compact
52
73
  )
53
74
  end
75
+
76
+ # Private helpers -------------------------------------------------------
77
+
78
+ define_method :"extract_ids_from_add_#{name}" do
79
+ send(:"add_#{name}")
80
+ .select { |item| item.is_a?(Hash) && (item[:id].present? || item["id"].present?) }
81
+ .map { |item| (item[:id] || item["id"]).to_i }
82
+ end
83
+
84
+ define_method :"parse_string_#{name}" do |value|
85
+ return [] if value.blank?
86
+
87
+ send(:"parse_#{name}_ids", value)
88
+ end
89
+
90
+ define_method :"parse_#{name}_ids" do |value|
91
+ ids = begin
92
+ Array(JSON.parse(value))
93
+ rescue JSON::ParserError
94
+ value.split(",").map(&:strip)
95
+ end
96
+
97
+ ids.map(&:to_i).reject(&:zero?)
98
+ end
99
+
100
+ private :"extract_ids_from_add_#{name}"
101
+ private :"parse_string_#{name}"
102
+ private :"parse_#{name}_ids"
54
103
  end
55
104
  end
56
105
  end
@@ -53,7 +53,7 @@ module Decidim
53
53
  protected
54
54
 
55
55
  def event_arguments
56
- raise "#{self.class} must implement #event_arguments"
56
+ raise "#{self.class} must implement #event_arguments (passed to #{event_namespace})"
57
57
  end
58
58
 
59
59
  def event_namespace
@@ -12,6 +12,16 @@ module Decidim
12
12
  class BaseRenderer
13
13
  include Decidim::ContentProcessor::Common
14
14
 
15
+ ReplacementContext = Struct.new(:placement, :node_name, :attribute_name, :ancestor_names, keyword_init: true) do
16
+ def text?
17
+ placement == :text
18
+ end
19
+
20
+ def attribute?
21
+ placement == :attribute
22
+ end
23
+ end
24
+
15
25
  # @return [String] the content to be formatted
16
26
  attr_reader :content
17
27
 
@@ -34,6 +44,108 @@ module Decidim
34
44
  def render(_options = nil)
35
45
  content
36
46
  end
47
+
48
+ protected
49
+
50
+ def replace_pattern_by_context(text, pattern, skip_ancestor_tags: %w(code pre script style), on_missing: "")
51
+ return text unless text.respond_to?(:gsub)
52
+
53
+ skip_ancestor_tags = Array(skip_ancestor_tags).map(&:to_s)
54
+
55
+ has_match = pattern.is_a?(String) ? text.include?(pattern) : pattern.match?(text)
56
+ return text unless has_match
57
+
58
+ fragment = html_fragment(text)
59
+ attr_modified = replace_pattern_in_attributes(fragment, pattern, skip_ancestor_tags:, on_missing:) do |match, context|
60
+ yield(match, context)
61
+ end
62
+ text_modified = replace_pattern_in_text_nodes(fragment, pattern, skip_ancestor_tags:, on_missing:) do |match, context|
63
+ yield(match, context)
64
+ end
65
+
66
+ return text unless attr_modified || text_modified
67
+
68
+ fragment.to_s
69
+ end
70
+
71
+ private
72
+
73
+ def replace_pattern_in_attributes(fragment, pattern, skip_ancestor_tags:, on_missing:)
74
+ modified = false
75
+ fragment.xpath(".//*").each do |node|
76
+ next if skip_replacement_for_node?(node, skip_ancestor_tags)
77
+
78
+ node.attribute_nodes.each do |attribute|
79
+ replaced_value = attribute.value.gsub(pattern) do |match|
80
+ replace_match(match, replacement_context(node, placement: :attribute, attribute_name: attribute.name), on_missing:) do |resolved_match, context|
81
+ yield(resolved_match, context)
82
+ end
83
+ end
84
+ unless replaced_value == attribute.value
85
+ attribute.value = replaced_value
86
+ modified = true
87
+ end
88
+ end
89
+ end
90
+ modified
91
+ end
92
+
93
+ def replace_pattern_in_text_nodes(fragment, pattern, skip_ancestor_tags:, on_missing:)
94
+ modified = false
95
+ fragment.xpath(".//text()").each do |node|
96
+ parent = node.parent
97
+ next if skip_replacement_for_node?(parent, skip_ancestor_tags)
98
+
99
+ original_text = node.text
100
+ has_node_match = pattern.is_a?(String) ? original_text.include?(pattern) : pattern.match?(original_text)
101
+ next unless has_node_match
102
+
103
+ doc = node.document
104
+ context = replacement_context(parent, placement: :text)
105
+ last_pos = 0
106
+
107
+ original_text.scan(pattern) do
108
+ m = Regexp.last_match
109
+ node.add_previous_sibling(Nokogiri::XML::Text.new(original_text[last_pos...m.begin(0)], doc)) if m.begin(0) > last_pos
110
+
111
+ replacement = replace_match(m[0], context, on_missing:) do |resolved_match, ctx|
112
+ yield(resolved_match, ctx)
113
+ end
114
+
115
+ Loofah.fragment(replacement.to_s).children.to_a.each do |child|
116
+ node.add_previous_sibling(child)
117
+ end
118
+
119
+ last_pos = m.end(0)
120
+ end
121
+
122
+ node.add_previous_sibling(Nokogiri::XML::Text.new(original_text[last_pos..], doc)) if last_pos < original_text.length
123
+ node.remove
124
+ modified = true
125
+ end
126
+ modified
127
+ end
128
+
129
+ def replace_match(match, context, on_missing:)
130
+ yield(match, context)
131
+ rescue ActiveRecord::RecordNotFound
132
+ on_missing.respond_to?(:call) ? on_missing.call(match, context) : on_missing
133
+ end
134
+
135
+ def replacement_context(node, placement:, attribute_name: nil)
136
+ ReplacementContext.new(
137
+ placement:,
138
+ node_name: node&.name,
139
+ attribute_name:,
140
+ ancestor_names: node ? node.ancestors.map(&:name) : []
141
+ )
142
+ end
143
+
144
+ def skip_replacement_for_node?(node, skip_ancestor_tags)
145
+ return false unless node
146
+
147
+ ([node.name] + node.ancestors.map(&:name)).any? { |name| skip_ancestor_tags.include?(name) }
148
+ end
37
149
  end
38
150
  end
39
151
  end
@@ -33,11 +33,10 @@ module Decidim
33
33
  protected
34
34
 
35
35
  def replace_pattern(text, pattern)
36
- return text unless text.respond_to?(:gsub)
37
-
38
- text.gsub(pattern) do
39
- blob_gid = Regexp.last_match(1)
40
- variation_key = Regexp.last_match(3)
36
+ replace_pattern_by_context(text, pattern) do |match, _context|
37
+ match_data = match.match(GLOBAL_ID_REGEX)
38
+ blob_gid = match_data[1]
39
+ variation_key = match_data[3]
41
40
 
42
41
  blob = GlobalID::Locator.locate(blob_gid)
43
42
  if variation_key
@@ -50,8 +49,6 @@ module Decidim
50
49
  else
51
50
  blob_url(blob)
52
51
  end
53
- rescue ActiveRecord::RecordNotFound => _e
54
- ""
55
52
  end
56
53
  end
57
54
 
@@ -27,17 +27,15 @@ module Decidim
27
27
  protected
28
28
 
29
29
  def replace_pattern(text, pattern, editor:)
30
- return text unless text.respond_to?(:gsub)
31
-
32
- text.gsub(pattern) do |resource_gid|
30
+ replace_pattern_by_context(text, pattern) do |resource_gid, context|
33
31
  resource = GlobalID::Locator.locate(resource_gid)
34
- if editor
32
+ if context.attribute?
33
+ render_resource_url(resource, editor:)
34
+ elsif editor
35
35
  render_editor(resource_gid, resource)
36
36
  else
37
37
  render_resource_link(resource)
38
38
  end
39
- rescue ActiveRecord::RecordNotFound => _e
40
- ""
41
39
  end
42
40
  end
43
41
 
@@ -50,6 +48,12 @@ module Decidim
50
48
  link_to mention_title(resource), resource_path(resource)
51
49
  end
52
50
 
51
+ def render_resource_url(resource, editor:)
52
+ return resource_path(resource) if editor
53
+
54
+ Decidim::ResourceLocatorPresenter.new(resource).url
55
+ end
56
+
53
57
  def mention_title(resource)
54
58
  title = presenter_for(resource).title
55
59
 
@@ -11,17 +11,26 @@ module Decidim
11
11
  #
12
12
  # @return [String] the content ready to display (contains HTML)
13
13
  def render(_options = nil)
14
- return content unless content.respond_to?(:gsub)
15
-
16
- content.gsub(regex) do |resource_gid|
14
+ replace_pattern_by_context(content, regex, on_missing: proc { |match, _| "~#{match.split("/").last}" }) do |resource_gid, context|
17
15
  resource = GlobalID::Locator.locate(resource_gid)
18
- resource.presenter.display_mention
19
- rescue ActiveRecord::RecordNotFound
20
- resource_id = resource_gid.split("/").last
21
- "~#{resource_id}"
16
+
17
+ if context.attribute?
18
+ resource_attribute_value(resource)
19
+ else
20
+ resource.presenter.display_mention
21
+ end
22
22
  end
23
23
  end
24
24
 
25
+ protected
26
+
27
+ def resource_attribute_value(resource)
28
+ presenter = resource.presenter
29
+ return presenter.profile_path if presenter.respond_to?(:profile_path)
30
+
31
+ Decidim::ResourceLocatorPresenter.new(resource).path
32
+ end
33
+
25
34
  def regex
26
35
  raise "Not implemented"
27
36
  end
@@ -24,17 +24,15 @@ module Decidim
24
24
  protected
25
25
 
26
26
  def replace_pattern(text, pattern, editor:)
27
- return text unless text.respond_to?(:gsub)
28
-
29
- text.gsub(pattern) do |mentionable_gid|
30
- mentionable = GlobalID::Locator.locate(mentionable_gid)
31
- if editor
32
- render_editor(mentionable)
27
+ replace_pattern_by_context(text, pattern) do |user_gid, context|
28
+ user = GlobalID::Locator.locate(user_gid)
29
+ if context.attribute?
30
+ render_profile_path(user)
31
+ elsif editor
32
+ render_editor(user)
33
33
  else
34
- render_text(mentionable)
34
+ render_text(user)
35
35
  end
36
- rescue ActiveRecord::RecordNotFound => _e
37
- ""
38
36
  end
39
37
  end
40
38
 
@@ -55,6 +53,10 @@ module Decidim
55
53
  presenter_for(user).display_mention
56
54
  end
57
55
  end
56
+
57
+ def render_profile_path(user)
58
+ presenter_for(user).profile_path
59
+ end
58
60
  end
59
61
  end
60
62
  end
@@ -51,11 +51,11 @@ module Decidim
51
51
  ]
52
52
 
53
53
  content_block.settings do |settings|
54
- settings.attribute :title, type: :text, translated: true
54
+ settings.attribute :title, type: :text, translated: true, required: true
55
55
  settings.attribute :short_description, type: :text, translated: true
56
- settings.attribute :action_button_title, type: :text, translated: true
57
- settings.attribute :action_button_subtitle, type: :text, translated: true
58
- settings.attribute :action_button_url, type: :text
56
+ settings.attribute :action_button_title, type: :text, translated: true, required: true
57
+ settings.attribute :action_button_subtitle, type: :text, translated: true, required: true
58
+ settings.attribute :action_button_url, type: :text, required: true
59
59
  end
60
60
 
61
61
  content_block.default!
@@ -495,6 +495,14 @@ module Decidim
495
495
  end
496
496
  end
497
497
 
498
+ initializer "decidim_core.delete_account" do
499
+ config.to_prepare do
500
+ ActiveSupport::Notifications.subscribe("decidim.destroy_account:after") do |_event_name, data|
501
+ Decidim::DeleteUserMailer.delete(user_email: data[:user_email], user_name: data[:user_name], locale: data[:locale], organization: data[:organization]).deliver_later
502
+ end
503
+ end
504
+ end
505
+
498
506
  initializer "decidim_core.add_cells_view_paths" do
499
507
  Cell::ViewModel.view_paths << Rails.root.join("app/views") # for partials
500
508
  Cell::ViewModel.view_paths << File.expand_path("#{Decidim::Core::Engine.root}/app/cells")
@@ -85,6 +85,8 @@ FactoryBot.define do
85
85
  "#{Faker::Lorem.sentence(word_count: 1, supplemental: true, random_words_to_add: 3)} #{n}"
86
86
  end
87
87
 
88
+ sequence(:component_position)
89
+
88
90
  factory :category, class: "Decidim::Category" do
89
91
  transient do
90
92
  skip_injection { false }
@@ -463,6 +465,7 @@ FactoryBot.define do
463
465
  manifest_name { "dummy" }
464
466
  published_at { Time.current }
465
467
  deleted_at { nil }
468
+ weight { generate(:component_position) }
466
469
  settings do
467
470
  {
468
471
  dummy_global_translatable_text: generate_localized_title(:dummy_global_translatable_text, skip_injection:),
@@ -87,7 +87,7 @@ end
87
87
 
88
88
  shared_examples "admin creates resource gallery" do
89
89
  context "when uploading images" do
90
- let(:uploaded_photos) do
90
+ let(:uploaded_attachments) do
91
91
  [
92
92
  {
93
93
  title: "Picture of the city",
@@ -99,10 +99,10 @@ shared_examples "admin creates resource gallery" do
99
99
  }
100
100
  ]
101
101
  end
102
- let(:photos) { [] }
102
+ let(:current_attachments) { [] }
103
103
 
104
104
  it "creates a gallery for the resource" do
105
- expect { command.call }.to change(Decidim::Attachment, :count).by(uploaded_photos.count)
105
+ expect { command.call }.to change(Decidim::Attachment, :count).by(uploaded_attachments.count)
106
106
 
107
107
  resource = resource_class.last
108
108
  expect(resource.photos.count).to eq(2)
@@ -111,7 +111,7 @@ shared_examples "admin creates resource gallery" do
111
111
  end
112
112
 
113
113
  context "when gallery is left blank" do
114
- let(:uploaded_photos) { [] }
114
+ let(:uploaded_attachments) { [] }
115
115
 
116
116
  it "broadcasts ok" do
117
117
  expect { command.call }.to broadcast(:ok)
@@ -122,7 +122,7 @@ end
122
122
 
123
123
  shared_examples "admin manages resource gallery" do
124
124
  context "when managing images" do
125
- let(:uploaded_photos) do
125
+ let(:uploaded_attachments) do
126
126
  [
127
127
  {
128
128
  title: "Picture of the city",
@@ -134,10 +134,10 @@ shared_examples "admin manages resource gallery" do
134
134
  }
135
135
  ]
136
136
  end
137
- let(:photos) { [] }
137
+ let(:current_attachments) { [] }
138
138
 
139
139
  it "creates a gallery for the resource" do
140
- expect { command.call }.to change(Decidim::Attachment, :count).by(uploaded_photos.count)
140
+ expect { command.call }.to change(Decidim::Attachment, :count).by(uploaded_attachments.count)
141
141
  resource = resource_class.last
142
142
  expect(resource.photos.count).to eq(2)
143
143
  last_attachment = Decidim::Attachment.last
@@ -145,7 +145,7 @@ shared_examples "admin manages resource gallery" do
145
145
  end
146
146
 
147
147
  context "when gallery is left blank" do
148
- let(:uploaded_photos) { [] }
148
+ let(:uploaded_attachments) { [] }
149
149
 
150
150
  it "broadcasts ok" do
151
151
  expect { command.call }.to broadcast(:ok)
@@ -155,8 +155,8 @@ shared_examples "admin manages resource gallery" do
155
155
  context "when images are removed" do
156
156
  let!(:image1) { create(:attachment, :with_image, attached_to: resource) }
157
157
  let!(:image2) { create(:attachment, :with_image, attached_to: resource) }
158
- let(:uploaded_photos) { [] }
159
- let(:current_photos) { [image1] }
158
+ let(:uploaded_attachments) { [] }
159
+ let(:current_attachments) { [image1.id] }
160
160
 
161
161
  it "to decrease the number of photos in the gallery" do
162
162
  expect(resource.attachments.count).to eq(2)
@@ -1000,16 +1000,16 @@ shared_examples "comments" do
1000
1000
  let(:content) { "A valid user mention: @#{mentioned_user.nickname}" }
1001
1001
 
1002
1002
  context "when text finish with a mention" do
1003
- it "shows the tribute container" do
1004
- expect(page).to have_css(".tribute-container", text: mentioned_user.name, wait: 10)
1003
+ it "shows the suggestions menu" do
1004
+ expect(page).to have_css(".editor-suggestions-item", text: "@#{mentioned_user.nickname} (#{mentioned_user.name})", wait: 10)
1005
1005
  end
1006
1006
  end
1007
1007
 
1008
1008
  context "when text contains a mention" do
1009
1009
  let(:content) { "A valid user mention: @#{mentioned_user.nickname}." }
1010
1010
 
1011
- it "shows the tribute container" do
1012
- expect(page).to have_no_css(".tribute-container", text: mentioned_user.name)
1011
+ it "hides the suggestions menu" do
1012
+ expect(page).to have_no_css(".editor-suggestions-item", text: "@#{mentioned_user.nickname} (#{mentioned_user.name})")
1013
1013
  end
1014
1014
  end
1015
1015
  end
@@ -1018,8 +1018,8 @@ shared_examples "comments" do
1018
1018
  let!(:mentioned_user) { create(:user, organization:) }
1019
1019
  let(:content) { "A unconfirmed user mention: @#{mentioned_user.nickname}" }
1020
1020
 
1021
- it "do not show the tribute container" do
1022
- expect(page).to have_no_css(".tribute-container", text: mentioned_user.name)
1021
+ it "does not show the suggestions menu" do
1022
+ expect(page).to have_no_css(".editor-suggestions-item", text: "@#{mentioned_user.nickname} (#{mentioned_user.name})")
1023
1023
  end
1024
1024
  end
1025
1025
  end
@@ -4,7 +4,7 @@ module Decidim
4
4
  # This holds the decidim-core version.
5
5
  module Core
6
6
  def self.version
7
- "0.31.5"
7
+ "0.31.6"
8
8
  end
9
9
  end
10
10
  end
@@ -37,19 +37,20 @@ module Decidim
37
37
  template.snippets.add(:decidim_geocoding_scripts, template.append_javascript_pack_tag("decidim_geocoding"))
38
38
  template.snippets.add(:decidim_geocoding_styles, template.append_stylesheet_pack_tag("decidim_geocoding"))
39
39
 
40
- template.content_tag(:div, class: "geocoding-container") do
40
+ template.content_tag(:div, class: "geocoding") do
41
41
  template.text_field(
42
42
  object_name,
43
43
  method,
44
44
  options.merge("data-decidim-geocoding" => view_options.to_json)
45
45
  ) +
46
- template.content_tag(:div, class: "input-group-button user-device-location") do
47
- template.content_tag(:button, class: "button button__sm md:button__sm button__text-secondary", type: "button", data: {
46
+ template.content_tag(:div, class: "input-group-button geocoding__locate") do
47
+ template.content_tag(:button, class: "button button__sm md:button__sm button__text-secondary geocoding__button", type: "button", data: {
48
48
  input: "#{object_name}_#{method}",
49
49
  latitude: "#{object_name}_latitude",
50
50
  longitude: "#{object_name}_longitude",
51
51
  error_no_location: I18n.t("errors.no_device_location", scope: "decidim.proposals.forms"),
52
52
  error_unsupported: I18n.t("errors.device_not_supported", scope: "decidim.proposals.forms"),
53
+ locating_text: I18n.t("locating", scope: "decidim.proposals.forms"),
53
54
  url: Decidim::Core::Engine.routes.url_helpers.locate_path
54
55
  }) do
55
56
  icon("map-pin-line", role: "img", "aria-hidden": true) + " #{I18n.t("use_my_location", scope: "decidim.proposals.forms")}"
@@ -117,6 +117,11 @@ module Decidim
117
117
  searchables_in_org.find_each do |sr|
118
118
  next if sr.blank?
119
119
 
120
+ unless sr.locale.in?(org.available_locales)
121
+ sr.destroy
122
+ next
123
+ end
124
+
120
125
  sr.update(contents_to_searchable_resource_attributes(fields, sr.locale))
121
126
  end
122
127
  end
@@ -79,7 +79,7 @@ module Decidim
79
79
  end
80
80
 
81
81
  def perform_caching?
82
- cache_hash.present?
82
+ super && cache_hash.present?
83
83
  end
84
84
 
85
85
  def cache_hash
@@ -4,20 +4,42 @@ namespace :decidim do
4
4
  namespace :mailers do
5
5
  desc "Sends the notification digest email with the daily report"
6
6
  task notifications_digest_daily: :environment do
7
- notifications_digest(:daily)
7
+ time = Time.now.utc
8
+ target_users = Decidim::User.where(notifications_sending_frequency: :daily)
9
+
10
+ target_users.find_in_batches do |batch|
11
+ notification_users =
12
+ Decidim::Notification
13
+ .daily(time)
14
+ .where(decidim_user_id: batch.pluck(:id))
15
+ .select(:decidim_user_id)
16
+ .distinct
17
+ .pluck(:decidim_user_id)
18
+
19
+ notification_users.each do |user_id|
20
+ Decidim::EmailNotificationsDigestGeneratorJob.perform_later(user_id, :daily, time:)
21
+ end
22
+ end
8
23
  end
9
24
 
10
25
  desc "Sends the notification digest email with the weekly report"
11
26
  task notifications_digest_weekly: :environment do
12
- notifications_digest(:weekly)
13
- end
14
- end
27
+ time = Time.now.utc
28
+ target_users = Decidim::User.where(notifications_sending_frequency: :weekly)
29
+
30
+ target_users.find_in_batches do |batch|
31
+ notification_users =
32
+ Decidim::Notification
33
+ .weekly(time)
34
+ .where(decidim_user_id: batch.pluck(:id))
35
+ .select(:decidim_user_id)
36
+ .distinct
37
+ .pluck(:decidim_user_id)
15
38
 
16
- def notifications_digest(frequency)
17
- target_users = Decidim::User.where(notifications_sending_frequency: frequency)
18
- time = Time.now.utc
19
- target_users.find_each do |user|
20
- Decidim::EmailNotificationsDigestGeneratorJob.perform_later(user.id, frequency, time:)
39
+ notification_users.each do |user_id|
40
+ Decidim::EmailNotificationsDigestGeneratorJob.perform_later(user_id, :weekly, time:)
41
+ end
42
+ end
21
43
  end
22
44
  end
23
45
  end