decidim-core 0.27.1 → 0.27.2
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/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_cell.rb +12 -7
- data/app/commands/decidim/unendorse_resource.rb +1 -1
- data/app/controllers/decidim/groups_controller.rb +5 -0
- data/app/controllers/decidim/links_controller.rb +4 -2
- data/app/controllers/decidim/profiles_controller.rb +1 -1
- data/app/helpers/decidim/icon_helper.rb +3 -3
- data/app/helpers/decidim/newsletters_helper.rb +1 -0
- data/app/mailers/decidim/newsletter_mailer.rb +10 -3
- data/app/models/decidim/newsletter.rb +28 -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/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/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/push_notification_presenter.rb +1 -1
- data/app/uploaders/decidim/application_uploader.rb +1 -1
- data/app/uploaders/decidim/avatar_uploader.rb +2 -2
- data/app/views/decidim/messaging/conversations/_conversation.html.erb +1 -1
- 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 +5 -4
- data/config/locales/bg.yml +5 -4
- data/config/locales/ca.yml +17 -13
- data/config/locales/cs.yml +6 -3
- data/config/locales/de.yml +2 -5
- data/config/locales/el.yml +4 -5
- data/config/locales/en.yml +6 -2
- data/config/locales/es-MX.yml +10 -6
- data/config/locales/es-PY.yml +10 -6
- data/config/locales/es.yml +15 -11
- data/config/locales/eu.yml +24 -22
- data/config/locales/fi-plain.yml +6 -2
- data/config/locales/fi.yml +7 -3
- data/config/locales/fr-CA.yml +6 -5
- data/config/locales/fr.yml +6 -5
- data/config/locales/gl.yml +2 -4
- data/config/locales/hu.yml +4 -5
- data/config/locales/id-ID.yml +5 -4
- data/config/locales/is-IS.yml +0 -1
- data/config/locales/it.yml +1 -5
- data/config/locales/ja.yml +20 -16
- data/config/locales/ka-GE.yml +1 -0
- data/config/locales/lb.yml +0 -4
- data/config/locales/lt.yml +0 -4
- data/config/locales/lv.yml +5 -4
- data/config/locales/nl.yml +0 -4
- data/config/locales/no.yml +2 -6
- data/config/locales/pl.yml +4 -5
- data/config/locales/pt-BR.yml +0 -4
- data/config/locales/pt.yml +0 -4
- data/config/locales/ro-RO.yml +49 -3
- data/config/locales/ru.yml +5 -1
- data/config/locales/sk.yml +5 -4
- data/config/locales/sv.yml +22 -5
- data/config/locales/tr-TR.yml +4 -5
- data/config/locales/uk.yml +5 -1
- data/config/locales/zh-CN.yml +3 -4
- 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 +93 -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/version.rb +1 -1
- data/lib/decidim/dependency_resolver.rb +14 -8
- data/lib/decidim/form_builder.rb +1 -1
- data/lib/decidim/participatory_space_resourceable.rb +7 -1
- metadata +10 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 391ba34f55c860208f7644dd10b1b3e2b6465ed45b9e9f9fd194ce1036516995
|
|
4
|
+
data.tar.gz: 93636f8d556d73547fcdff45986fcc85ba4a22e9c399fdeca0c550ee6c241363
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 157c005fbea98fe374586f91f15f17bc54450d6e4b6f7136d51582d5cae7af9442d250edbbf4fe2a4308aac902ba0eb28c49681153dcda6b6948f8589414e930
|
|
7
|
+
data.tar.gz: 8ebc9089ad86980956ded3024d490f419bf8015ae0dc4b3fe6e003d415010de8ed28751bd0c168cf526c8a17f5db50467c9223242c2150cf600304990e1eda04
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<tr>
|
|
17
17
|
<th>
|
|
18
18
|
<center>
|
|
19
|
-
<%= render partial: "layouts/decidim/mailer_logo.html", locals: { organization: organization } %>
|
|
19
|
+
<%= render partial: "layouts/decidim/mailer_logo.html", locals: { organization: organization, custom_url_for_mail_root: custom_url_for_mail_root } %>
|
|
20
20
|
</center>
|
|
21
21
|
</th>
|
|
22
22
|
</tr>
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<tr>
|
|
28
28
|
<th>
|
|
29
29
|
<% if organization.official_img_header.attached? %>
|
|
30
|
-
<%= link_to
|
|
30
|
+
<%= link_to newsletter.organization_official_url do %>
|
|
31
31
|
<%= image_tag organization.attached_uploader(:official_img_header).path, alt: "", style: "max-height: 50px", class: "float-right" %>
|
|
32
32
|
<% end %>
|
|
33
33
|
<% end %>
|
|
@@ -72,8 +72,8 @@
|
|
|
72
72
|
<th class="expander"></th>
|
|
73
73
|
<th class="small-12 first columns cityhall-bar">
|
|
74
74
|
<div class="decidim-logo" style="float: right; text-align: right; padding-right: 16px">
|
|
75
|
-
<% if
|
|
76
|
-
<%= link_to organization.name.html_safe,
|
|
75
|
+
<% if custom_url_for_mail_root.present? %>
|
|
76
|
+
<%= link_to organization.name.html_safe, custom_url_for_mail_root %>
|
|
77
77
|
<% else %>
|
|
78
78
|
<%= link_to organization.name.html_safe, decidim.root_url(host: organization.host) %>
|
|
79
79
|
<% end %>
|
|
@@ -24,7 +24,7 @@ table.button table td {
|
|
|
24
24
|
<tr>
|
|
25
25
|
<th>
|
|
26
26
|
<center>
|
|
27
|
-
<%= render partial: "layouts/decidim/mailer_logo.html", locals: { organization: organization } %>
|
|
27
|
+
<%= render partial: "layouts/decidim/mailer_logo.html", locals: { organization: organization, custom_url_for_mail_root: custom_url_for_mail_root } %>
|
|
28
28
|
</center>
|
|
29
29
|
</th>
|
|
30
30
|
</tr>
|
|
@@ -35,7 +35,7 @@ table.button table td {
|
|
|
35
35
|
<tr>
|
|
36
36
|
<th>
|
|
37
37
|
<% if organization.official_img_header.attached? %>
|
|
38
|
-
<%= link_to
|
|
38
|
+
<%= link_to newsletter.organization_official_url do %>
|
|
39
39
|
<%= image_tag organization.attached_uploader(:official_img_header).url(host: organization.host), alt: "", style: "max-height: 50px", class: "float-right" %>
|
|
40
40
|
<% end %>
|
|
41
41
|
<% end %>
|
|
@@ -111,8 +111,8 @@ table.button table td {
|
|
|
111
111
|
<th class="expander"></th>
|
|
112
112
|
<th class="small-12 first columns cityhall-bar">
|
|
113
113
|
<div class="decidim-logo" style="float: right; text-align: right; padding-right: 16px">
|
|
114
|
-
<% if
|
|
115
|
-
<%= link_to organization.name.html_safe,
|
|
114
|
+
<% if custom_url_for_mail_root.present? %>
|
|
115
|
+
<%= link_to organization.name.html_safe, custom_url_for_mail_root %>
|
|
116
116
|
<% else %>
|
|
117
117
|
<%= link_to organization.name.html_safe, decidim.root_url(host: organization.host) %>
|
|
118
118
|
<% end %>
|
|
@@ -87,9 +87,18 @@ module Decidim
|
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
def explanation
|
|
90
|
-
|
|
90
|
+
i18n_options = {
|
|
91
|
+
scope: options[:help_i18n_scope].presence || "decidim.forms.upload_help",
|
|
92
|
+
attribute: attribute_translation
|
|
93
|
+
}
|
|
91
94
|
|
|
92
|
-
I18n.t("explanation",
|
|
95
|
+
I18n.t("explanation", **i18n_options)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def attribute_translation
|
|
99
|
+
I18n.t(attribute, scope: [:activemodel, :attributes, resource_class.constantize.model_name.param_key].join("."))
|
|
100
|
+
rescue NameError
|
|
101
|
+
I18n.t(attribute, scope: "activemodel.attributes")
|
|
93
102
|
end
|
|
94
103
|
|
|
95
104
|
def add_attribute
|
|
@@ -145,11 +154,7 @@ module Decidim
|
|
|
145
154
|
end
|
|
146
155
|
|
|
147
156
|
def file_name_for(attachment)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return "(#{filename})" if has_title?
|
|
151
|
-
|
|
152
|
-
filename
|
|
157
|
+
determine_filename(attachment)
|
|
153
158
|
end
|
|
154
159
|
|
|
155
160
|
def determine_filename(attachment)
|
|
@@ -31,7 +31,7 @@ module Decidim
|
|
|
31
31
|
query = if @current_group.present?
|
|
32
32
|
@resource.endorsements.where(decidim_user_group_id: @current_group&.id)
|
|
33
33
|
else
|
|
34
|
-
@resource.endorsements.where(author: @current_user)
|
|
34
|
+
@resource.endorsements.where(author: @current_user, decidim_user_group_id: nil)
|
|
35
35
|
end
|
|
36
36
|
query.destroy_all
|
|
37
37
|
end
|
|
@@ -7,6 +7,7 @@ module Decidim
|
|
|
7
7
|
include UserGroups
|
|
8
8
|
|
|
9
9
|
before_action :enforce_user_groups_enabled
|
|
10
|
+
before_action :ensure_user_group_not_blocked
|
|
10
11
|
|
|
11
12
|
def new
|
|
12
13
|
enforce_permission_to :create, :user_group, current_user: current_user
|
|
@@ -78,6 +79,10 @@ module Decidim
|
|
|
78
79
|
|
|
79
80
|
private
|
|
80
81
|
|
|
82
|
+
def ensure_user_group_not_blocked
|
|
83
|
+
raise ActionController::RoutingError, "Blocked User Group" if user_group&.blocked?
|
|
84
|
+
end
|
|
85
|
+
|
|
81
86
|
def accepted_user_group
|
|
82
87
|
@accepted_user_group ||= Decidim::UserGroups::AcceptedUserGroups.for(current_user).find_by(nickname: params[:id])
|
|
83
88
|
end
|
|
@@ -7,9 +7,11 @@ module Decidim
|
|
|
7
7
|
skip_before_action :store_current_location
|
|
8
8
|
|
|
9
9
|
helper Decidim::ExternalDomainHelper
|
|
10
|
+
helper_method :external_url
|
|
10
11
|
|
|
11
12
|
before_action :parse_url
|
|
12
13
|
rescue_from Decidim::InvalidUrlError, with: :invalid_url
|
|
14
|
+
rescue_from URI::InvalidURIError, with: :invalid_url
|
|
13
15
|
|
|
14
16
|
def new
|
|
15
17
|
headers["X-Robots-Tag"] = "noindex"
|
|
@@ -25,7 +27,7 @@ module Decidim
|
|
|
25
27
|
def parse_url
|
|
26
28
|
raise Decidim::InvalidUrlError unless external_url
|
|
27
29
|
|
|
28
|
-
parts = external_url.match %r{
|
|
30
|
+
parts = external_url.match %r{\A(([a-z]+):)?//([^/]+)(/.*)?\z}
|
|
29
31
|
raise Decidim::InvalidUrlError unless parts
|
|
30
32
|
|
|
31
33
|
@url_parts = {
|
|
@@ -36,7 +38,7 @@ module Decidim
|
|
|
36
38
|
end
|
|
37
39
|
|
|
38
40
|
def external_url
|
|
39
|
-
@external_url ||= params[:external_url]
|
|
41
|
+
@external_url ||= URI.parse(params[:external_url]).to_s
|
|
40
42
|
end
|
|
41
43
|
end
|
|
42
44
|
end
|
|
@@ -13,7 +13,7 @@ module Decidim
|
|
|
13
13
|
before_action :ensure_profile_holder
|
|
14
14
|
before_action :ensure_profile_holder_is_a_group, only: [:members]
|
|
15
15
|
before_action :ensure_profile_holder_is_a_user, only: [:groups, :following]
|
|
16
|
-
before_action :ensure_user_not_blocked
|
|
16
|
+
before_action :ensure_user_not_blocked
|
|
17
17
|
|
|
18
18
|
def show
|
|
19
19
|
return redirect_to profile_timeline_path(nickname: params[:nickname]) if profile_holder == current_user
|
|
@@ -24,7 +24,7 @@ module Decidim
|
|
|
24
24
|
#
|
|
25
25
|
# Returns an HTML tag with the icon.
|
|
26
26
|
def manifest_icon(manifest, options = {})
|
|
27
|
-
if manifest.icon
|
|
27
|
+
if manifest.respond_to?(:icon) && manifest.icon.present?
|
|
28
28
|
external_icon manifest.icon, options
|
|
29
29
|
else
|
|
30
30
|
icon "question-mark", options
|
|
@@ -42,9 +42,9 @@ module Decidim
|
|
|
42
42
|
def resource_icon(resource, options = {})
|
|
43
43
|
if resource.instance_of?(Decidim::Comments::Comment)
|
|
44
44
|
icon "comment-square", options
|
|
45
|
-
elsif resource.respond_to?(:component)
|
|
45
|
+
elsif resource.respond_to?(:component) && resource.component.present?
|
|
46
46
|
component_icon(resource.component, options)
|
|
47
|
-
elsif resource.respond_to?(:manifest)
|
|
47
|
+
elsif resource.respond_to?(:manifest) && resource.manifest.present?
|
|
48
48
|
manifest_icon(resource.manifest, options)
|
|
49
49
|
elsif resource.is_a?(Decidim::User)
|
|
50
50
|
icon "person", options
|
|
@@ -31,6 +31,7 @@ module Decidim
|
|
|
31
31
|
# this method is used to generate the root link on mail with the utm_codes
|
|
32
32
|
# If the newsletter_id is nil, it returns the root_url
|
|
33
33
|
def custom_url_for_mail_root(organization, newsletter_id = nil)
|
|
34
|
+
decidim = EngineRouter.new("decidim", {})
|
|
34
35
|
if newsletter_id.present?
|
|
35
36
|
decidim.root_url(host: organization.host) + utm_codes(organization.host, newsletter_id.to_s)
|
|
36
37
|
else
|
|
@@ -11,14 +11,20 @@ module Decidim
|
|
|
11
11
|
|
|
12
12
|
helper_method :cell
|
|
13
13
|
|
|
14
|
-
def newsletter(user, newsletter)
|
|
14
|
+
def newsletter(user, newsletter, preview: false)
|
|
15
15
|
return if user.email.blank?
|
|
16
16
|
|
|
17
17
|
@organization = user.organization
|
|
18
18
|
@newsletter = newsletter
|
|
19
19
|
@user = user
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
@preview = preview
|
|
21
|
+
|
|
22
|
+
@custom_url_for_mail_root =
|
|
23
|
+
if @preview
|
|
24
|
+
"#"
|
|
25
|
+
elsif Decidim.config.track_newsletter_links
|
|
26
|
+
custom_url_for_mail_root(@organization, @newsletter.id)
|
|
27
|
+
end
|
|
22
28
|
@encrypted_token = Decidim::NewsletterEncryptor.sent_at_encrypted(@user.id, @newsletter.sent_at)
|
|
23
29
|
|
|
24
30
|
with_user(user) do
|
|
@@ -40,6 +46,7 @@ module Decidim
|
|
|
40
46
|
organization: @organization,
|
|
41
47
|
newsletter: @newsletter,
|
|
42
48
|
recipient_user: @user,
|
|
49
|
+
custom_url_for_mail_root: @custom_url_for_mail_root,
|
|
43
50
|
context: {
|
|
44
51
|
controller: self
|
|
45
52
|
}
|
|
@@ -56,6 +56,24 @@ module Decidim
|
|
|
56
56
|
.find_by(scoped_resource_id: id)
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
+
def url(**kwargs)
|
|
60
|
+
proxy_url(:newsletter_url, id: id, **kwargs)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def notifications_settings_url(**kwargs)
|
|
64
|
+
proxy_url(__method__, **kwargs)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def unsubscribe_newsletters_url(**kwargs)
|
|
68
|
+
proxy_url(__method__, **kwargs)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def organization_official_url
|
|
72
|
+
return "#" unless sent?
|
|
73
|
+
|
|
74
|
+
organization.official_url || proxy_url(:root_url)
|
|
75
|
+
end
|
|
76
|
+
|
|
59
77
|
private
|
|
60
78
|
|
|
61
79
|
def author_belongs_to_organization
|
|
@@ -63,5 +81,15 @@ module Decidim
|
|
|
63
81
|
|
|
64
82
|
errors.add(:author, :invalid) unless author.organization == organization
|
|
65
83
|
end
|
|
84
|
+
|
|
85
|
+
def proxy_url(method, **kwargs)
|
|
86
|
+
return "#" unless sent?
|
|
87
|
+
|
|
88
|
+
router.public_send(method, host: organization.host, **kwargs)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def router
|
|
92
|
+
@router ||= EngineRouter.new("decidim", {})
|
|
93
|
+
end
|
|
66
94
|
end
|
|
67
95
|
end
|
data/app/models/decidim/user.rb
CHANGED
|
@@ -36,8 +36,6 @@ module Decidim
|
|
|
36
36
|
has_many :access_tokens, class_name: "Doorkeeper::AccessToken", foreign_key: :resource_owner_id, dependent: :destroy
|
|
37
37
|
has_many :reminders, foreign_key: "decidim_user_id", class_name: "Decidim::Reminder", dependent: :destroy
|
|
38
38
|
|
|
39
|
-
has_one :blocking, class_name: "Decidim::UserBlock", foreign_key: :id, primary_key: :block_id, dependent: :destroy
|
|
40
|
-
|
|
41
39
|
validates :name, presence: true, unless: -> { deleted? }
|
|
42
40
|
validates :nickname,
|
|
43
41
|
presence: true,
|
|
@@ -17,6 +17,8 @@ module Decidim
|
|
|
17
17
|
has_many :notifications, foreign_key: "decidim_user_id", class_name: "Decidim::Notification", dependent: :destroy
|
|
18
18
|
has_many :following_follows, foreign_key: "decidim_user_id", class_name: "Decidim::Follow", dependent: :destroy
|
|
19
19
|
|
|
20
|
+
has_one :blocking, class_name: "Decidim::UserBlock", foreign_key: :id, primary_key: :block_id, dependent: :destroy
|
|
21
|
+
|
|
20
22
|
# Regex for name & nickname format validations
|
|
21
23
|
REGEXP_NAME = /\A(?!.*[<>?%&\^*#@()\[\]=+:;"{}\\|])/
|
|
22
24
|
|
|
@@ -4,7 +4,7 @@ module Decidim
|
|
|
4
4
|
class UserBlock < ApplicationRecord
|
|
5
5
|
MINIMUM_JUSTIFICATION_LENGTH = 15
|
|
6
6
|
|
|
7
|
-
belongs_to :user, class_name: "Decidim::
|
|
8
|
-
belongs_to :blocking_user, class_name: "Decidim::
|
|
7
|
+
belongs_to :user, class_name: "Decidim::UserBaseEntity", foreign_key: :decidim_user_id
|
|
8
|
+
belongs_to :blocking_user, class_name: "Decidim::UserBaseEntity"
|
|
9
9
|
end
|
|
10
10
|
end
|
|
@@ -21,7 +21,7 @@ module Decidim
|
|
|
21
21
|
foreign_key: :decidim_user_id,
|
|
22
22
|
source: :user
|
|
23
23
|
|
|
24
|
-
validates :name, presence: true, uniqueness: { scope: :decidim_organization_id }
|
|
24
|
+
validates :name, presence: true, uniqueness: { scope: :decidim_organization_id }, unless: -> { blocked? }
|
|
25
25
|
|
|
26
26
|
validate :correct_state
|
|
27
27
|
validate :unique_document_number, if: :has_document_number?
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* global spyOn */
|
|
1
|
+
/* global spyOn, jest */
|
|
2
2
|
/* eslint-disable id-length */
|
|
3
3
|
window.$ = $;
|
|
4
4
|
|
|
@@ -7,6 +7,20 @@ import DataPicker from "./data_picker"
|
|
|
7
7
|
|
|
8
8
|
const FormFilterComponent = require("./form_filter.component_for_testing.js");
|
|
9
9
|
|
|
10
|
+
const expectedPushState = (state, filters) => {
|
|
11
|
+
const queryString = Object.keys(filters).map((key) => {
|
|
12
|
+
const name = `filter[${key}]`;
|
|
13
|
+
const val = filters[key];
|
|
14
|
+
if (Array.isArray(val)) {
|
|
15
|
+
return val.map((v) => `${encodeURIComponent(`${name}[]`)}=${encodeURIComponent(v)}`).join("&");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return `${encodeURIComponent(name)}=${encodeURIComponent(val)}`;
|
|
19
|
+
}).join("&");
|
|
20
|
+
|
|
21
|
+
return [state, null, `/filters?${queryString}`];
|
|
22
|
+
}
|
|
23
|
+
|
|
10
24
|
describe("FormFilterComponent", () => {
|
|
11
25
|
const selector = "form#new_filter";
|
|
12
26
|
let subject = null;
|
|
@@ -15,6 +29,10 @@ describe("FormFilterComponent", () => {
|
|
|
15
29
|
beforeEach(() => {
|
|
16
30
|
let form = `
|
|
17
31
|
<form id="new_filter" action="/filters" method="get">
|
|
32
|
+
<fieldset>
|
|
33
|
+
<input id="filter_search_text_cont" placeholder="Search" data-disable-dynamic-change="true" type="search" name="filter[search_text_cont]">
|
|
34
|
+
</fieldset>
|
|
35
|
+
|
|
18
36
|
<fieldset>
|
|
19
37
|
<div id="filter_somerandomid_scope_id" class="data-picker picker-multiple" data-picker-name="filter[scope_id]">
|
|
20
38
|
<div class="picker-values">
|
|
@@ -67,11 +85,25 @@ describe("FormFilterComponent", () => {
|
|
|
67
85
|
`;
|
|
68
86
|
$("body").append(form);
|
|
69
87
|
|
|
88
|
+
const $form = $(document).find("form");
|
|
89
|
+
|
|
70
90
|
window.Decidim = window.Decidim || {};
|
|
71
91
|
|
|
72
92
|
window.theDataPicker = new DataPicker($(".data-picker"));
|
|
73
93
|
window.theCheckBoxesTree = new CheckBoxesTree();
|
|
74
|
-
|
|
94
|
+
window.Rails = {
|
|
95
|
+
fire: (htmlElement, event) => {
|
|
96
|
+
// Hack to call trigger on the correct instance of the form, as fetching
|
|
97
|
+
// with the selector does not work.
|
|
98
|
+
if (htmlElement === $form[0]) {
|
|
99
|
+
$form.trigger(event);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
subject = new FormFilterComponent($form);
|
|
105
|
+
|
|
106
|
+
jest.useFakeTimers();
|
|
75
107
|
});
|
|
76
108
|
|
|
77
109
|
it("exists", () => {
|
|
@@ -88,7 +120,19 @@ describe("FormFilterComponent", () => {
|
|
|
88
120
|
|
|
89
121
|
describe("when mounted", () => {
|
|
90
122
|
beforeEach(() => {
|
|
91
|
-
|
|
123
|
+
// Jest doesn't implement listening on the form submit event so we need
|
|
124
|
+
// to hack it.
|
|
125
|
+
const originalOn = subject.$form.on.bind(subject.$form);
|
|
126
|
+
jest.spyOn(subject.$form, "on").mockImplementation((...args) => {
|
|
127
|
+
if (args[0] === "submit") {
|
|
128
|
+
subject.$form.submitHandler = args[1];
|
|
129
|
+
} else if (args[0] === "change" && typeof args[1] === "string") {
|
|
130
|
+
subject.$form.changeHandler = args[2];
|
|
131
|
+
} else {
|
|
132
|
+
originalOn(...args);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
92
136
|
subject.mountComponent();
|
|
93
137
|
});
|
|
94
138
|
|
|
@@ -100,8 +144,98 @@ describe("FormFilterComponent", () => {
|
|
|
100
144
|
expect(subject.mounted).toBeTruthy();
|
|
101
145
|
});
|
|
102
146
|
|
|
103
|
-
it("binds the form change
|
|
147
|
+
it("binds the form change and submit events", () => {
|
|
104
148
|
expect(subject.$form.on).toHaveBeenCalledWith("change", "input:not([data-disable-dynamic-change]), select:not([data-disable-dynamic-change])", subject._onFormChange);
|
|
149
|
+
expect(subject.$form.on).toHaveBeenCalledWith("submit", subject._onFormSubmit);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("form changes", () => {
|
|
153
|
+
beforeEach(() => {
|
|
154
|
+
spyOn(window.history, "pushState");
|
|
155
|
+
|
|
156
|
+
// This is a hack to be able to trigger the events even somewhat close
|
|
157
|
+
// to an actual situation. In real browser environment the change events
|
|
158
|
+
// would be triggered by the input/select elements but to simplify the
|
|
159
|
+
// test implementation, we trigger them directly on the form.
|
|
160
|
+
const originalTrigger = subject.$form.trigger.bind(subject.$form);
|
|
161
|
+
jest.spyOn(subject.$form, "trigger").mockImplementation((...args) => {
|
|
162
|
+
if (args[0] === "submit") {
|
|
163
|
+
subject.$form.submitHandler(
|
|
164
|
+
$.event.fix(new CustomEvent("submit", { bubbles: true, cancelable: true }))
|
|
165
|
+
);
|
|
166
|
+
} else if (args[0] === "change") {
|
|
167
|
+
subject.$form.changeHandler();
|
|
168
|
+
} else {
|
|
169
|
+
originalTrigger(...args);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
jest.runAllTimers();
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("does not save the state in case there were no changes to previous state", () => {
|
|
177
|
+
subject.$form.trigger("change");
|
|
178
|
+
|
|
179
|
+
expect(window.history.pushState).not.toHaveBeenCalled();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("saves the state after dynamic form changes", () => {
|
|
183
|
+
$("#filter_somerandomid_category_id").val(2);
|
|
184
|
+
|
|
185
|
+
subject.$form.trigger("change");
|
|
186
|
+
|
|
187
|
+
const state = {
|
|
188
|
+
"filter_somerandomid_scope_id": [
|
|
189
|
+
{
|
|
190
|
+
"text": "Scope 1",
|
|
191
|
+
"url": "picker_url_1",
|
|
192
|
+
"value": "3"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"text": "Scope 2",
|
|
196
|
+
"url": "picker_url_2",
|
|
197
|
+
"value": "4"
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
};
|
|
201
|
+
const filters = {
|
|
202
|
+
"search_text_cont": "",
|
|
203
|
+
"scope_id": [3, 4],
|
|
204
|
+
"category_id": 2,
|
|
205
|
+
"state": [""]
|
|
206
|
+
};
|
|
207
|
+
expect(window.history.pushState).toHaveBeenCalledWith(...expectedPushState(state, filters));
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("saves the state after form submission through input element", () => {
|
|
211
|
+
const textInput = document.getElementById("filter_search_text_cont");
|
|
212
|
+
textInput.value = "search";
|
|
213
|
+
|
|
214
|
+
subject.$form.trigger("submit");
|
|
215
|
+
|
|
216
|
+
const state = {
|
|
217
|
+
"filter_somerandomid_scope_id": [
|
|
218
|
+
{
|
|
219
|
+
"text": "Scope 1",
|
|
220
|
+
"url": "picker_url_1",
|
|
221
|
+
"value": "3"
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"text": "Scope 2",
|
|
225
|
+
"url": "picker_url_2",
|
|
226
|
+
"value": "4"
|
|
227
|
+
}
|
|
228
|
+
]
|
|
229
|
+
}
|
|
230
|
+
const filters = {
|
|
231
|
+
"search_text_cont": "search",
|
|
232
|
+
"scope_id": [3, 4],
|
|
233
|
+
"category_id": 1,
|
|
234
|
+
"state": [""]
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
expect(window.history.pushState).toHaveBeenCalledWith(...expectedPushState(state, filters));
|
|
238
|
+
});
|
|
105
239
|
});
|
|
106
240
|
|
|
107
241
|
describe("onpopstate event", () => {
|
|
@@ -131,6 +265,14 @@ describe("FormFilterComponent", () => {
|
|
|
131
265
|
expect(checked.map((input) => input.value)).toEqual(["", "accepted", "evaluating"]);
|
|
132
266
|
expect(checked.filter((input) => input.indeterminate).map((input) => input.value)).toEqual([""]);
|
|
133
267
|
});
|
|
268
|
+
|
|
269
|
+
it("does not save the state", () => {
|
|
270
|
+
spyOn(window.history, "pushState");
|
|
271
|
+
|
|
272
|
+
window.onpopstate({ isTrusted: true, state: scopesPickerState});
|
|
273
|
+
|
|
274
|
+
expect(window.history.pushState).not.toHaveBeenCalled();
|
|
275
|
+
});
|
|
134
276
|
});
|
|
135
277
|
});
|
|
136
278
|
|
|
@@ -145,8 +287,9 @@ describe("FormFilterComponent", () => {
|
|
|
145
287
|
expect(subject.mounted).toBeFalsy();
|
|
146
288
|
});
|
|
147
289
|
|
|
148
|
-
it("unbinds the form change
|
|
290
|
+
it("unbinds the form change and submit events", () => {
|
|
149
291
|
expect(subject.$form.off).toHaveBeenCalledWith("change", "input, select", subject._onFormChange);
|
|
292
|
+
expect(subject.$form.off).toHaveBeenCalledWith("submit", subject._onFormSubmit);
|
|
150
293
|
});
|
|
151
294
|
});
|
|
152
295
|
|
|
@@ -23,6 +23,7 @@ export default class FormFilterComponent {
|
|
|
23
23
|
|
|
24
24
|
this._updateInitialState();
|
|
25
25
|
this._onFormChange = delayed(this, this._onFormChange.bind(this));
|
|
26
|
+
this._onFormSubmit = delayed(this, this._onFormSubmit.bind(this));
|
|
26
27
|
this._onPopState = this._onPopState.bind(this);
|
|
27
28
|
|
|
28
29
|
if (window.Decidim.PopStateHandler) {
|
|
@@ -42,6 +43,7 @@ export default class FormFilterComponent {
|
|
|
42
43
|
if (this.mounted) {
|
|
43
44
|
this.mounted = false;
|
|
44
45
|
this.$form.off("change", "input, select", this._onFormChange);
|
|
46
|
+
this.$form.off("submit", this._onFormSubmit);
|
|
45
47
|
|
|
46
48
|
unregisterCallback(`filters-${this.id}`)
|
|
47
49
|
}
|
|
@@ -62,6 +64,7 @@ export default class FormFilterComponent {
|
|
|
62
64
|
contentContainer = this.$form.data("remoteFill");
|
|
63
65
|
}
|
|
64
66
|
this.$form.on("change", "input:not([data-disable-dynamic-change]), select:not([data-disable-dynamic-change])", this._onFormChange);
|
|
67
|
+
this.$form.on("submit", this._onFormSubmit);
|
|
65
68
|
|
|
66
69
|
this.currentFormRequest = null;
|
|
67
70
|
this.$form.on("ajax:beforeSend", (e) => {
|
|
@@ -254,14 +257,16 @@ export default class FormFilterComponent {
|
|
|
254
257
|
|
|
255
258
|
// Only one instance should submit the form on browser history navigation
|
|
256
259
|
if (this.popStateSubmiter) {
|
|
257
|
-
Rails.fire(this.$form[0], "submit");
|
|
260
|
+
Rails.fire(this.$form[0], "submit", { from: "pop" });
|
|
258
261
|
}
|
|
259
262
|
|
|
260
263
|
this.changeEvents = true;
|
|
261
264
|
}
|
|
262
265
|
|
|
263
266
|
/**
|
|
264
|
-
* Handles the logic to
|
|
267
|
+
* Handles the logic to decide whether the form should be submitted or not
|
|
268
|
+
* after a form change event. The form is only submitted when changes have
|
|
269
|
+
* occurred.
|
|
265
270
|
* @private
|
|
266
271
|
* @returns {Void} - Returns nothing.
|
|
267
272
|
*/
|
|
@@ -270,7 +275,7 @@ export default class FormFilterComponent {
|
|
|
270
275
|
return;
|
|
271
276
|
}
|
|
272
277
|
|
|
273
|
-
const [newPath
|
|
278
|
+
const [newPath] = this._currentStateAndPath();
|
|
274
279
|
const path = this._getLocation(false);
|
|
275
280
|
|
|
276
281
|
if (newPath === path) {
|
|
@@ -278,6 +283,23 @@ export default class FormFilterComponent {
|
|
|
278
283
|
}
|
|
279
284
|
|
|
280
285
|
Rails.fire(this.$form[0], "submit");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Saves the current state of the search on form submit to update the search
|
|
290
|
+
* parameters to the URL and store the picker states.
|
|
291
|
+
* @private
|
|
292
|
+
* @param {jQuery.Event} ev The event that caused the form to submit.
|
|
293
|
+
* @returns {Void} - Returns nothing.
|
|
294
|
+
*/
|
|
295
|
+
_onFormSubmit(ev) {
|
|
296
|
+
const eventDetail = ev.originalEvent.detail;
|
|
297
|
+
if (eventDetail && eventDetail.from === "pop") {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const [newPath, newState] = this._currentStateAndPath();
|
|
302
|
+
|
|
281
303
|
pushState(newPath, newState);
|
|
282
304
|
this._saveFilters(newPath);
|
|
283
305
|
}
|
|
@@ -314,7 +336,7 @@ export default class FormFilterComponent {
|
|
|
314
336
|
* @returns {String} - Returns a unique identifier
|
|
315
337
|
*/
|
|
316
338
|
_getUID() {
|
|
317
|
-
return `filter-form-${new Date().
|
|
339
|
+
return `filter-form-${new Date().getUTCMilliseconds()}-${Math.floor(Math.random() * 10000000)}`;
|
|
318
340
|
}
|
|
319
341
|
|
|
320
342
|
/**
|
|
@@ -52,7 +52,7 @@ module Decidim
|
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
def unreported_user
|
|
55
|
-
@unreported_user ||= Decidim::
|
|
55
|
+
@unreported_user ||= Decidim::UserBaseEntity.find_by(id: action_log.extra.dig("extra", "user_id"))
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
def has_diff?
|