decidim-core 0.31.5 → 0.31.7
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/amendable/amend_button_card/show.erb +2 -2
- data/app/cells/decidim/author_cell.rb +0 -4
- data/app/cells/decidim/card_metadata_cell.rb +1 -1
- data/app/cells/decidim/comments_button_cell.rb +9 -1
- data/app/cells/decidim/content_blocks/cta_settings_form/show.erb +1 -1
- data/app/cells/decidim/content_blocks/hero_settings_form/show.erb +2 -2
- data/app/cells/decidim/content_blocks/highlighted_content_banner_cell.rb +1 -1
- data/app/cells/decidim/content_blocks/highlighted_content_banner_settings_form/show.erb +1 -1
- data/app/cells/decidim/content_blocks/highlighted_participatory_spaces_cell.rb +1 -1
- data/app/cells/decidim/content_blocks/participatory_space_hero_settings_form/show.erb +1 -1
- data/app/cells/decidim/content_blocks/static_page/section_settings_form/show.erb +3 -1
- data/app/cells/decidim/content_blocks/static_page/summary_settings_form/show.erb +3 -1
- data/app/cells/decidim/content_blocks/static_page/two_pane_section_settings_form/show.erb +4 -2
- data/app/cells/decidim/data_consent/category.erb +20 -33
- data/app/cells/decidim/footer_topics/show.erb +5 -8
- data/app/cells/decidim/participatory_space_dropdown_metadata/links.erb +1 -1
- data/app/cells/decidim/resource_types_filter/show.erb +2 -2
- data/app/cells/decidim/share_widget/qr_code_modal.erb +1 -1
- data/app/commands/decidim/destroy_account.rb +12 -1
- data/app/commands/decidim/multiple_attachments_methods.rb +28 -27
- data/app/controllers/concerns/decidim/devise_controllers.rb +10 -0
- data/app/events/decidim/amendable/amendment_base_event.rb +7 -1
- data/app/helpers/decidim/menu_helper.rb +0 -2
- data/app/jobs/decidim/process_inactive_participant_job.rb +0 -7
- data/app/mailers/decidim/delete_user_mailer.rb +14 -0
- data/app/mailers/decidim/participants_account_mailer.rb +0 -16
- data/app/models/decidim/moderation.rb +1 -1
- data/app/models/decidim/participatory_space_private_user.rb +1 -1
- data/app/models/decidim/user.rb +2 -2
- data/app/models/decidim/user_base_entity.rb +17 -2
- data/app/models/decidim/user_moderation.rb +1 -1
- data/app/packs/src/decidim/controllers/accordion/accordion.test.js +196 -42
- data/app/packs/src/decidim/controllers/accordion/controller.js +93 -17
- data/app/packs/src/decidim/controllers/dropdown/controller.js +6 -0
- data/app/packs/src/decidim/controllers/dropdown/dropdown.test.js +28 -0
- data/app/packs/src/decidim/controllers/form_validator/form_validator.js +6 -6
- data/app/packs/src/decidim/controllers/form_validator/form_validator.test.js +23 -1
- data/app/packs/src/decidim/controllers/mention/controller.js +296 -140
- data/app/packs/src/decidim/controllers/mention/input_mentions.test.js +120 -457
- data/app/packs/src/decidim/controllers/multiple_mentions/controller.js +68 -32
- data/app/packs/src/decidim/controllers/multiple_mentions/input_multiple_mentions.test.js +30 -23
- data/app/packs/src/decidim/controllers/password_toggler/controller.js +22 -1
- data/app/packs/src/decidim/controllers/password_toggler/password_toggler.test.js +11 -20
- data/app/packs/src/decidim/direct_uploads/upload_field.js +4 -4
- data/app/packs/src/decidim/direct_uploads/upload_modal.js +12 -7
- data/app/packs/src/decidim/geocoding/reverse_geocoding.js +15 -5
- data/app/packs/src/decidim/geocoding/reverse_geocoding.test.js +197 -0
- data/app/packs/src/decidim/index.js +4 -3
- data/app/packs/src/decidim/map/controller.js +10 -1
- data/app/packs/src/decidim/refactor/moved/focus_guard.js +12 -12
- data/app/packs/src/decidim/refactor/moved/focus_guard.test.js +136 -0
- data/app/packs/src/decidim/sw/sw.js +1 -1
- data/app/packs/src/decidim/utilities/text.js +6 -6
- data/app/packs/stylesheets/decidim/_cards.scss +1 -1
- data/app/packs/stylesheets/decidim/_conversations.scss +14 -0
- data/app/packs/stylesheets/decidim/_cookies.scss +13 -5
- data/app/packs/stylesheets/decidim/_documents.scss +1 -1
- data/app/packs/stylesheets/decidim/_editor_suggestions.scss +49 -0
- data/app/packs/stylesheets/decidim/_floating_help.scss +1 -1
- data/app/packs/stylesheets/decidim/_footer.scss +33 -1
- data/app/packs/stylesheets/decidim/_participatory_spaces.scss +1 -1
- data/app/packs/stylesheets/decidim/_tom_select.scss +23 -0
- data/app/packs/stylesheets/decidim/application.scss +2 -0
- data/app/packs/stylesheets/decidim/editor.scss +2 -33
- data/app/packs/stylesheets/decidim/geocoding_addons.scss +10 -2
- data/app/presenters/decidim/footer_menu_presenter.rb +2 -4
- data/app/presenters/decidim/menu_item_presenter.rb +9 -3
- data/app/presenters/decidim/stats_presenter.rb +1 -1
- data/app/resolvers/decidim/meta_image_url_resolver.rb +7 -0
- data/app/uploaders/decidim/image_uploader.rb +1 -1
- data/app/views/decidim/delete_user_mailer/delete.html.erb +6 -0
- data/app/views/decidim/gamification/badges/index.html.erb +1 -1
- data/app/views/decidim/homepage/show.html.erb +1 -1
- data/app/views/decidim/last_activities/index.html.erb +1 -1
- data/app/views/decidim/messaging/conversations/_error_modal.html.erb +11 -19
- data/app/views/decidim/messaging/conversations/error.js.erb +12 -7
- data/app/views/decidim/newsletters/unsubscribe.html.erb +1 -1
- data/app/views/decidim/offline/show.html.erb +1 -1
- data/app/views/decidim/pages/index.html.erb +1 -1
- data/app/views/decidim/profiles/show.html.erb +1 -1
- data/app/views/decidim/searches/_filters.html.erb +1 -1
- data/app/views/decidim/shared/_resource_actions.html.erb +4 -4
- data/app/views/decidim/user_activities/index.html.erb +1 -1
- data/app/views/layouts/decidim/_wrapper.html.erb +2 -2
- data/app/views/layouts/decidim/footer/_main.html.erb +1 -1
- data/app/views/layouts/decidim/footer/_main_links.html.erb +56 -28
- data/app/views/layouts/decidim/header/_menu_breadcrumb_mobile_tablet.html.erb +1 -1
- data/app/views/layouts/decidim/shared/_layout_center.html.erb +1 -1
- data/app/views/layouts/decidim/shared/_layout_item.html.erb +1 -1
- data/app/views/layouts/decidim/shared/_layout_two_col.html.erb +1 -1
- data/config/locales/ar.yml +0 -25
- data/config/locales/bg.yml +0 -25
- data/config/locales/ca-IT.yml +37 -33
- data/config/locales/ca.yml +37 -33
- data/config/locales/cs.yml +12 -31
- data/config/locales/de.yml +5 -42
- data/config/locales/el.yml +0 -22
- data/config/locales/en.yml +36 -32
- data/config/locales/es-MX.yml +36 -32
- data/config/locales/es-PY.yml +36 -32
- data/config/locales/es.yml +36 -32
- data/config/locales/eu.yml +72 -70
- data/config/locales/fi-plain.yml +37 -32
- data/config/locales/fi.yml +37 -32
- data/config/locales/fr-CA.yml +16 -28
- data/config/locales/fr.yml +16 -28
- data/config/locales/gl.yml +0 -22
- data/config/locales/hu.yml +0 -22
- data/config/locales/id-ID.yml +0 -21
- data/config/locales/is-IS.yml +0 -6
- data/config/locales/it.yml +1 -20
- data/config/locales/ja.yml +52 -44
- data/config/locales/lb.yml +0 -23
- data/config/locales/lt.yml +0 -24
- data/config/locales/lv.yml +0 -21
- data/config/locales/nl.yml +0 -23
- data/config/locales/no.yml +0 -23
- data/config/locales/pl.yml +4 -27
- data/config/locales/pt-BR.yml +2 -40
- data/config/locales/pt.yml +0 -23
- data/config/locales/ro-RO.yml +1 -32
- data/config/locales/ru.yml +0 -8
- data/config/locales/sk.yml +1 -39
- data/config/locales/sv.yml +6 -36
- data/config/locales/tr-TR.yml +0 -24
- data/config/locales/uk.yml +0 -6
- data/config/locales/zh-CN.yml +0 -21
- data/config/locales/zh-TW.yml +0 -24
- data/db/migrate/20250217192438_convert_user_groups_into_users.rb +3 -2
- data/decidim-core.gemspec +3 -0
- data/lib/decidim/api/functions/user_entity_list.rb +2 -0
- data/lib/decidim/attachment_attributes.rb +58 -9
- data/lib/decidim/command.rb +1 -1
- data/lib/decidim/content_renderers/base_renderer.rb +112 -0
- data/lib/decidim/content_renderers/blob_renderer.rb +4 -7
- data/lib/decidim/content_renderers/mention_resource_renderer.rb +10 -6
- data/lib/decidim/content_renderers/resource_renderer.rb +16 -7
- data/lib/decidim/content_renderers/user_renderer.rb +11 -9
- data/lib/decidim/core/content_blocks/registry_manager.rb +4 -4
- data/lib/decidim/core/engine.rb +8 -0
- data/lib/decidim/core/test/factories.rb +3 -0
- data/lib/decidim/core/test/shared_examples/admin_resource_gallery_examples.rb +10 -10
- data/lib/decidim/core/test/shared_examples/amendable/amendment_event_examples.rb +2 -2
- data/lib/decidim/core/test/shared_examples/amendable/amendment_promoted_event_examples.rb +3 -3
- data/lib/decidim/core/test/shared_examples/comments_examples.rb +32 -6
- data/lib/decidim/core/test/shared_examples/has_space_in_mcell_examples.rb +2 -1
- data/lib/decidim/core/test/shared_examples/resource_liked_event_examples.rb +3 -3
- data/lib/decidim/core/version.rb +1 -1
- data/lib/decidim/core.rb +1 -1
- data/lib/decidim/map/autocomplete.rb +4 -3
- data/lib/decidim/map/dynamic_map.rb +3 -1
- data/lib/decidim/searchable.rb +5 -0
- data/lib/decidim/view_model.rb +1 -1
- data/lib/tasks/decidim_mailers_tasks.rake +31 -9
- metadata +32 -10
- data/app/commands/decidim/gallery_methods.rb +0 -107
- data/app/packs/src/decidim/vendor/tribute.js +0 -1890
- data/app/packs/stylesheets/decidim/_tribute.scss +0 -36
- data/app/views/decidim/participants_account_mailer/removal_notification.html.erb +0 -11
|
@@ -33,8 +33,8 @@ class ConvertUserGroupsIntoUsers < ActiveRecord::Migration[7.0]
|
|
|
33
33
|
def up
|
|
34
34
|
User.old_group.find_each do |group|
|
|
35
35
|
if group.email.blank? || another_user_with_same_email_in_organization?(group)
|
|
36
|
-
group.update_attribute(:email, "user_group_#{group.id}@#{group.organization.host}.invalid")
|
|
37
36
|
group.update_attribute(:extended_data, (group.extended_data || {}).merge("patched" => true, "previous_email" => group.email))
|
|
37
|
+
group.update_attribute(:email, "user_group_#{group.id}@#{group.organization.host}.invalid")
|
|
38
38
|
|
|
39
39
|
group.reload
|
|
40
40
|
end
|
|
@@ -49,7 +49,8 @@ class ConvertUserGroupsIntoUsers < ActiveRecord::Migration[7.0]
|
|
|
49
49
|
User.new_group.find_each do |group|
|
|
50
50
|
group.update_attribute(:officialized_at, nil)
|
|
51
51
|
group.update_attribute(:type, "Decidim::UserGroup")
|
|
52
|
-
group.update_attribute(:
|
|
52
|
+
group.update_attribute(:email, group.extended_data["previous_email"]) if group.extended_data["previous_email"].present?
|
|
53
|
+
group.update_attribute(:extended_data, (group.extended_data || {}).except("group", "patched", "previous_email"))
|
|
53
54
|
end
|
|
54
55
|
end
|
|
55
56
|
# rubocop:enable Rails/SkipsModelValidations
|
data/decidim-core.gemspec
CHANGED
|
@@ -74,6 +74,9 @@ Gem::Specification.new do |s|
|
|
|
74
74
|
s.add_dependency "premailer-rails", "~> 1.10"
|
|
75
75
|
s.add_dependency "rack", "~> 2.2", ">= 2.2.8.1"
|
|
76
76
|
s.add_dependency "rack-attack", "~> 6.0"
|
|
77
|
+
# Workaround until shakapacker releases a new version compatible with rack-proxy v1.0
|
|
78
|
+
# @see https://github.com/shakacode/shakapacker/issues/1220
|
|
79
|
+
s.add_dependency "rack-proxy", "~> 0.8.3", "< 1.0"
|
|
77
80
|
s.add_dependency "rails", "~> 7.2.0", ">= 7.2.2.2"
|
|
78
81
|
s.add_dependency "rails-i18n", "~> 7.0"
|
|
79
82
|
s.add_dependency "ransack", "~> 4.2.0"
|
|
@@ -11,6 +11,7 @@ module Decidim
|
|
|
11
11
|
#
|
|
12
12
|
class UserEntityList
|
|
13
13
|
include NeedsApiFilterAndOrder
|
|
14
|
+
include NeedsApiDefaultOrder
|
|
14
15
|
|
|
15
16
|
def initialize
|
|
16
17
|
@model_class = Decidim::UserBaseEntity
|
|
@@ -24,6 +25,7 @@ module Decidim
|
|
|
24
25
|
.includes(avatar_attachment: :blob)
|
|
25
26
|
add_filter_keys(args[:filter])
|
|
26
27
|
add_order_keys(args[:order].to_h)
|
|
28
|
+
add_default_order
|
|
27
29
|
@query
|
|
28
30
|
end
|
|
29
31
|
end
|
|
@@ -15,24 +15,39 @@ module Decidim
|
|
|
15
15
|
#
|
|
16
16
|
# name - The attribute's name
|
|
17
17
|
#
|
|
18
|
-
#
|
|
19
18
|
# Example:
|
|
20
19
|
#
|
|
21
|
-
#
|
|
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
|
|
26
|
-
# #
|
|
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
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
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
|
-
|
|
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
|
data/lib/decidim/command.rb
CHANGED
|
@@ -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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
render_editor(
|
|
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(
|
|
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!
|
data/lib/decidim/core/engine.rb
CHANGED
|
@@ -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(:
|
|
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(:
|
|
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(
|
|
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(:
|
|
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(:
|
|
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(:
|
|
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(
|
|
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(:
|
|
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(:
|
|
159
|
-
let(:
|
|
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)
|
|
@@ -8,7 +8,7 @@ shared_examples "amendment event" do
|
|
|
8
8
|
it_behaves_like "a simple event email"
|
|
9
9
|
it_behaves_like "a simple event notification"
|
|
10
10
|
|
|
11
|
-
let(:
|
|
11
|
+
let(:emendation_author_name) { emendation.creator_author.name }
|
|
12
12
|
let(:emendation_path) { Decidim::ResourceLocatorPresenter.new(emendation).path }
|
|
13
13
|
let(:emendation_author_path) { Decidim::UserPresenter.new(emendation.creator_author).profile_path }
|
|
14
14
|
let(:amendable_path) { Decidim::ResourceLocatorPresenter.new(amendable).path }
|
|
@@ -16,5 +16,5 @@ shared_examples "amendment event" do
|
|
|
16
16
|
let(:email_intro) { "An amendment has been #{amendment_type} for #{amendable_title}. You can see it from this page:" }
|
|
17
17
|
let(:email_outro) { "You have received this notification because you are following #{amendable_title}. You can stop receiving notifications following the previous link." }
|
|
18
18
|
|
|
19
|
-
let(:notification_title) { "The <a href=\"#{emendation_path}\">amendment</a> created by <a href=\"#{emendation_author_path}\">#{
|
|
19
|
+
let(:notification_title) { "The <a href=\"#{emendation_path}\">amendment</a> created by <a href=\"#{emendation_author_path}\">#{emendation_author_name}</a> has been #{amendment_type} for <a href=\"#{amendable_path}\">#{amendable_title}</a>." } # rubocop:disable Layout/LineLength
|
|
20
20
|
end
|
|
@@ -10,15 +10,15 @@ shared_examples "amendment promoted event" do
|
|
|
10
10
|
it_behaves_like "a simple event email"
|
|
11
11
|
it_behaves_like "a simple event notification"
|
|
12
12
|
|
|
13
|
-
let(:
|
|
13
|
+
let(:emendation_author_name) { emendation.creator_author.name }
|
|
14
14
|
let(:emendation_path) { Decidim::ResourceLocatorPresenter.new(emendation).path }
|
|
15
15
|
let(:emendation_author_path) { Decidim::UserPresenter.new(emendation.creator_author).profile_path }
|
|
16
16
|
let(:amendable_path) { Decidim::ResourceLocatorPresenter.new(amendable).path }
|
|
17
17
|
|
|
18
|
-
let(:email_subject) { "An amendment from #{
|
|
18
|
+
let(:email_subject) { "An amendment from #{emendation_author_name} has been published as a new proposal" }
|
|
19
19
|
let(:email_intro) { "A rejected amendment for #{amendable_title} has been published as a new #{amendable_type}. You can see it from this page:" }
|
|
20
20
|
let(:email_outro) { "You have received this notification because you are following #{amendable_title}. You can stop receiving notifications following the previous link." }
|
|
21
21
|
# rubocop:disable Layout/LineLength
|
|
22
|
-
let(:notification_title) { "A <a href=\"#{emendation_path}\">rejected amendment</a> for <a href=\"#{amendable_path}\">#{amendable_title}</a> has been published as a new #{amendable_type} by <a href=\"#{emendation_author_path}\">#{
|
|
22
|
+
let(:notification_title) { "A <a href=\"#{emendation_path}\">rejected amendment</a> for <a href=\"#{amendable_path}\">#{amendable_title}</a> has been published as a new #{amendable_type} by <a href=\"#{emendation_author_path}\">#{emendation_author_name}</a>." }
|
|
23
23
|
# rubocop:enable Layout/LineLength
|
|
24
24
|
end
|
|
@@ -147,6 +147,16 @@ shared_examples "comments" do
|
|
|
147
147
|
expect(page).to have_css(".comment-thread")
|
|
148
148
|
end
|
|
149
149
|
|
|
150
|
+
it "opens the login modal when clicking the comment button" do
|
|
151
|
+
visit resource_path
|
|
152
|
+
|
|
153
|
+
expect(page).to have_no_css("#loginModal-content", visible: :visible)
|
|
154
|
+
|
|
155
|
+
find(".add-comment-mobile").click
|
|
156
|
+
|
|
157
|
+
expect(page).to have_css("#loginModal-content", visible: :visible)
|
|
158
|
+
end
|
|
159
|
+
|
|
150
160
|
context "when user visit a mobile browser" do
|
|
151
161
|
before do
|
|
152
162
|
driven_by(:iphone)
|
|
@@ -176,6 +186,22 @@ shared_examples "comments" do
|
|
|
176
186
|
expect(page).to have_css(".add-comment form")
|
|
177
187
|
end
|
|
178
188
|
|
|
189
|
+
it "does not show login modal when clicking the comment button" do
|
|
190
|
+
visit resource_path
|
|
191
|
+
|
|
192
|
+
find("a.add-comment-mobile").click
|
|
193
|
+
|
|
194
|
+
expect(page).to have_no_css("#loginModal-content", visible: :visible)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
it "guides the user to the comment form when clicking the comment button" do
|
|
198
|
+
visit resource_path
|
|
199
|
+
|
|
200
|
+
find("a.add-comment-mobile").click
|
|
201
|
+
|
|
202
|
+
expect(page).to have_css("#add-comment-anchor")
|
|
203
|
+
end
|
|
204
|
+
|
|
179
205
|
context "when user visit a computer browser" do
|
|
180
206
|
before do
|
|
181
207
|
switch_to_host(organization.host)
|
|
@@ -1000,16 +1026,16 @@ shared_examples "comments" do
|
|
|
1000
1026
|
let(:content) { "A valid user mention: @#{mentioned_user.nickname}" }
|
|
1001
1027
|
|
|
1002
1028
|
context "when text finish with a mention" do
|
|
1003
|
-
it "shows the
|
|
1004
|
-
expect(page).to have_css(".
|
|
1029
|
+
it "shows the suggestions menu" do
|
|
1030
|
+
expect(page).to have_css(".editor-suggestions-item", text: "@#{mentioned_user.nickname} (#{mentioned_user.name})", wait: 10)
|
|
1005
1031
|
end
|
|
1006
1032
|
end
|
|
1007
1033
|
|
|
1008
1034
|
context "when text contains a mention" do
|
|
1009
1035
|
let(:content) { "A valid user mention: @#{mentioned_user.nickname}." }
|
|
1010
1036
|
|
|
1011
|
-
it "
|
|
1012
|
-
expect(page).to have_no_css(".
|
|
1037
|
+
it "hides the suggestions menu" do
|
|
1038
|
+
expect(page).to have_no_css(".editor-suggestions-item", text: "@#{mentioned_user.nickname} (#{mentioned_user.name})")
|
|
1013
1039
|
end
|
|
1014
1040
|
end
|
|
1015
1041
|
end
|
|
@@ -1018,8 +1044,8 @@ shared_examples "comments" do
|
|
|
1018
1044
|
let!(:mentioned_user) { create(:user, organization:) }
|
|
1019
1045
|
let(:content) { "A unconfirmed user mention: @#{mentioned_user.nickname}" }
|
|
1020
1046
|
|
|
1021
|
-
it "
|
|
1022
|
-
expect(page).to have_no_css(".
|
|
1047
|
+
it "does not show the suggestions menu" do
|
|
1048
|
+
expect(page).to have_no_css(".editor-suggestions-item", text: "@#{mentioned_user.nickname} (#{mentioned_user.name})")
|
|
1023
1049
|
end
|
|
1024
1050
|
end
|
|
1025
1051
|
end
|
|
@@ -8,7 +8,8 @@ shared_examples_for "has space in m-cell" do
|
|
|
8
8
|
let(:show_space) { true }
|
|
9
9
|
|
|
10
10
|
it "renders the space where the model belongs to" do
|
|
11
|
-
expect(cell_html).to have_content(
|
|
11
|
+
expect(cell_html).to have_content(translated_attribute(model.component.participatory_space.title))
|
|
12
|
+
expect(cell_html.to_s).to include(decidim_escape_translated(model.component.participatory_space.title).gsub(""", "\""))
|
|
12
13
|
end
|
|
13
14
|
end
|
|
14
15
|
end
|