decidim-core 0.26.2 → 0.26.4
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/amendable/announcement_cell.rb +1 -1
- data/app/cells/decidim/card_m_cell.rb +1 -1
- data/app/cells/decidim/content_blocks/cta/show.erb +1 -1
- data/app/cells/decidim/content_blocks/hero/show.erb +1 -1
- data/app/cells/decidim/content_blocks/highlighted_content_banner/show.erb +1 -1
- data/app/cells/decidim/content_blocks/stats_cell.rb +1 -0
- data/app/controllers/concerns/decidim/resource_versions_concern.rb +4 -0
- data/app/controllers/decidim/devise/invitations_controller.rb +9 -2
- data/app/controllers/decidim/devise/registrations_controller.rb +5 -1
- data/app/events/decidim/resource_endorsed_event.rb +2 -1
- data/app/forms/decidim/account_form.rb +8 -7
- data/app/forms/decidim/amendable/form.rb +2 -1
- data/app/forms/decidim/registration_form.rb +11 -5
- data/app/helpers/decidim/filters_helper.rb +5 -1
- data/app/mailers/decidim/notification_mailer.rb +1 -0
- data/app/models/decidim/action_log.rb +9 -9
- data/app/models/decidim/user_base_entity.rb +1 -0
- data/app/packs/src/decidim/editor/clipboard_override.js +143 -0
- data/app/packs/src/decidim/editor/clipboard_utilities.js +119 -0
- data/app/packs/src/decidim/editor/linebreak_module.js +0 -8
- data/app/packs/src/decidim/editor.js +9 -2
- data/app/packs/src/decidim/map/factory.js +3 -1
- data/app/packs/src/decidim/map/legacy.js +2 -2
- data/app/packs/src/decidim/map.js +2 -2
- data/app/packs/stylesheets/decidim/modules/_cards.scss +2 -0
- data/app/packs/stylesheets/decidim/modules/_comments.scss +2 -0
- data/app/packs/stylesheets/decidim/modules/_forms.scss +5 -0
- data/app/permissions/decidim/permissions.rb +4 -2
- data/app/presenters/decidim/home_stats_presenter.rb +11 -4
- data/app/presenters/decidim/stats_presenter.rb +7 -8
- data/app/presenters/decidim/user_presenter.rb +12 -4
- data/app/services/decidim/activity_search.rb +1 -0
- data/app/validators/etiquette_validator.rb +7 -3
- data/app/views/decidim/data_portability/show.html.erb +1 -1
- data/app/views/decidim/notification_mailer/event_received.html.erb +1 -1
- data/app/views/decidim/notifications_settings/show.html.erb +49 -51
- data/app/views/decidim/user_interests/show.html.erb +11 -13
- data/config/locales/ar.yml +0 -24
- data/config/locales/bg.yml +1 -23
- data/config/locales/ca.yml +5 -25
- data/config/locales/cs.yml +13 -33
- data/config/locales/de.yml +64 -25
- data/config/locales/el.yml +0 -22
- data/config/locales/en.yml +2 -22
- data/config/locales/es-MX.yml +5 -25
- data/config/locales/es-PY.yml +5 -25
- data/config/locales/es.yml +10 -30
- data/config/locales/eu.yml +5 -26
- data/config/locales/fi-plain.yml +2 -22
- data/config/locales/fi.yml +2 -22
- data/config/locales/fr-CA.yml +4 -24
- data/config/locales/fr.yml +11 -31
- data/config/locales/ga-IE.yml +1 -5
- data/config/locales/gl.yml +0 -24
- data/config/locales/gn-PY.yml +1 -0
- data/config/locales/hu.yml +173 -23
- data/config/locales/id-ID.yml +0 -24
- data/config/locales/is-IS.yml +2 -1
- data/config/locales/it.yml +3 -25
- data/config/locales/ja.yml +4 -24
- data/config/locales/lb.yml +1 -23
- data/config/locales/lo-LA.yml +1 -0
- data/config/locales/lt.yml +1780 -0
- data/config/locales/lv.yml +0 -22
- data/config/locales/nl.yml +3 -25
- data/config/locales/no.yml +1 -23
- data/config/locales/oc-FR.yml +1 -0
- data/config/locales/pl.yml +50 -23
- data/config/locales/pt-BR.yml +3 -25
- data/config/locales/pt.yml +2 -24
- data/config/locales/ro-RO.yml +1 -23
- data/config/locales/ru.yml +0 -5
- data/config/locales/sk.yml +1 -26
- data/config/locales/sv.yml +2 -23
- data/config/locales/tr-TR.yml +2 -24
- data/config/locales/uk.yml +1 -2
- data/config/locales/zh-CN.yml +2 -24
- data/config/routes.rb +20 -2
- data/lib/decidim/attributes/localized_date.rb +9 -1
- data/lib/decidim/attributes/time_with_zone.rb +13 -1
- data/lib/decidim/content_parsers/hashtag_parser.rb +1 -1
- data/lib/decidim/core/engine.rb +1 -6
- data/lib/decidim/core/test/shared_examples/mcell_examples.rb +17 -0
- data/lib/decidim/core/test/shared_examples/resource_endorsed_event_examples.rb +60 -0
- data/lib/decidim/core/test/shared_examples/versions_controller_examples.rb +40 -0
- data/lib/decidim/core/test/shared_examples/with_endorsable_permissions_examples.rb +1 -1
- data/lib/decidim/core/test.rb +3 -0
- data/lib/decidim/core/version.rb +1 -1
- data/lib/decidim/events/simple_event.rb +8 -1
- data/lib/decidim/form_builder.rb +8 -1
- data/lib/decidim/has_resource_permission.rb +0 -2
- data/lib/decidim/map/provider/dynamic_map/here.rb +46 -1
- data/lib/decidim/nicknamizable.rb +1 -1
- data/lib/decidim/resourceable.rb +5 -4
- data/lib/decidim/settings_manifest.rb +1 -1
- data/lib/decidim/translatable_attributes.rb +8 -1
- metadata +14 -8
- data/app/packs/images/decidim/gamification/badges/decidim_gamification_badges_invitations.svg +0 -1
- data/app/views/decidim/devise/registrations/edit.html.erb +0 -41
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1863adec9ae8ce9d4d528f247a2f18561de3618eacb84e50c03fae6a89fb2d6e
|
4
|
+
data.tar.gz: 032b9cce96757af5e5d87d687f64231a1630fdc003973b3b16cc4044f7f667bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 44579a2195e6d79ca6e5d78a1695815c4db2d2dd850b9a94e819d5219cda61cad771aa172ad99628bb9460e79a99b02fa4cd72fb377649b565b22ad010eb6924
|
7
|
+
data.tar.gz: 959d4d1552af3fd6b25fb1fb27292d8d107c260bedddd5a49dce829f6271772fd8a2c043d401cdc5061a615e1de71157d0b084eeab3b794e9aa3373451ad7ef8
|
@@ -39,7 +39,7 @@ module Decidim::Amendable
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def proposal_link(resource = model.amendable, text = nil)
|
42
|
-
text ||= %(<strong>#{present(model.amendable).title}</strong>)
|
42
|
+
text ||= %(<strong>#{decidim_sanitize(present(model.amendable).title, strip_tags: true)}</strong>)
|
43
43
|
link_to resource_locator(resource).path do
|
44
44
|
text
|
45
45
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<section class="section">
|
2
|
-
<div class="expanded hero" style="background-image:url(<%= background_image %>);">
|
2
|
+
<div class="expanded hero" style="background-image:url('<%= background_image %>');">
|
3
3
|
<div class="hero__container">
|
4
4
|
<div class="row">
|
5
5
|
<div class="columns small-centered medium-6 text-center">
|
@@ -1,4 +1,4 @@
|
|
1
|
-
<section class="extended hero home-section" style="background-image:url(<%= background_image %>);">
|
1
|
+
<section class="extended hero home-section" style="background-image:url('<%= background_image %>');">
|
2
2
|
<div class="hero__container">
|
3
3
|
<div class="row">
|
4
4
|
<div class="columns small-centered large-10">
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<section class="extended highligted-content-banner home-section"
|
2
|
-
style="background-image:url(<%= current_organization.attached_uploader(:highlighted_content_banner_image).path %>);">
|
2
|
+
style="background-image:url('<%= current_organization.attached_uploader(:highlighted_content_banner_image).path %>');">
|
3
3
|
<div class="highligted-content-banner__container">
|
4
4
|
<div class="row">
|
5
5
|
<div class="columns large-10">
|
@@ -10,6 +10,10 @@ module Decidim
|
|
10
10
|
helper Decidim::TraceabilityHelper
|
11
11
|
helper_method :current_version, :versioned_resource
|
12
12
|
|
13
|
+
def show
|
14
|
+
raise ActionController::RoutingError, "Not found" unless current_version
|
15
|
+
end
|
16
|
+
|
13
17
|
private
|
14
18
|
|
15
19
|
# Overwrite this method in your controller to define how to find the
|
@@ -19,7 +19,7 @@ module Decidim
|
|
19
19
|
# invitation. Using the param `invite_redirect` we can redirect the user
|
20
20
|
# to a custom path after it has accepted the invitation.
|
21
21
|
def after_accept_path_for(resource)
|
22
|
-
|
22
|
+
invite_redirect_path || after_sign_in_path_for(resource)
|
23
23
|
end
|
24
24
|
|
25
25
|
# When a managed user accepts the invitation is promoted to non-managed user.
|
@@ -30,7 +30,6 @@ module Decidim
|
|
30
30
|
resource.update!(newsletter_notifications_at: Time.current) if update_resource_params[:newsletter_notifications]
|
31
31
|
resource.update!(managed: false) if resource.managed?
|
32
32
|
resource.update!(accepted_tos_version: resource.organization.tos_version)
|
33
|
-
Decidim::Gamification.increment_score(resource.invited_by, :invitations) if resource.invited_by
|
34
33
|
end
|
35
34
|
|
36
35
|
resource
|
@@ -38,6 +37,14 @@ module Decidim
|
|
38
37
|
|
39
38
|
protected
|
40
39
|
|
40
|
+
def invite_redirect_path
|
41
|
+
path = params[:invite_redirect]
|
42
|
+
return unless path
|
43
|
+
return unless path.starts_with?(%r{^/[a-z0-9]+})
|
44
|
+
|
45
|
+
path
|
46
|
+
end
|
47
|
+
|
41
48
|
def configure_permitted_parameters
|
42
49
|
devise_parameter_sanitizer.permit(:accept_invitation, keys: [:nickname, :tos_agreement, :newsletter_notifications])
|
43
50
|
end
|
@@ -37,7 +37,7 @@ module Decidim
|
|
37
37
|
end
|
38
38
|
|
39
39
|
on(:invalid) do
|
40
|
-
flash.now[:alert] = @form.errors
|
40
|
+
flash.now[:alert] = @form.errors.full_messages.join(", ") if @form.errors.full_messages.any?
|
41
41
|
render :new
|
42
42
|
end
|
43
43
|
end
|
@@ -58,6 +58,10 @@ module Decidim
|
|
58
58
|
super(hash)
|
59
59
|
resource.organization = current_organization
|
60
60
|
end
|
61
|
+
|
62
|
+
def devise_mapping
|
63
|
+
::Devise.mappings[:user]
|
64
|
+
end
|
61
65
|
end
|
62
66
|
end
|
63
67
|
end
|
@@ -19,9 +19,9 @@ module Decidim
|
|
19
19
|
attribute :personal_url
|
20
20
|
attribute :about
|
21
21
|
|
22
|
-
validates :name, presence: true
|
23
|
-
validates :email, presence: true,
|
24
|
-
validates :nickname, presence: true, format: Decidim::User::REGEXP_NICKNAME
|
22
|
+
validates :name, presence: true, format: { with: Decidim::User::REGEXP_NAME }
|
23
|
+
validates :email, presence: true, "valid_email_2/email": { disposable: true }
|
24
|
+
validates :nickname, presence: true, format: { with: Decidim::User::REGEXP_NICKNAME }
|
25
25
|
|
26
26
|
validates :nickname, length: { maximum: Decidim::User.nickname_max_length, allow_blank: true }
|
27
27
|
validates :password, confirmation: true
|
@@ -50,7 +50,7 @@ module Decidim
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def unique_email
|
53
|
-
return true if Decidim::
|
53
|
+
return true if Decidim::UserBaseEntity.where(
|
54
54
|
organization: context.current_organization,
|
55
55
|
email: email
|
56
56
|
).where.not(id: context.current_user.id).empty?
|
@@ -60,9 +60,10 @@ module Decidim
|
|
60
60
|
end
|
61
61
|
|
62
62
|
def unique_nickname
|
63
|
-
return true if Decidim::
|
64
|
-
|
65
|
-
|
63
|
+
return true if Decidim::UserBaseEntity.where(
|
64
|
+
"decidim_organization_id = ? AND LOWER(nickname) = ? ",
|
65
|
+
context.current_organization.id,
|
66
|
+
nickname.downcase
|
66
67
|
).where.not(id: context.current_user.id).empty?
|
67
68
|
|
68
69
|
errors.add :nickname, :taken
|
@@ -66,7 +66,8 @@ module Decidim
|
|
66
66
|
errors = amendable_form_errors.details[key] - @original_form.errors.details[key]
|
67
67
|
|
68
68
|
errors.map do |hash|
|
69
|
-
|
69
|
+
error = hash.delete(:error)
|
70
|
+
@amendable_form.errors.add(key, error, **hash) unless @amendable_form.errors.details[key].include?(error: error)
|
70
71
|
end
|
71
72
|
end
|
72
73
|
end
|
@@ -14,9 +14,9 @@ module Decidim
|
|
14
14
|
attribute :tos_agreement, Boolean
|
15
15
|
attribute :current_locale, String
|
16
16
|
|
17
|
-
validates :name, presence: true
|
18
|
-
validates :nickname, presence: true, format:
|
19
|
-
validates :email, presence: true,
|
17
|
+
validates :name, presence: true, format: { with: Decidim::User::REGEXP_NAME }
|
18
|
+
validates :nickname, presence: true, format: { with: Decidim::User::REGEXP_NICKNAME }, length: { maximum: Decidim::User.nickname_max_length }
|
19
|
+
validates :email, presence: true, "valid_email_2/email": { disposable: true }
|
20
20
|
validates :password, confirmation: true
|
21
21
|
validates :password, password: { name: :name, email: :email, username: :nickname }
|
22
22
|
validates :password_confirmation, presence: true
|
@@ -35,11 +35,17 @@ module Decidim
|
|
35
35
|
private
|
36
36
|
|
37
37
|
def email_unique_in_organization
|
38
|
-
errors.add :email, :taken if
|
38
|
+
errors.add :email, :taken if valid_users.find_by(email: email, organization: current_organization).present?
|
39
39
|
end
|
40
40
|
|
41
41
|
def nickname_unique_in_organization
|
42
|
-
|
42
|
+
return false unless nickname
|
43
|
+
|
44
|
+
errors.add :nickname, :taken if valid_users.find_by("LOWER(nickname)= ? AND decidim_organization_id = ?", nickname.downcase, current_organization.id).present?
|
45
|
+
end
|
46
|
+
|
47
|
+
def valid_users
|
48
|
+
UserBaseEntity.where(invitation_token: nil)
|
43
49
|
end
|
44
50
|
|
45
51
|
def no_pending_invitations_exist
|
@@ -24,7 +24,11 @@ module Decidim
|
|
24
24
|
remote: true,
|
25
25
|
html: { id: nil }.merge(html_options)
|
26
26
|
) do |form|
|
27
|
-
|
27
|
+
# Cannot use `concat()` here because it's not available in cells
|
28
|
+
inner = []
|
29
|
+
inner << hidden_field_tag("per_page", params[:per_page], id: nil) if params[:per_page]
|
30
|
+
inner << capture { yield form }
|
31
|
+
inner.join.html_safe
|
28
32
|
end
|
29
33
|
end
|
30
34
|
end
|
@@ -5,6 +5,7 @@ module Decidim
|
|
5
5
|
# a events are received.
|
6
6
|
class NotificationMailer < Decidim::ApplicationMailer
|
7
7
|
helper Decidim::ResourceHelper
|
8
|
+
helper Decidim::SanitizeHelper
|
8
9
|
|
9
10
|
def event_received(event, event_class_name, resource, user, user_role, extra) # rubocop:disable Metrics/ParameterLists
|
10
11
|
with_user(user) do
|
@@ -130,16 +130,16 @@ module Decidim
|
|
130
130
|
end
|
131
131
|
|
132
132
|
# Whether this activity or log is visible for a given user (can also be nil)
|
133
|
-
#
|
134
|
-
# Returns a True/False.
|
135
133
|
def visible_for?(user)
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
134
|
+
resource_lazy.present? &&
|
135
|
+
participatory_space_lazy.present? &&
|
136
|
+
!resource_lazy.try(:deleted?) &&
|
137
|
+
!resource_lazy.try(:hidden?) &&
|
138
|
+
(!resource_lazy.respond_to?(:can_participate?) || resource_lazy.try(:can_participate?, user))
|
139
|
+
rescue NameError => e
|
140
|
+
Rails.logger.warn "Failed resource for #{self.class.name}(id=#{id}): #{e.message}"
|
141
|
+
|
142
|
+
false
|
143
143
|
end
|
144
144
|
end
|
145
145
|
end
|
@@ -30,6 +30,7 @@ module Decidim
|
|
30
30
|
|
31
31
|
scope :blocked, -> { where(blocked: true) }
|
32
32
|
scope :not_blocked, -> { where(blocked: false) }
|
33
|
+
scope :available, -> { where(deleted_at: nil, blocked: false, managed: false) }
|
33
34
|
|
34
35
|
# Public: Returns a collection with all the public entities this user is following.
|
35
36
|
#
|
@@ -0,0 +1,143 @@
|
|
1
|
+
/* eslint max-lines: ["error", 350] */
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Quill clipboard utilities
|
5
|
+
*
|
6
|
+
* Copyright (c) 2017, Slab
|
7
|
+
* Copyright (c) 2014, Jason Chen
|
8
|
+
* Copyright (c) 2013, salesforce.com
|
9
|
+
* BSD 3-Clause "New" or "Revised" License
|
10
|
+
*
|
11
|
+
* Extends the original version from https://github.com/quilljs/quill
|
12
|
+
* Relevant parts converted from TypeScript to JavaScript
|
13
|
+
*/
|
14
|
+
|
15
|
+
import CodeBlock from "quill/formats/code";
|
16
|
+
import { matchNewline, matchBreak, deltaEndsWith, traverse } from "src/decidim/editor/clipboard_utilities";
|
17
|
+
|
18
|
+
const Delta = Quill.import("delta");
|
19
|
+
const Clipboard = Quill.import("modules/clipboard");
|
20
|
+
|
21
|
+
/**
|
22
|
+
* Pasting bold text is broken in Quill as described at:
|
23
|
+
* https://github.com/quilljs/quill/issues/306
|
24
|
+
*
|
25
|
+
* The reason is that the `<strong>` nodes are not recognized as bold types.
|
26
|
+
* This override fixes the issue by introducing parts of the newer Quill code
|
27
|
+
* at GitHub and defining the `<strong>` tags as bold tags.
|
28
|
+
*/
|
29
|
+
export default class ClipboardOverride extends Clipboard {
|
30
|
+
constructor(quill, options) {
|
31
|
+
super(quill, options);
|
32
|
+
this.overrideMatcher("b", "b, strong");
|
33
|
+
this.overrideMatcher("br", "br", matchBreak);
|
34
|
+
|
35
|
+
// Change the matchNewLine matchers to the newer version
|
36
|
+
this.matchers[1][1] = matchNewline;
|
37
|
+
this.matchers[3][1] = matchNewline;
|
38
|
+
|
39
|
+
// Remove `matchSpacing` as that is also removed in the newer versions.
|
40
|
+
this.removeMatcher(Node.ELEMENT_NODE, "matchSpacing");
|
41
|
+
}
|
42
|
+
|
43
|
+
overrideMatcher(originalSelector, newSelector, newMatcher = null) {
|
44
|
+
const idx = this.matchers.findIndex((item) => item[0] === originalSelector);
|
45
|
+
if (idx >= 0) {
|
46
|
+
this.matchers[idx][0] = newSelector;
|
47
|
+
if (newMatcher) {
|
48
|
+
this.matchers[idx][1] = newMatcher;
|
49
|
+
}
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
removeMatcher(selector, matcherName) {
|
54
|
+
const idx = this.matchers.findIndex((item) => item[0] === selector && item[1].name === matcherName);
|
55
|
+
if (idx >= 0) {
|
56
|
+
this.matchers.splice(idx, 1);
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
onPaste(ev) {
|
61
|
+
if (ev.defaultPrevented || !this.quill.isEnabled()) {
|
62
|
+
return;
|
63
|
+
}
|
64
|
+
ev.preventDefault();
|
65
|
+
const range = this.quill.getSelection(true);
|
66
|
+
if (range === null) {
|
67
|
+
return;
|
68
|
+
}
|
69
|
+
const html = ev.clipboardData.getData("text/html");
|
70
|
+
const text = ev.clipboardData.getData("text/plain");
|
71
|
+
const files = Array.from(ev.clipboardData.files || []);
|
72
|
+
if (!html && files.length > 0) {
|
73
|
+
this.quill.uploader.upload(range, files);
|
74
|
+
return;
|
75
|
+
}
|
76
|
+
if (html && files.length > 0) {
|
77
|
+
const doc = new DOMParser().parseFromString(html, "text/html");
|
78
|
+
if (
|
79
|
+
doc.body.childElementCount === 1 &&
|
80
|
+
doc.body.firstElementChild.tagName === "IMG"
|
81
|
+
) {
|
82
|
+
this.quill.uploader.upload(range, files);
|
83
|
+
return;
|
84
|
+
}
|
85
|
+
}
|
86
|
+
this.onPasteRange(range, { html, text });
|
87
|
+
}
|
88
|
+
|
89
|
+
onPasteRange(range, { text, html }) {
|
90
|
+
const formats = this.quill.getFormat(range.index);
|
91
|
+
const pastedDelta = this.convertPaste({ text, html }, formats);
|
92
|
+
// debug.log('onPaste", pastedDelta, { text, html });
|
93
|
+
const delta = new Delta().retain(range.index).delete(range.length).concat(pastedDelta);
|
94
|
+
this.quill.updateContents(delta, Quill.sources.USER);
|
95
|
+
// range.length contributes to delta.length()
|
96
|
+
this.quill.setSelection(
|
97
|
+
delta.length() - range.length,
|
98
|
+
Quill.sources.SILENT,
|
99
|
+
);
|
100
|
+
this.quill.scrollIntoView();
|
101
|
+
}
|
102
|
+
|
103
|
+
convertPaste({ html, text }, formats = {}) {
|
104
|
+
if (formats[CodeBlock.blotName]) {
|
105
|
+
return new Delta().insert(text, {
|
106
|
+
[CodeBlock.blotName]: formats[CodeBlock.blotName]
|
107
|
+
});
|
108
|
+
}
|
109
|
+
if (!html) {
|
110
|
+
return new Delta().insert(text || "");
|
111
|
+
}
|
112
|
+
const delta = this.convertPasteHTML(html);
|
113
|
+
// Remove trailing newline
|
114
|
+
if (
|
115
|
+
deltaEndsWith(delta, "\n") &&
|
116
|
+
(delta.ops[delta.ops.length - 1].attributes === null || formats.table)
|
117
|
+
) {
|
118
|
+
return delta.compose(new Delta().retain(delta.length() - 1).delete(1));
|
119
|
+
}
|
120
|
+
return delta;
|
121
|
+
}
|
122
|
+
|
123
|
+
convertPasteHTML(html) {
|
124
|
+
const doc = new DOMParser().parseFromString(html, "text/html");
|
125
|
+
const container = doc.body;
|
126
|
+
const nodeMatches = new WeakMap();
|
127
|
+
const [elementMatchers, textMatchers] = this.prepareMatching(
|
128
|
+
container,
|
129
|
+
nodeMatches
|
130
|
+
);
|
131
|
+
return traverse(
|
132
|
+
this.quill.scroll,
|
133
|
+
container,
|
134
|
+
elementMatchers,
|
135
|
+
textMatchers,
|
136
|
+
nodeMatches
|
137
|
+
);
|
138
|
+
}
|
139
|
+
}
|
140
|
+
|
141
|
+
// Disable warning messages from overwritting modules
|
142
|
+
Quill.debug("error");
|
143
|
+
Quill.register({"modules/clipboard": ClipboardOverride}, true);
|
@@ -0,0 +1,119 @@
|
|
1
|
+
import { BlockEmbed } from "quill/blots/block";
|
2
|
+
|
3
|
+
const Delta = Quill.import("delta");
|
4
|
+
const Parchment = Quill.import("parchment");
|
5
|
+
|
6
|
+
// Newer version used only for the pasting, not compatible with the version of
|
7
|
+
// Quill in use.
|
8
|
+
const traverse = (scroll, node, elementMatchers, textMatchers, nodeMatches) => { // eslint-disable-line max-params
|
9
|
+
// Post-order
|
10
|
+
if (node.nodeType === node.TEXT_NODE) {
|
11
|
+
return textMatchers.reduce((delta, matcher) => {
|
12
|
+
return matcher(node, delta, scroll);
|
13
|
+
}, new Delta());
|
14
|
+
}
|
15
|
+
if (node.nodeType === node.ELEMENT_NODE) {
|
16
|
+
return Array.from(node.childNodes || []).reduce((delta, childNode) => {
|
17
|
+
let childrenDelta = traverse(
|
18
|
+
scroll,
|
19
|
+
childNode,
|
20
|
+
elementMatchers,
|
21
|
+
textMatchers,
|
22
|
+
nodeMatches,
|
23
|
+
);
|
24
|
+
if (childNode.nodeType === node.ELEMENT_NODE) {
|
25
|
+
childrenDelta = elementMatchers.reduce((reducedDelta, matcher) => {
|
26
|
+
return matcher(childNode, reducedDelta, scroll);
|
27
|
+
}, childrenDelta);
|
28
|
+
childrenDelta = (nodeMatches.get(childNode) || []).reduce(
|
29
|
+
(reducedDelta, matcher) => {
|
30
|
+
return matcher(childNode, reducedDelta, scroll);
|
31
|
+
},
|
32
|
+
childrenDelta,
|
33
|
+
);
|
34
|
+
}
|
35
|
+
return delta.concat(childrenDelta);
|
36
|
+
}, new Delta());
|
37
|
+
}
|
38
|
+
return new Delta();
|
39
|
+
}
|
40
|
+
|
41
|
+
const deltaEndsWith = (delta, text) => {
|
42
|
+
let endText = "";
|
43
|
+
for (let idx = delta.ops.length - 1; idx >= 0 && endText.length < text.length; idx -= 1) {
|
44
|
+
const op = delta.ops[idx];
|
45
|
+
if (typeof op.insert !== "string") {
|
46
|
+
break;
|
47
|
+
}
|
48
|
+
endText = op.insert + endText;
|
49
|
+
}
|
50
|
+
return endText.slice(-1 * text.length) === text;
|
51
|
+
}
|
52
|
+
|
53
|
+
const isLine = (node) => {
|
54
|
+
if (node.childNodes.length === 0) {
|
55
|
+
// Exclude embed blocks
|
56
|
+
return false;
|
57
|
+
}
|
58
|
+
return [
|
59
|
+
"address", "article", "blockquote", "canvas", "dd", "div", "dl", "dt",
|
60
|
+
"fieldset", "figcaption", "figure", "footer", "form", "h1", "h2", "h3",
|
61
|
+
"h4", "h5", "h6", "header", "iframe", "li", "main", "nav", "ol", "output",
|
62
|
+
"p", "pre", "section", "table", "td", "tr", "ul", "video"
|
63
|
+
].includes(node.tagName.toLowerCase());
|
64
|
+
}
|
65
|
+
|
66
|
+
const matchNewLineScroll = (nextSibling, delta, scroll) => {
|
67
|
+
if (!scroll) {
|
68
|
+
return null;
|
69
|
+
}
|
70
|
+
|
71
|
+
const match = Parchment.query(nextSibling)
|
72
|
+
if (match && match.prototype instanceof BlockEmbed) {
|
73
|
+
return delta.insert("\n");
|
74
|
+
}
|
75
|
+
return null;
|
76
|
+
}
|
77
|
+
|
78
|
+
const matchNewline = (node, delta, scroll) => {
|
79
|
+
if (!deltaEndsWith(delta, "\n")) {
|
80
|
+
// When scroll is defined, it was initiated from the paste event. Otherwise
|
81
|
+
// it is a normal Quill initiated traversal which handles adding the line
|
82
|
+
// breaks already.
|
83
|
+
if (scroll && node.nodeType === node.ELEMENT_NODE && node.tagName === "BR") {
|
84
|
+
return delta.insert({"break": ""});
|
85
|
+
}
|
86
|
+
if (isLine(node)) {
|
87
|
+
return delta.insert("\n");
|
88
|
+
}
|
89
|
+
if (delta.length() > 0 && node.nextSibling) {
|
90
|
+
let { nextSibling } = node;
|
91
|
+
while (nextSibling !== null) {
|
92
|
+
if (isLine(nextSibling)) {
|
93
|
+
return delta.insert("\n");
|
94
|
+
}
|
95
|
+
const scrollMatch = matchNewLineScroll(nextSibling, delta, scroll);
|
96
|
+
if (scrollMatch) {
|
97
|
+
return scrollMatch;
|
98
|
+
}
|
99
|
+
nextSibling = nextSibling.firstChild;
|
100
|
+
}
|
101
|
+
}
|
102
|
+
}
|
103
|
+
return delta;
|
104
|
+
}
|
105
|
+
|
106
|
+
const matchBreak = (node, delta) => {
|
107
|
+
if (!deltaEndsWith(delta, "\n")) {
|
108
|
+
delta.insert({"break": ""});
|
109
|
+
}
|
110
|
+
return delta;
|
111
|
+
}
|
112
|
+
|
113
|
+
export {
|
114
|
+
traverse,
|
115
|
+
deltaEndsWith,
|
116
|
+
isLine,
|
117
|
+
matchNewline,
|
118
|
+
matchBreak
|
119
|
+
}
|
@@ -129,7 +129,6 @@ class ScrollOvderride extends Scroll {
|
|
129
129
|
Quill.register("blots/scroll", ScrollOvderride, true);
|
130
130
|
Parchment.register(ScrollOvderride);
|
131
131
|
|
132
|
-
|
133
132
|
export default function lineBreakButtonHandler(quill) {
|
134
133
|
let range = quill.selection.getRange()[0];
|
135
134
|
let currentLeaf = quill.getLeaf(range.index)[0];
|
@@ -167,13 +166,6 @@ Quill.register("modules/linebreak", (quill) => {
|
|
167
166
|
}
|
168
167
|
});
|
169
168
|
|
170
|
-
quill.clipboard.addMatcher("BR", (node) => {
|
171
|
-
if (node?.parentNode?.tagName === "A") {
|
172
|
-
return new Delta().insert("\n");
|
173
|
-
}
|
174
|
-
return new Delta().insert({"break": ""});
|
175
|
-
});
|
176
|
-
|
177
169
|
addEnterBindings(quill);
|
178
170
|
backspaceBindingsRangeAny(quill);
|
179
171
|
backspaceBindings(quill);
|
@@ -1,6 +1,7 @@
|
|
1
1
|
/* eslint-disable require-jsdoc */
|
2
2
|
|
3
3
|
import lineBreakButtonHandler from "src/decidim/editor/linebreak_module"
|
4
|
+
import "src/decidim/editor/clipboard_override"
|
4
5
|
import "src/decidim/vendor/image-resize.min"
|
5
6
|
import "src/decidim/vendor/image-upload.min"
|
6
7
|
|
@@ -10,6 +11,7 @@ export default function createQuillEditor(container) {
|
|
10
11
|
const toolbar = $(container).data("toolbar");
|
11
12
|
const disabled = $(container).data("disabled");
|
12
13
|
|
14
|
+
const allowedEmptyContentSelector = "iframe";
|
13
15
|
let quillToolbar = [
|
14
16
|
["bold", "italic", "underline", "linebreak"],
|
15
17
|
[{ list: "ordered" }, { list: "bullet" }],
|
@@ -93,10 +95,15 @@ export default function createQuillEditor(container) {
|
|
93
95
|
});
|
94
96
|
container.dispatchEvent(event);
|
95
97
|
|
96
|
-
if (text === "\n" || text === "\n\n") {
|
98
|
+
if ((text === "\n" || text === "\n\n") && quill.root.querySelectorAll(allowedEmptyContentSelector).length === 0) {
|
97
99
|
$input.val("");
|
98
100
|
} else {
|
99
|
-
|
101
|
+
const emptyParagraph = "<p><br></p>";
|
102
|
+
const cleanHTML = quill.root.innerHTML.replace(
|
103
|
+
new RegExp(`^${emptyParagraph}|${emptyParagraph}$`, "g"),
|
104
|
+
""
|
105
|
+
);
|
106
|
+
$input.val(cleanHTML);
|
100
107
|
}
|
101
108
|
});
|
102
109
|
// After editor is ready, linebreak_module deletes two extraneous new lines
|
@@ -33,7 +33,7 @@ import MapDragMarkerController from "src/decidim/map/controller/drag_marker"
|
|
33
33
|
* @param {Object} config The map configuration object.
|
34
34
|
* @returns {MapController} The controller for the map.
|
35
35
|
*/
|
36
|
-
|
36
|
+
const createMapController = function(mapId, config) {
|
37
37
|
if (config.type === "static") {
|
38
38
|
return new MapStaticController(mapId, config);
|
39
39
|
} else if (config.type === "drag-marker") {
|
@@ -42,3 +42,5 @@ export default function createMapController(mapId, config) {
|
|
42
42
|
|
43
43
|
return new MapMarkersController(mapId, config);
|
44
44
|
}
|
45
|
+
|
46
|
+
window.Decidim.createMapController = createMapController;
|
@@ -1,7 +1,7 @@
|
|
1
1
|
/* eslint-disable require-jsdoc */
|
2
2
|
|
3
3
|
import * as L from "leaflet";
|
4
|
-
import
|
4
|
+
import "src/decidim/map/factory"
|
5
5
|
|
6
6
|
/**
|
7
7
|
* @deprecated
|
@@ -60,7 +60,7 @@ const loadMap = (mapId, markersData) => {
|
|
60
60
|
legacyMapSupport($map);
|
61
61
|
|
62
62
|
const mapData = $map.data("decidim-map");
|
63
|
-
const ctrl = createMapController(mapId, mapData);
|
63
|
+
const ctrl = window.Decidim.createMapController(mapId, mapData);
|
64
64
|
const map = ctrl.load();
|
65
65
|
|
66
66
|
L.tileLayer.here(mapData.tileLayer).addTo(map);
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import
|
1
|
+
import "src/decidim/map/factory"
|
2
2
|
|
3
3
|
$(() => {
|
4
4
|
// Load the map controller factory method in the document.ready handler to
|
@@ -20,7 +20,7 @@ $(() => {
|
|
20
20
|
}
|
21
21
|
|
22
22
|
const mapConfig = $map.data("decidim-map");
|
23
|
-
const ctrl = createMapController(mapId, mapConfig);
|
23
|
+
const ctrl = window.Decidim.createMapController(mapId, mapConfig);
|
24
24
|
const map = ctrl.load();
|
25
25
|
|
26
26
|
$map.data("map", map);
|