decidim-core 0.27.1 → 0.27.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of decidim-core might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/app/cells/decidim/newsletter_templates/base_cell.rb +8 -0
- data/app/cells/decidim/newsletter_templates/basic_only_text/show.erb +4 -4
- data/app/cells/decidim/newsletter_templates/image_text_cta/show.erb +4 -4
- data/app/cells/decidim/upload_modal/files.erb +1 -0
- data/app/cells/decidim/upload_modal_cell.rb +26 -11
- data/app/commands/decidim/attachment_methods.rb +20 -2
- data/app/commands/decidim/create_registration.rb +1 -0
- data/app/commands/decidim/gallery_methods.rb +1 -1
- data/app/commands/decidim/unendorse_resource.rb +1 -1
- data/app/commands/decidim/update_account.rb +1 -0
- data/app/commands/decidim/update_password.rb +2 -0
- data/app/controllers/decidim/devise/sessions_controller.rb +18 -2
- data/app/controllers/decidim/groups_controller.rb +5 -0
- data/app/controllers/decidim/links_controller.rb +10 -11
- data/app/controllers/decidim/profiles_controller.rb +1 -1
- data/app/helpers/decidim/cells_helper.rb +1 -0
- data/app/helpers/decidim/external_domain_helper.rb +14 -3
- data/app/helpers/decidim/icon_helper.rb +3 -3
- data/app/helpers/decidim/newsletters_helper.rb +1 -0
- data/app/helpers/decidim/sanitize_helper.rb +3 -2
- data/app/mailers/decidim/newsletter_mailer.rb +10 -3
- data/app/models/decidim/newsletter.rb +28 -0
- data/app/models/decidim/scope_type.rb +24 -0
- data/app/models/decidim/user.rb +0 -2
- data/app/models/decidim/user_base_entity.rb +2 -0
- data/app/models/decidim/user_block.rb +2 -2
- data/app/models/decidim/user_group.rb +1 -1
- data/app/packs/src/decidim/direct_uploads/upload_modal.js +0 -1
- data/app/packs/src/decidim/editor/clipboard_override.js +6 -2
- data/app/packs/src/decidim/editor.js +63 -33
- data/app/packs/src/decidim/form_filter.component.test.js +148 -5
- data/app/packs/src/decidim/form_filter.js +26 -4
- data/app/packs/stylesheets/decidim/email.scss +7 -0
- data/app/packs/stylesheets/decidim/modules/_buttons.scss +10 -6
- data/app/packs/stylesheets/decidim/modules/_cards.scss +1 -1
- data/app/packs/stylesheets/decidim/modules/_comments.scss +24 -0
- data/app/packs/stylesheets/decidim/modules/_input-gallery.scss +2 -1
- data/app/packs/stylesheets/decidim/modules/_upload_modal.scss +0 -4
- data/app/packs/stylesheets/decidim/vizzs/_linechart.scss +2 -2
- data/app/packs/stylesheets/decidim/vizzs/_rowchart.scss +2 -2
- data/app/presenters/decidim/admin_log/user_group_presenter.rb +1 -1
- data/app/presenters/decidim/admin_log/user_moderation_presenter.rb +1 -1
- data/app/presenters/decidim/notification_presenter.rb +1 -1
- data/app/presenters/decidim/notification_to_mailer_presenter.rb +1 -0
- data/app/presenters/decidim/push_notification_presenter.rb +1 -1
- data/app/presenters/decidim/user_group_presenter.rb +1 -1
- data/app/presenters/decidim/user_presenter.rb +1 -1
- data/app/scrubbers/decidim/admin_input_scrubber.rb +3 -1
- data/app/scrubbers/decidim/user_input_scrubber.rb +30 -1
- data/app/services/decidim/traceability.rb +1 -0
- data/app/uploaders/decidim/application_uploader.rb +1 -1
- data/app/uploaders/decidim/avatar_uploader.rb +2 -2
- data/app/validators/uploader_image_dimensions_validator.rb +22 -2
- data/app/views/decidim/links/_invalid_url_modal.html.erb +17 -0
- data/app/views/decidim/links/_modal.html.erb +1 -1
- data/app/views/decidim/links/invalid_url.js.erb +24 -0
- data/app/views/decidim/links/new.html.erb +1 -1
- data/app/views/decidim/messaging/conversations/_conversation.html.erb +2 -6
- data/app/views/decidim/newsletter_mailer/newsletter.html.erb +3 -3
- data/app/views/decidim/newsletters/show.html.erb +1 -1
- data/app/views/layouts/decidim/_mailer_logo.html.erb +2 -2
- data/app/views/layouts/decidim/newsletter_base.html.erb +2 -2
- data/config/locales/ar.yml +571 -7
- data/config/locales/bg.yml +6 -8
- data/config/locales/ca.yml +38 -30
- data/config/locales/cs.yml +28 -33
- data/config/locales/da.yml +4 -0
- data/config/locales/de.yml +4 -25
- data/config/locales/el.yml +6 -9
- data/config/locales/en.yml +22 -15
- data/config/locales/eo.yml +2 -1
- data/config/locales/es-MX.yml +30 -22
- data/config/locales/es-PY.yml +30 -22
- data/config/locales/es.yml +36 -28
- data/config/locales/et.yml +4 -0
- data/config/locales/eu.yml +173 -80
- data/config/locales/fa-IR.yml +1 -0
- data/config/locales/fi-plain.yml +7 -20
- data/config/locales/fi.yml +26 -18
- data/config/locales/fr-CA.yml +27 -19
- data/config/locales/fr.yml +25 -17
- data/config/locales/ga-IE.yml +1 -0
- data/config/locales/gl.yml +2 -25
- data/config/locales/gn-PY.yml +4 -0
- data/config/locales/hr.yml +4 -0
- data/config/locales/hu.yml +68 -28
- data/config/locales/id-ID.yml +7 -8
- data/config/locales/is-IS.yml +2 -2
- data/config/locales/it.yml +2 -10
- data/config/locales/ja.yml +30 -37
- data/config/locales/ka-GE.yml +5 -0
- data/config/locales/kaa.yml +1 -0
- data/config/locales/lb.yml +0 -8
- data/config/locales/lt.yml +0 -38
- data/config/locales/lv.yml +5 -7
- data/config/locales/nl.yml +1 -27
- data/config/locales/no.yml +3 -29
- data/config/locales/oc-FR.yml +3 -0
- data/config/locales/pl.yml +4 -39
- data/config/locales/pt-BR.yml +2 -10
- data/config/locales/pt.yml +0 -8
- data/config/locales/ro-RO.yml +85 -7
- data/config/locales/ru.yml +6 -4
- data/config/locales/sk.yml +8 -9
- data/config/locales/sl.yml +1 -0
- data/config/locales/sr-CS.yml +2 -0
- data/config/locales/sv.yml +23 -28
- data/config/locales/tr-TR.yml +7 -12
- data/config/locales/uk.yml +6 -4
- data/config/locales/zh-CN.yml +3 -8
- data/config/locales/zh-TW.yml +1872 -0
- data/lib/decidim/api/types/localized_string_type.rb +9 -0
- data/lib/decidim/api/types/translated_field_type.rb +20 -5
- data/lib/decidim/asset_router/pipeline.rb +95 -0
- data/lib/decidim/asset_router/storage.rb +82 -0
- data/lib/decidim/asset_router.rb +3 -75
- data/lib/decidim/attribute_object/form.rb +9 -0
- data/lib/decidim/core/test/factories.rb +13 -6
- data/lib/decidim/core/test/shared_examples/comments_examples.rb +36 -0
- data/lib/decidim/core/test/shared_examples/digest_mail_examples.rb +33 -0
- data/lib/decidim/core/test/shared_examples/editor_shared_examples.rb +5 -4
- data/lib/decidim/core/test/shared_examples/rich_text_editor_examples.rb +7 -3
- data/lib/decidim/core/test.rb +1 -0
- data/lib/decidim/core/version.rb +1 -1
- data/lib/decidim/dependency_resolver.rb +14 -8
- data/lib/decidim/form_builder.rb +5 -4
- data/lib/decidim/participatory_space_resourceable.rb +7 -1
- data/lib/decidim/publicable.rb +4 -0
- metadata +14 -6
@@ -8,6 +8,15 @@ module Decidim
|
|
8
8
|
|
9
9
|
field :locale, GraphQL::Types::String, "The standard locale of this translation.", null: false
|
10
10
|
field :text, GraphQL::Types::String, "The content of this translation.", null: true
|
11
|
+
field :machine_translated, GraphQL::Types::Boolean, "Whether this string is machine translated or not.", null: false
|
12
|
+
|
13
|
+
def machine_translated
|
14
|
+
if object.respond_to?(:machine_translated)
|
15
|
+
object.machine_translated.present?
|
16
|
+
else
|
17
|
+
false
|
18
|
+
end
|
19
|
+
end
|
11
20
|
end
|
12
21
|
end
|
13
22
|
end
|
@@ -17,19 +17,34 @@ module Decidim
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def locales
|
20
|
-
|
20
|
+
(defined_translations.keys + machine_translations.keys).uniq
|
21
21
|
end
|
22
22
|
|
23
23
|
def translation(locale: "")
|
24
|
-
|
25
|
-
translations[locale]
|
24
|
+
display_translations[locale]
|
26
25
|
end
|
27
26
|
|
28
27
|
def translations(locales: [])
|
29
|
-
translations =
|
28
|
+
translations = display_translations
|
30
29
|
translations = translations.slice(*locales) unless locales.empty?
|
31
30
|
|
32
|
-
translations.map { |locale, text| OpenStruct.new(locale: locale, text: text) }
|
31
|
+
translations.map { |locale, text| OpenStruct.new(locale: locale, text: text, machine_translated: defined_translations[locale].blank?) }
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def display_translations
|
37
|
+
@display_translations ||= locales.index_with do |locale|
|
38
|
+
defined_translations[locale].presence || machine_translations[locale]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def defined_translations
|
43
|
+
object.stringify_keys.except("machine_translations")
|
44
|
+
end
|
45
|
+
|
46
|
+
def machine_translations
|
47
|
+
object.stringify_keys["machine_translations"]&.stringify_keys || {}
|
33
48
|
end
|
34
49
|
end
|
35
50
|
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module AssetRouter
|
5
|
+
# The pipeline asset router provides global access to the asset routers for
|
6
|
+
# assets that are precompiled through the Rails assets pipeline (or webpack)
|
7
|
+
# and stored locally or in a remote CDN. This handles the configuration
|
8
|
+
# options for the asset routes so that they don't have to be always manually
|
9
|
+
# created.
|
10
|
+
class Pipeline
|
11
|
+
# Initializes the router.
|
12
|
+
#
|
13
|
+
# @param asset [String] The asset to route to
|
14
|
+
# @param model [ActiveRecord::Base, nil] The model that provides the
|
15
|
+
# organizational context. When nil, the host will be included in the URL
|
16
|
+
# when it is available through other configurations. Otherwise, the host
|
17
|
+
# will not be part of the URL.
|
18
|
+
def initialize(asset, model: nil)
|
19
|
+
@asset = asset
|
20
|
+
@model = model
|
21
|
+
end
|
22
|
+
|
23
|
+
# Generates the correct URL to the asset with the provided options.
|
24
|
+
#
|
25
|
+
# @param options [Hash] The options for the URL that are the normal route
|
26
|
+
# options Rails route helpers accept
|
27
|
+
# @return [String] The full URL to the asset or when host cannot be
|
28
|
+
# resolved, the asset path.
|
29
|
+
def url(**options)
|
30
|
+
path = ActionController::Base.helpers.asset_pack_path(asset, **options)
|
31
|
+
return path if path.match?(%r{\A(https?:)?//})
|
32
|
+
|
33
|
+
"#{asset_host}#{path}"
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :asset, :model
|
39
|
+
|
40
|
+
# Fetches the organization from the model or returns the model itself if
|
41
|
+
# it is an organization.
|
42
|
+
#
|
43
|
+
# @return [Decidim::Organization]
|
44
|
+
def organization
|
45
|
+
@organization ||=
|
46
|
+
if model.is_a?(Decidim::Organization)
|
47
|
+
model
|
48
|
+
else
|
49
|
+
model.try(:organization)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Resolves the full asset host with the resolved options and also adds the
|
54
|
+
# port at the end of the URL unless it is the default port 80 or 443.
|
55
|
+
#
|
56
|
+
# @return [String] The hostname with protocol and port or an empty string
|
57
|
+
# when the host cannot be resolved.
|
58
|
+
def asset_host
|
59
|
+
return "" if default_options[:host].blank?
|
60
|
+
|
61
|
+
base_host = ActionController::Base.helpers.compute_asset_host("", **default_options)
|
62
|
+
return "" if base_host.blank?
|
63
|
+
return base_host if option_resolver.default_port?
|
64
|
+
|
65
|
+
"#{base_host}:#{option_resolver.port}"
|
66
|
+
end
|
67
|
+
|
68
|
+
# Determines the default options to be passed to the route helper.
|
69
|
+
#
|
70
|
+
# @return [Hash] The default options hash to pass to the route helper
|
71
|
+
def default_options
|
72
|
+
@default_options ||= option_resolver.options.tap do |opts|
|
73
|
+
opts[:host] = default_host if default_host
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Determines the default host for the pipeline assets. Either the
|
78
|
+
# configured assets host or the organization host when available. When the
|
79
|
+
# default host is not available, the host returned by the
|
80
|
+
# UrlOptionResolver will be used.
|
81
|
+
#
|
82
|
+
# @return [String, nil]
|
83
|
+
def default_host
|
84
|
+
@default_host ||= Rails.configuration.action_controller.asset_host || organization&.host
|
85
|
+
end
|
86
|
+
|
87
|
+
# Stores an instance of UrlOptionResolver for convenience.
|
88
|
+
#
|
89
|
+
# @return [Decidim::UrlOptionResolver]
|
90
|
+
def option_resolver
|
91
|
+
@option_resolver ||= UrlOptionResolver.new
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module AssetRouter
|
5
|
+
# Storage asset router provides global access to the asset routes for assets
|
6
|
+
# saved through ActiveStorage. This handles the different cases for routing
|
7
|
+
# to the remote routes when using an assets CDN or to local routes when
|
8
|
+
# using the local disk storage driver.
|
9
|
+
class Storage
|
10
|
+
# Initializes the router.
|
11
|
+
#
|
12
|
+
# @param [ActiveStorage::Attached, ActiveStorage::Blob] The asset to route
|
13
|
+
# to
|
14
|
+
def initialize(asset)
|
15
|
+
@asset = asset
|
16
|
+
end
|
17
|
+
|
18
|
+
# Generates the correct URL to the asset with the provided options.
|
19
|
+
#
|
20
|
+
# @param options The options for the URL that are the normal route options
|
21
|
+
# Rails route helpers accept
|
22
|
+
def url(**options)
|
23
|
+
if asset.is_a? ActiveStorage::Attached
|
24
|
+
routes.rails_blob_url(asset.blob, **default_options.merge(options))
|
25
|
+
else
|
26
|
+
routes.rails_representation_url(asset, **default_options.merge(options))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :asset
|
33
|
+
|
34
|
+
# Provides the route helpers depending on whether the URL is generated to
|
35
|
+
# the local host or an external CDN (remote).
|
36
|
+
#
|
37
|
+
# @return [Module, Decidim::EngineRouter] The correct route helpers based
|
38
|
+
# on the configuration
|
39
|
+
def routes
|
40
|
+
@routes ||=
|
41
|
+
if remote?
|
42
|
+
Rails.application.routes.url_helpers
|
43
|
+
else
|
44
|
+
EngineRouter.new("main_app", {})
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Determines whether the assets call should be to a remote CDN or to the
|
49
|
+
# local server based on the storage options.
|
50
|
+
#
|
51
|
+
# @return [Boolean] A boolean indicating whether the assets are served
|
52
|
+
# through a remote CDN
|
53
|
+
def remote?
|
54
|
+
remote_storage_options.present?
|
55
|
+
end
|
56
|
+
|
57
|
+
# Determines the default options to be passed to the route helper. For the
|
58
|
+
# remote storage, returns the remote storage options and for the local
|
59
|
+
# disk storage returns an empty hash.
|
60
|
+
#
|
61
|
+
# @return [Hash] The default options hash to pass to the route helper
|
62
|
+
def default_options
|
63
|
+
@default_options ||=
|
64
|
+
if remote?
|
65
|
+
remote_storage_options
|
66
|
+
else
|
67
|
+
{}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# The remote storage options when using a remote CDN. An empty hash in
|
72
|
+
# case using the local disk storage.
|
73
|
+
#
|
74
|
+
# @return [Hash] The remote storage options hash
|
75
|
+
def remote_storage_options
|
76
|
+
@remote_storage_options ||= {
|
77
|
+
host: Rails.application.secrets.dig(:storage, :cdn_host)
|
78
|
+
}.compact
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/decidim/asset_router.rb
CHANGED
@@ -1,80 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Decidim
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
# local disk storage driver.
|
8
|
-
class AssetRouter
|
9
|
-
# Initializes the router.
|
10
|
-
#
|
11
|
-
# @param [ActiveStorage::Attached, ActiveStorage::Blob] The asset to route
|
12
|
-
# to
|
13
|
-
def initialize(asset)
|
14
|
-
@asset = asset
|
15
|
-
end
|
16
|
-
|
17
|
-
# Generates the correct URL to the asset with the provided options.
|
18
|
-
#
|
19
|
-
# @param options The options for the URL that are the normal route options
|
20
|
-
# Rails route helpers accept
|
21
|
-
def url(**options)
|
22
|
-
if asset.is_a? ActiveStorage::Attached
|
23
|
-
routes.rails_blob_url(asset.blob, **default_options.merge(options))
|
24
|
-
else
|
25
|
-
routes.rails_representation_url(asset, **default_options.merge(options))
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
attr_reader :asset
|
32
|
-
|
33
|
-
# Provides the route helpers depending on whether the URL is generated to
|
34
|
-
# the local host or an external CDN (remote).
|
35
|
-
#
|
36
|
-
# @return [Module, Decidim::EngineRouter] The correct route helpers based
|
37
|
-
# on the configuration
|
38
|
-
def routes
|
39
|
-
@routes ||=
|
40
|
-
if remote?
|
41
|
-
Rails.application.routes.url_helpers
|
42
|
-
else
|
43
|
-
EngineRouter.new("main_app", {})
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
# Determines whether the assets call should be to a remote CDN or to the
|
48
|
-
# local server based on the storage options.
|
49
|
-
#
|
50
|
-
# @return [Boolean] A boolean indicating whether the assets are served
|
51
|
-
# through a remote CDN
|
52
|
-
def remote?
|
53
|
-
remote_storage_options.present?
|
54
|
-
end
|
55
|
-
|
56
|
-
# Determines the default options to be passed to the route helper. For the
|
57
|
-
# remote storage, returns the remote storage options and for the local disk
|
58
|
-
# storage returns an empty hash.
|
59
|
-
#
|
60
|
-
# @return [Hash] The default options hash to pass to the route helper
|
61
|
-
def default_options
|
62
|
-
@default_options ||=
|
63
|
-
if remote?
|
64
|
-
remote_storage_options
|
65
|
-
else
|
66
|
-
{}
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
# The remote storage options when using a remote CDN. An empty hash in case
|
71
|
-
# using the local disk storage.
|
72
|
-
#
|
73
|
-
# @return [Hash] The remote storage options hash
|
74
|
-
def remote_storage_options
|
75
|
-
@remote_storage_options ||= {
|
76
|
-
host: Rails.application.secrets.dig(:storage, :cdn_host)
|
77
|
-
}.compact
|
78
|
-
end
|
4
|
+
module AssetRouter
|
5
|
+
autoload :Pipeline, "decidim/asset_router/pipeline"
|
6
|
+
autoload :Storage, "decidim/asset_router/storage"
|
79
7
|
end
|
80
8
|
end
|
@@ -48,6 +48,15 @@ module Decidim
|
|
48
48
|
value.attachment.try(:blob)
|
49
49
|
when ActiveStorage::Attached::Many
|
50
50
|
value.attachments.map(&:blob)
|
51
|
+
when ActiveRecord::Associations::CollectionProxy, ActiveRecord::Relation, Array
|
52
|
+
if attribute_types[key].type == :array
|
53
|
+
value
|
54
|
+
else
|
55
|
+
# This is a sub-form that needs to read the properties directly
|
56
|
+
# from the original model. We cannot pass an array here as it
|
57
|
+
# would be passed to the form constructor causing an error.
|
58
|
+
model
|
59
|
+
end
|
51
60
|
else
|
52
61
|
value
|
53
62
|
end
|
@@ -234,13 +234,20 @@ FactoryBot.define do
|
|
234
234
|
confirmed_at { Time.current }
|
235
235
|
end
|
236
236
|
|
237
|
+
trait :blocked do
|
238
|
+
blocked { true }
|
239
|
+
blocked_at { Time.current }
|
240
|
+
extended_data { { user_name: generate(:name) } }
|
241
|
+
name { "Blocked user group" }
|
242
|
+
end
|
243
|
+
|
237
244
|
after(:build) do |user_group, evaluator|
|
238
|
-
user_group.extended_data = {
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
245
|
+
user_group.extended_data = user_group.extended_data.merge({
|
246
|
+
document_number: evaluator.document_number,
|
247
|
+
phone: evaluator.phone,
|
248
|
+
rejected_at: evaluator.rejected_at,
|
249
|
+
verified_at: evaluator.verified_at
|
250
|
+
})
|
244
251
|
end
|
245
252
|
|
246
253
|
after(:create) do |user_group, evaluator|
|
@@ -416,6 +416,42 @@ shared_examples "comments" do
|
|
416
416
|
)
|
417
417
|
end
|
418
418
|
|
419
|
+
context "when user can hide replies on a thread" do
|
420
|
+
let(:thread) { comments.first }
|
421
|
+
let(:new_reply_body) { "Hey, I just jumped inside the thread!" }
|
422
|
+
let!(:new_reply) { create(:comment, commentable: thread, root_commentable: commentable, body: new_reply_body) }
|
423
|
+
|
424
|
+
it "displays the hide button" do
|
425
|
+
visit current_path
|
426
|
+
within "#comment_#{thread.id}" do
|
427
|
+
expect(page).to have_content("Hide replies")
|
428
|
+
expect(page).to have_content(new_reply_body)
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
it "displays the show button" do
|
433
|
+
visit current_path
|
434
|
+
within "#comment_#{thread.id}" do
|
435
|
+
click_button "Hide replies"
|
436
|
+
expect(page).to have_content("Show reply")
|
437
|
+
expect(page).not_to have_content(new_reply_body)
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
context "when are more replies" do
|
442
|
+
let!(:new_replies) { create_list(:comment, 2, commentable: thread, root_commentable: commentable, body: new_reply_body) }
|
443
|
+
|
444
|
+
it "displays the show button" do
|
445
|
+
visit current_path
|
446
|
+
within "#comment_#{thread.id}" do
|
447
|
+
click_button "Hide replies"
|
448
|
+
expect(page).to have_content("Show 3 replies")
|
449
|
+
expect(page).not_to have_content(new_reply_body)
|
450
|
+
end
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
419
455
|
context "when inside a thread reply form" do
|
420
456
|
let(:thread) { comments.first }
|
421
457
|
let(:new_reply_body) { "Hey, I just jumped inside the thread!" }
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
shared_context "when sends the notification digest" do
|
4
|
+
context "when daily notification mail" do
|
5
|
+
let(:user) { create(:user, :admin, organization: organization, notifications_sending_frequency: "daily") }
|
6
|
+
|
7
|
+
it_behaves_like "notification digest mail"
|
8
|
+
end
|
9
|
+
|
10
|
+
context "when weekly notification mail" do
|
11
|
+
let(:user) { create(:user, :admin, organization: organization, notifications_sending_frequency: "weekly") }
|
12
|
+
|
13
|
+
it_behaves_like "notification digest mail"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
shared_examples_for "notification digest mail" do
|
18
|
+
context "when a notificable event takes place" do
|
19
|
+
let!(:organization) { create(:organization) }
|
20
|
+
let!(:participatory_space) { create(:participatory_process, organization: organization) }
|
21
|
+
|
22
|
+
it "sends a notification to the user's email" do
|
23
|
+
perform_enqueued_jobs do
|
24
|
+
expect(command.call).to broadcast(:ok)
|
25
|
+
Decidim::Notification.last.update(created_at: 1.day.ago)
|
26
|
+
Decidim::EmailNotificationsDigestGeneratorJob.perform_now(user.id, user.notifications_sending_frequency)
|
27
|
+
end
|
28
|
+
|
29
|
+
expect(last_email_body.length).to be_positive
|
30
|
+
expect(last_email_body).not_to include("translation missing")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -1,8 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
shared_examples_for "has embedded video in description" do |description_attribute_name|
|
3
|
+
shared_examples_for "has embedded video in description" do |description_attribute_name, count: 1|
|
4
4
|
let(description_attribute_name) { { en: %(Description <iframe class="ql-video" allowfullscreen="true" src="#{iframe_src}" frameborder="0"></iframe>) } }
|
5
5
|
let(:iframe_src) { "http://www.example.org" }
|
6
|
+
let!(:cookie_warning) { "You need to enable all cookies in order to see this content" }
|
6
7
|
|
7
8
|
context "when cookies are rejected" do
|
8
9
|
before do
|
@@ -11,7 +12,7 @@ shared_examples_for "has embedded video in description" do |description_attribut
|
|
11
12
|
end
|
12
13
|
|
13
14
|
it "disables iframe" do
|
14
|
-
expect(page).to have_content(
|
15
|
+
expect(page).to have_content(cookie_warning)
|
15
16
|
expect(page).not_to have_selector("iframe")
|
16
17
|
end
|
17
18
|
end
|
@@ -23,8 +24,8 @@ shared_examples_for "has embedded video in description" do |description_attribut
|
|
23
24
|
end
|
24
25
|
|
25
26
|
it "shows iframe" do
|
26
|
-
expect(page).not_to have_content(
|
27
|
-
expect(page).to have_selector("iframe", count:
|
27
|
+
expect(page).not_to have_content(cookie_warning)
|
28
|
+
expect(page).to have_selector("iframe", count: count)
|
28
29
|
end
|
29
30
|
end
|
30
31
|
end
|
@@ -1,13 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
shared_examples "having a rich text editor" do |
|
4
|
-
it "has a
|
5
|
-
within
|
3
|
+
shared_examples "having a rich text editor for field" do |selector, toolbar|
|
4
|
+
it "has a rich text editor" do
|
5
|
+
within selector do
|
6
6
|
expect(page).to have_selector("div.editor-container[data-toolbar='#{toolbar}']", visible: :all)
|
7
7
|
end
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
+
shared_examples "having a rich text editor" do |css, toolbar|
|
12
|
+
it_behaves_like "having a rich text editor for field", "form.#{css}", toolbar
|
13
|
+
end
|
14
|
+
|
11
15
|
shared_context "with rich text editor content" do
|
12
16
|
let(:content) { "<p>#{safe_tags}</p>#{script}" }
|
13
17
|
let(:safe_tags) { em + u + strong }
|
data/lib/decidim/core/test.rb
CHANGED
@@ -75,3 +75,4 @@ require "decidim/core/test/shared_examples/conversations_examples"
|
|
75
75
|
require "decidim/core/test/shared_examples/resource_endorsed_event_examples"
|
76
76
|
require "decidim/core/test/shared_examples/versions_controller_examples"
|
77
77
|
require "decidim/core/test/shared_examples/mcell_examples"
|
78
|
+
require "decidim/core/test/shared_examples/digest_mail_examples"
|
data/lib/decidim/core/version.rb
CHANGED
@@ -28,11 +28,9 @@ module Decidim
|
|
28
28
|
# specs which means any gems available in the gem install folder.
|
29
29
|
#
|
30
30
|
# @param gem [String] The name for the gem to be looked up.
|
31
|
-
# @return [
|
32
|
-
#
|
33
|
-
#
|
34
|
-
# necessary methods for the other checks. In both cases returns nil if
|
35
|
-
# the gem is not found.
|
31
|
+
# @return [Gem::Specification, nil] Returns the gem specification for the
|
32
|
+
# given gem name. The specification implements the necessary methods for
|
33
|
+
# the other checks. In both cases returns nil if the gem is not found.
|
36
34
|
def lookup(gem)
|
37
35
|
# In case the lookup method is called with a spec definition, return the
|
38
36
|
# definition itself.
|
@@ -217,10 +215,18 @@ module Decidim
|
|
217
215
|
# Gemfile, e.g. when the Decidim gems are installed through git.
|
218
216
|
#
|
219
217
|
# @param name [String] The name of the gem to find.
|
220
|
-
# @return [
|
221
|
-
# or nil if the gem is not listed in the locked gems
|
218
|
+
# @return [Gem::Specification, nil] The specification for the gem
|
219
|
+
# or nil if the gem is not listed in the locked gems or nil when the
|
220
|
+
# returned spec is not installed in the current gem environment.
|
222
221
|
def spec(name)
|
223
|
-
Bundler.definition.locked_gems.specs.find { |s| s.name == name }
|
222
|
+
sp = Bundler.definition.locked_gems.specs.find { |s| s.name == name }
|
223
|
+
|
224
|
+
# Fetching the gem through Gem.loaded_specs ensures we are not returning
|
225
|
+
# a lazy specification which does not respond to `#full_gem_path` which
|
226
|
+
# is needed for the resolver.
|
227
|
+
return Gem.loaded_specs[sp.name] if sp
|
228
|
+
|
229
|
+
nil
|
224
230
|
end
|
225
231
|
|
226
232
|
private
|
data/lib/decidim/form_builder.rb
CHANGED
@@ -47,7 +47,7 @@ module Decidim
|
|
47
47
|
# rubocop:enable Metrics/ParameterLists
|
48
48
|
|
49
49
|
def create_language_selector(locales, tabs_id, name)
|
50
|
-
if
|
50
|
+
if locales.count > 4
|
51
51
|
language_selector_select(locales, tabs_id, name)
|
52
52
|
else
|
53
53
|
language_tabs(locales, tabs_id, name)
|
@@ -444,7 +444,7 @@ module Decidim
|
|
444
444
|
# * resouce_name: Name of the resource (e.g. user)
|
445
445
|
# * resource_class: Attribute's resource class (e.g. Decidim::User)
|
446
446
|
# * resouce_class: Class of the resource (e.g. user)
|
447
|
-
# *
|
447
|
+
# * required: Whether the file is required or not (false by default).
|
448
448
|
# * titled: Whether the file can have title or not.
|
449
449
|
# * show_current: Whether the current file is displayed next to the button.
|
450
450
|
# * help: Array of help messages which are displayed inside of the upload modal.
|
@@ -464,7 +464,7 @@ module Decidim
|
|
464
464
|
attribute: attribute,
|
465
465
|
resource_name: @object_name,
|
466
466
|
resource_class: options[:resource_class]&.to_s || resource_class(attribute),
|
467
|
-
|
467
|
+
required: false,
|
468
468
|
titled: false,
|
469
469
|
show_current: true,
|
470
470
|
max_file_size: max_file_size,
|
@@ -712,6 +712,8 @@ module Decidim
|
|
712
712
|
safe_join([yield, text.html_safe])
|
713
713
|
elsif block_given?
|
714
714
|
safe_join([text.html_safe, yield])
|
715
|
+
else
|
716
|
+
text
|
715
717
|
end
|
716
718
|
|
717
719
|
label(attribute, text, options || {})
|
@@ -928,7 +930,6 @@ module Decidim
|
|
928
930
|
return {} unless options[:editor_images]
|
929
931
|
|
930
932
|
{
|
931
|
-
editor_images: true,
|
932
933
|
upload_images_path: Decidim::Core::Engine.routes.url_helpers.editor_images_path,
|
933
934
|
drag_and_drop_help_text: I18n.t("drag_and_drop_help", scope: "decidim.editor_images")
|
934
935
|
}
|
@@ -47,7 +47,13 @@ module Decidim
|
|
47
47
|
.joins(:participatory_space_resource_links_to)
|
48
48
|
.where(decidim_participatory_space_links: { name: link_name, from_id: id, from_type: self.class.name })
|
49
49
|
|
50
|
-
klass.where(id: from).or(klass.where(id: to))
|
50
|
+
query = klass.where(id: from).or(klass.where(id: to)).published
|
51
|
+
|
52
|
+
if klass.column_names.include?("weight")
|
53
|
+
query.order(:weight)
|
54
|
+
else
|
55
|
+
query.order(created_at: :desc)
|
56
|
+
end
|
51
57
|
end
|
52
58
|
|
53
59
|
def participatory_space_sibling_scope(participatory_space_name)
|
data/lib/decidim/publicable.rb
CHANGED