decidim-core 0.27.2 → 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.

Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/upload_modal/files.erb +1 -0
  3. data/app/cells/decidim/upload_modal_cell.rb +14 -4
  4. data/app/commands/decidim/attachment_methods.rb +20 -2
  5. data/app/commands/decidim/create_registration.rb +1 -0
  6. data/app/commands/decidim/gallery_methods.rb +1 -1
  7. data/app/commands/decidim/update_account.rb +1 -0
  8. data/app/commands/decidim/update_password.rb +2 -0
  9. data/app/controllers/decidim/devise/sessions_controller.rb +18 -2
  10. data/app/controllers/decidim/links_controller.rb +8 -11
  11. data/app/helpers/decidim/cells_helper.rb +1 -0
  12. data/app/helpers/decidim/external_domain_helper.rb +14 -3
  13. data/app/helpers/decidim/sanitize_helper.rb +3 -2
  14. data/app/models/decidim/scope_type.rb +24 -0
  15. data/app/packs/src/decidim/direct_uploads/upload_modal.js +0 -1
  16. data/app/packs/src/decidim/editor/clipboard_override.js +6 -2
  17. data/app/packs/src/decidim/editor.js +63 -33
  18. data/app/packs/stylesheets/decidim/modules/_buttons.scss +10 -6
  19. data/app/packs/stylesheets/decidim/modules/_cards.scss +1 -1
  20. data/app/packs/stylesheets/decidim/modules/_comments.scss +24 -0
  21. data/app/packs/stylesheets/decidim/modules/_input-gallery.scss +2 -1
  22. data/app/packs/stylesheets/decidim/modules/_upload_modal.scss +0 -4
  23. data/app/packs/stylesheets/decidim/vizzs/_linechart.scss +2 -2
  24. data/app/packs/stylesheets/decidim/vizzs/_rowchart.scss +2 -2
  25. data/app/presenters/decidim/notification_presenter.rb +1 -1
  26. data/app/presenters/decidim/notification_to_mailer_presenter.rb +1 -0
  27. data/app/presenters/decidim/user_group_presenter.rb +1 -1
  28. data/app/presenters/decidim/user_presenter.rb +1 -1
  29. data/app/scrubbers/decidim/admin_input_scrubber.rb +3 -1
  30. data/app/scrubbers/decidim/user_input_scrubber.rb +30 -1
  31. data/app/services/decidim/traceability.rb +1 -0
  32. data/app/validators/uploader_image_dimensions_validator.rb +22 -2
  33. data/app/views/decidim/links/_invalid_url_modal.html.erb +17 -0
  34. data/app/views/decidim/links/_modal.html.erb +1 -1
  35. data/app/views/decidim/links/invalid_url.js.erb +24 -0
  36. data/app/views/decidim/links/new.html.erb +1 -1
  37. data/app/views/decidim/messaging/conversations/_conversation.html.erb +1 -5
  38. data/config/locales/ar.yml +566 -3
  39. data/config/locales/bg.yml +1 -4
  40. data/config/locales/ca.yml +21 -17
  41. data/config/locales/cs.yml +22 -30
  42. data/config/locales/da.yml +4 -0
  43. data/config/locales/de.yml +4 -22
  44. data/config/locales/el.yml +2 -4
  45. data/config/locales/en.yml +16 -13
  46. data/config/locales/eo.yml +2 -1
  47. data/config/locales/es-MX.yml +20 -16
  48. data/config/locales/es-PY.yml +20 -16
  49. data/config/locales/es.yml +21 -17
  50. data/config/locales/et.yml +4 -0
  51. data/config/locales/eu.yml +149 -58
  52. data/config/locales/fa-IR.yml +1 -0
  53. data/config/locales/fi-plain.yml +1 -18
  54. data/config/locales/fi.yml +19 -15
  55. data/config/locales/fr-CA.yml +23 -16
  56. data/config/locales/fr.yml +21 -14
  57. data/config/locales/ga-IE.yml +1 -0
  58. data/config/locales/gl.yml +0 -21
  59. data/config/locales/gn-PY.yml +4 -0
  60. data/config/locales/hr.yml +4 -0
  61. data/config/locales/hu.yml +64 -23
  62. data/config/locales/id-ID.yml +2 -4
  63. data/config/locales/is-IS.yml +2 -1
  64. data/config/locales/it.yml +1 -5
  65. data/config/locales/ja.yml +10 -21
  66. data/config/locales/ka-GE.yml +4 -0
  67. data/config/locales/kaa.yml +1 -0
  68. data/config/locales/lb.yml +0 -4
  69. data/config/locales/lt.yml +0 -34
  70. data/config/locales/lv.yml +0 -3
  71. data/config/locales/nl.yml +1 -23
  72. data/config/locales/no.yml +1 -23
  73. data/config/locales/oc-FR.yml +3 -0
  74. data/config/locales/pl.yml +0 -34
  75. data/config/locales/pt-BR.yml +2 -6
  76. data/config/locales/pt.yml +0 -4
  77. data/config/locales/ro-RO.yml +36 -4
  78. data/config/locales/ru.yml +1 -3
  79. data/config/locales/sk.yml +3 -5
  80. data/config/locales/sl.yml +1 -0
  81. data/config/locales/sr-CS.yml +2 -0
  82. data/config/locales/sv.yml +1 -23
  83. data/config/locales/tr-TR.yml +3 -7
  84. data/config/locales/uk.yml +1 -3
  85. data/config/locales/zh-CN.yml +0 -4
  86. data/config/locales/zh-TW.yml +1872 -0
  87. data/lib/decidim/asset_router/pipeline.rb +2 -0
  88. data/lib/decidim/core/test/shared_examples/comments_examples.rb +36 -0
  89. data/lib/decidim/core/test/shared_examples/digest_mail_examples.rb +33 -0
  90. data/lib/decidim/core/test/shared_examples/editor_shared_examples.rb +5 -4
  91. data/lib/decidim/core/test/shared_examples/rich_text_editor_examples.rb +7 -3
  92. data/lib/decidim/core/test.rb +1 -0
  93. data/lib/decidim/core/version.rb +1 -1
  94. data/lib/decidim/form_builder.rb +4 -3
  95. data/lib/decidim/publicable.rb +4 -0
  96. metadata +12 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 391ba34f55c860208f7644dd10b1b3e2b6465ed45b9e9f9fd194ce1036516995
4
- data.tar.gz: 93636f8d556d73547fcdff45986fcc85ba4a22e9c399fdeca0c550ee6c241363
3
+ metadata.gz: d6577740d87b562e24f541c4723480ec774239f69a389d4434536ab72be3b116
4
+ data.tar.gz: 8366df277375b9fadc5c96b4a9e647a0300057d7148eafcb672f3a57d3abd55f
5
5
  SHA512:
6
- metadata.gz: 157c005fbea98fe374586f91f15f17bc54450d6e4b6f7136d51582d5cae7af9442d250edbbf4fe2a4308aac902ba0eb28c49681153dcda6b6948f8589414e930
7
- data.tar.gz: 8ebc9089ad86980956ded3024d490f419bf8015ae0dc4b3fe6e003d415010de8ed28751bd0c168cf526c8a17f5db50467c9223242c2150cf600304990e1eda04
6
+ metadata.gz: f6f4e9e7bcf14ccc5943ec41d4468338d1cf62aec7d37b296d0fa37a3c1c7bc97711b06579d10b32167ce291b8d664989f90eecfcba9a94f228ff60e7e8698e0
7
+ data.tar.gz: 4fcd0dacd12572c3a4b404709ec632dcc6c5cc964dfd9dc29d92a4dd03da2b10ddc3e5df57a6ed91cb3f90e0dac70c4668ba89040f8cf5788e34d1f8e857d3b8
@@ -30,6 +30,7 @@
30
30
  add_attribute: add_attribute,
31
31
  resource_name: resource_name,
32
32
  resource_class: resource_class,
33
+ required: required?,
33
34
  optional: optional,
34
35
  max_file_size: max_file_size,
35
36
  multiple: multiple,
@@ -5,6 +5,7 @@ module Decidim
5
5
  class UploadModalCell < Decidim::ViewModel
6
6
  include Cell::ViewModel::Partial
7
7
  include ERB::Util
8
+ include Decidim::SanitizeHelper
8
9
 
9
10
  alias form model
10
11
 
@@ -29,7 +30,7 @@ module Decidim
29
30
  end
30
31
 
31
32
  def label
32
- options[:label]
33
+ form.send(:custom_label, attribute, options[:label], { required: required?, for: nil })
33
34
  end
34
35
 
35
36
  def button_label
@@ -72,8 +73,17 @@ module Decidim
72
73
  options[:multiple] || false
73
74
  end
74
75
 
76
+ # @deprecated Please use `required?` instead.
77
+ #
78
+ # NOTE: When this is removed, also the `optional` option should be removed.
75
79
  def optional
76
- options[:optional]
80
+ !required?
81
+ end
82
+
83
+ def required?
84
+ return !options[:optional] if options.has_key?(:optional)
85
+
86
+ options[:required] == true
77
87
  end
78
88
 
79
89
  # By default Foundation adds form errors next to input, but since input is in the modal
@@ -81,7 +91,7 @@ module Decidim
81
91
  # This should only be necessary when file is required by the form.
82
92
  def input_validation_field
83
93
  object_name = form.object.present? ? "#{form.object.model_name.param_key}[#{add_attribute}_validation]" : "#{add_attribute}_validation"
84
- input = check_box_tag object_name, 1, attachments.present?, class: "hide", label: false, required: !optional
94
+ input = check_box_tag object_name, 1, attachments.present?, class: "hide", label: false, required: required?
85
95
  message = form.send(:abide_error_element, add_attribute) + form.send(:error_and_help_text, add_attribute)
86
96
  input + message
87
97
  end
@@ -142,7 +152,7 @@ module Decidim
142
152
  def title_for(attachment)
143
153
  return unless has_title?
144
154
 
145
- translated_attribute(attachment.title)
155
+ decidim_html_escape(decidim_sanitize(translated_attribute(attachment.title)))
146
156
  end
147
157
 
148
158
  def truncated_file_name_for(attachment, max_length = 31)
@@ -12,8 +12,8 @@ module Decidim
12
12
  @attachment = Attachment.new(
13
13
  title: { I18n.locale => @form.attachment.title },
14
14
  attached_to: attached_to,
15
- file: @form.attachment.file, # Define attached_to before this
16
- content_type: @form.attachment.file.content_type
15
+ file: signed_id_for(@form.attachment.file),
16
+ content_type: content_type_for(@form.attachment.file)
17
17
  )
18
18
  end
19
19
 
@@ -53,5 +53,23 @@ module Decidim
53
53
  def delete_attachment?
54
54
  @form.attachment&.delete_file.present?
55
55
  end
56
+
57
+ protected
58
+
59
+ def signed_id_for(attachment)
60
+ return attachment[:file] if attachment.is_a?(Hash)
61
+
62
+ attachment
63
+ end
64
+
65
+ def content_type_for(attachment)
66
+ return attachment.content_type if attachment.instance_of?(ActionDispatch::Http::UploadedFile)
67
+
68
+ blob(signed_id_for(attachment)).content_type
69
+ end
70
+
71
+ def blob(signed_id)
72
+ ActiveStorage::Blob.find_signed(signed_id)
73
+ end
56
74
  end
57
75
  end
@@ -41,6 +41,7 @@ module Decidim
41
41
  nickname: form.nickname,
42
42
  password: form.password,
43
43
  password_confirmation: form.password_confirmation,
44
+ password_updated_at: Time.current,
44
45
  organization: form.current_organization,
45
46
  tos_agreement: form.tos_agreement,
46
47
  newsletter_notifications_at: form.newsletter_at,
@@ -24,7 +24,7 @@ module Decidim
24
24
  end
25
25
 
26
26
  def update_attachment_title_for(photo)
27
- Decidim::Attachment.find(photo[:id]).update(title: title_for(photo))
27
+ Decidim::Attachment.find(photo[:id]).update(title: photos_title(photo))
28
28
  end
29
29
 
30
30
  def image?(signed_id)
@@ -55,6 +55,7 @@ module Decidim
55
55
 
56
56
  @user.password = @form.password
57
57
  @user.password_confirmation = @form.password_confirmation
58
+ @user.password_updated_at = Time.current
58
59
  end
59
60
 
60
61
  def notify_followers
@@ -16,6 +16,8 @@ module Decidim
16
16
  return broadcast(:invalid) if form.invalid?
17
17
 
18
18
  user.password = form.password
19
+ user.password_confirmation = form.password
20
+ user.password_updated_at = Time.current
19
21
 
20
22
  if user.save
21
23
  broadcast(:ok)
@@ -6,9 +6,25 @@ module Decidim
6
6
  class SessionsController < ::Devise::SessionsController
7
7
  include Decidim::DeviseControllers
8
8
 
9
- # rubocop: disable Rails/LexicallyScopedActionFilter
10
9
  before_action :check_sign_in_enabled, only: :create
11
- # rubocop: enable Rails/LexicallyScopedActionFilter
10
+
11
+ def create
12
+ super do |user|
13
+ if user.admin?
14
+ # Check that the admin password passes the validation and clear the
15
+ # `password_updated_at` field when the password is weak to force a
16
+ # password update on the user.
17
+ #
18
+ # Handles a case when the user registers through the registration
19
+ # form and they are promoted to an admin after that. In this case,
20
+ # the newly promoted admin user would otherwise have to change their
21
+ # password straight away even if they originally registered with a
22
+ # strong password.
23
+ validator = PasswordValidator.new({ attributes: :password })
24
+ user.update!(password_updated_at: nil) unless validator.validate_each(user, :password, sign_in_params[:password])
25
+ end
26
+ end
27
+ end
12
28
 
13
29
  def destroy
14
30
  current_user.invalidate_all_sessions!
@@ -21,24 +21,21 @@ module Decidim
21
21
 
22
22
  def invalid_url
23
23
  flash[:alert] = I18n.t("decidim.links.invalid_url")
24
- redirect_to decidim.root_path
24
+ if request.xhr?
25
+ render "invalid_url"
26
+ else
27
+ redirect_to decidim.root_path
28
+ end
25
29
  end
26
30
 
27
31
  def parse_url
32
+ raise Decidim::InvalidUrlError if params[:external_url].blank?
28
33
  raise Decidim::InvalidUrlError unless external_url
29
-
30
- parts = external_url.match %r{\A(([a-z]+):)?//([^/]+)(/.*)?\z}
31
- raise Decidim::InvalidUrlError unless parts
32
-
33
- @url_parts = {
34
- protocol: parts[1],
35
- domain: parts[3],
36
- path: parts[4]
37
- }
34
+ raise Decidim::InvalidUrlError unless %w(http https).include?(external_url.scheme)
38
35
  end
39
36
 
40
37
  def external_url
41
- @external_url ||= URI.parse(params[:external_url]).to_s
38
+ @external_url ||= URI.parse(params[:external_url])
42
39
  end
43
40
  end
44
41
  end
@@ -35,6 +35,7 @@ module Decidim
35
35
  end
36
36
 
37
37
  def user_flaggable?
38
+ return if (try(:profile_holder) || try(:profile_user) || try(:model)).try(:blocked)
38
39
  return unless context[:controller].try(:flaggable_controller?)
39
40
 
40
41
  true
@@ -3,10 +3,21 @@
3
3
  module Decidim
4
4
  module ExternalDomainHelper
5
5
  def highlight_domain
6
+ highlighted_domain = [
7
+ external_url.host,
8
+ (external_url.port && [80, 443].include?(external_url.port) ? "" : ":#{external_url.port}")
9
+ ].join
10
+
11
+ path = [
12
+ external_url.path,
13
+ (external_url.query ? "?#{external_url.query}" : ""),
14
+ (external_url.fragment ? "##{external_url.fragment}" : "")
15
+ ].join
16
+
6
17
  tag.div do
7
- content_tag(:span, "#{@url_parts[:protocol]}//") +
8
- content_tag(:span, @url_parts[:domain], class: "alert") +
9
- content_tag(:span, @url_parts[:path])
18
+ content_tag(:span, "#{external_url.scheme}://") +
19
+ content_tag(:span, highlighted_domain, class: "text-alert") +
20
+ content_tag(:span, path)
10
21
  end
11
22
  end
12
23
  end
@@ -112,9 +112,10 @@ module Decidim
112
112
  #
113
113
  # @return ActiveSupport::SafeBuffer
114
114
  def render_sanitized_content(resource, method)
115
- content = present(resource).send(method, links: true, strip_tags: !safe_content?)
115
+ content = present(resource).send(method, links: true, strip_tags: !try(:safe_content?))
116
116
 
117
- return decidim_sanitize(content, {}) unless safe_content?
117
+ return decidim_sanitize(content, {}) unless try(:safe_content?)
118
+ return decidim_sanitize_editor_admin(content, {}) if try(:safe_content_admin?)
118
119
 
119
120
  decidim_sanitize_editor(content)
120
121
  end
@@ -17,8 +17,32 @@ module Decidim
17
17
 
18
18
  validates :name, presence: true
19
19
 
20
+ before_destroy :detach_dynamic_associations
21
+
20
22
  def self.log_presenter_class_for(_log)
21
23
  Decidim::AdminLog::ScopeTypePresenter
22
24
  end
25
+
26
+ private
27
+
28
+ # This method detaches all records that may have association with the scope
29
+ # type. This cannot be done directly using the `dependent` option in the
30
+ # `has_many` relation in order to avoid tight coupling between the modules.
31
+ #
32
+ # This logic does not have to be applied to any classes that have been
33
+ # defined as `has_many` associations within this model already as they are
34
+ # already handled by the `dependent` option.
35
+ def detach_dynamic_associations
36
+ ActiveRecord::Base.descendants.each do |cls|
37
+ next if cls.abstract_class? || !cls.name&.match?(/^Decidim::/)
38
+ next if cls == self.class || cls == Decidim::Scope
39
+
40
+ cls.reflect_on_all_associations(:belongs_to).each do |ref|
41
+ next unless ref.options[:class_name] == self.class.name
42
+
43
+ cls.where(ref.options[:foreign_key] => id).update_all(ref.options[:foreign_key] => nil) # rubocop:disable Rails/SkipsModelValidations
44
+ end
45
+ end
46
+ end
23
47
  end
24
48
  end
@@ -16,7 +16,6 @@ export default class UploadModal {
16
16
  // - addAttribute - Field name / attribute of resource (e.g. avatar)
17
17
  // - resourceName - The resource to which the attribute belongs (e.g. user)
18
18
  // - resourceClass - Ruby class of the resource (e.g. Decidim::User)
19
- // - optional - Defines if file is optional
20
19
  // - multiple - Defines if multiple files can be uploaded
21
20
  // - titled - Defines if file(s) can have titles
22
21
  // - maxFileSize - Defines maximum file size in bytes
@@ -70,7 +70,9 @@ export default class ClipboardOverride extends Clipboard {
70
70
  const text = ev.clipboardData.getData("text/plain");
71
71
  const files = Array.from(ev.clipboardData.files || []);
72
72
  if (!html && files.length > 0) {
73
- this.quill.uploader.upload(range, files);
73
+ if (typeof this.quill.uploader !== "undefined") {
74
+ this.quill.uploader.upload(range, files);
75
+ }
74
76
  return;
75
77
  }
76
78
  if (html && files.length > 0) {
@@ -79,7 +81,9 @@ export default class ClipboardOverride extends Clipboard {
79
81
  doc.body.childElementCount === 1 &&
80
82
  doc.body.firstElementChild.tagName === "IMG"
81
83
  ) {
82
- this.quill.uploader.upload(range, files);
84
+ if (typeof this.quill.uploader !== "undefined") {
85
+ this.quill.uploader.upload(range, files);
86
+ }
83
87
  return;
84
88
  }
85
89
  }
@@ -1,11 +1,25 @@
1
1
  /* eslint-disable require-jsdoc */
2
2
 
3
- import lineBreakButtonHandler from "src/decidim/editor/linebreak_module"
4
- import "src/decidim/editor/clipboard_override"
5
- import "src/decidim/vendor/image-resize.min"
6
- import "src/decidim/vendor/image-upload.min"
3
+ import lineBreakButtonHandler from "src/decidim/editor/linebreak_module";
4
+ import "src/decidim/editor/clipboard_override";
5
+ import "src/decidim/vendor/image-resize.min";
6
+ import "src/decidim/vendor/image-upload.min";
7
7
 
8
- const quillFormats = ["bold", "italic", "link", "underline", "header", "list", "video", "image", "alt", "break", "width", "style", "code", "blockquote", "indent"];
8
+ const quillFormats = [
9
+ "bold",
10
+ "italic",
11
+ "link",
12
+ "underline",
13
+ "header",
14
+ "list",
15
+ "alt",
16
+ "break",
17
+ "width",
18
+ "style",
19
+ "code",
20
+ "blockquote",
21
+ "indent"
22
+ ];
9
23
 
10
24
  export default function createQuillEditor(container) {
11
25
  const toolbar = $(container).data("toolbar");
@@ -17,26 +31,28 @@ export default function createQuillEditor(container) {
17
31
  [{ list: "ordered" }, { list: "bullet" }],
18
32
  ["link", "clean"],
19
33
  ["code", "blockquote"],
20
- [{ "indent": "-1"}, { "indent": "+1" }]
34
+ [{ indent: "-1" }, { indent: "+1" }]
21
35
  ];
22
36
 
23
- let addImage = $(container).data("editorImages");
37
+ let addImage = false;
38
+ let addVideo = false;
24
39
 
25
- if (toolbar === "full") {
40
+ /**
41
+ * - basic = only basic controls without titles
42
+ * - content = basic + headings
43
+ * - full = basic + headings + image + video
44
+ */
45
+ if (toolbar === "content") {
46
+ quillToolbar = [[{ header: [2, 3, 4, 5, 6, false] }], ...quillToolbar];
47
+ } else if (toolbar === "full") {
48
+ addImage = true;
49
+ addVideo = true;
26
50
  quillToolbar = [
27
51
  [{ header: [2, 3, 4, 5, 6, false] }],
28
52
  ...quillToolbar,
29
- ["video"]
53
+ ["video"],
54
+ ["image"]
30
55
  ];
31
- } else if (toolbar === "basic") {
32
- quillToolbar = [
33
- ...quillToolbar,
34
- ["video"]
35
- ];
36
- }
37
-
38
- if (addImage) {
39
- quillToolbar.push(["image"]);
40
56
  }
41
57
 
42
58
  let modules = {
@@ -44,17 +60,26 @@ export default function createQuillEditor(container) {
44
60
  toolbar: {
45
61
  container: quillToolbar,
46
62
  handlers: {
47
- "linebreak": lineBreakButtonHandler
63
+ linebreak: lineBreakButtonHandler
48
64
  }
49
65
  }
50
66
  };
51
67
  const $input = $(container).siblings('input[type="hidden"]');
52
68
  container.innerHTML = $input.val() || "";
53
69
  const token = $('meta[name="csrf-token"]').attr("content");
70
+
71
+ if (addVideo) {
72
+ quillFormats.push("video");
73
+ }
74
+
54
75
  if (addImage) {
76
+ // Attempt to allow images only if the image support is enabled at editor support.
77
+ // see: https://github.com/quilljs/quill/issues/1108
78
+ quillFormats.push("image");
79
+
55
80
  modules.imageResize = {
56
81
  modules: ["Resize", "DisplaySize"]
57
- }
82
+ };
58
83
  modules.imageUpload = {
59
84
  url: $(container).data("uploadImagesPath"),
60
85
  method: "POST",
@@ -62,18 +87,23 @@ export default function createQuillEditor(container) {
62
87
  withCredentials: false,
63
88
  headers: { "X-CSRF-Token": token },
64
89
  callbackOK: (serverResponse, next) => {
65
- $("div.ql-toolbar").last().removeClass("editor-loading")
90
+ $("div.ql-toolbar").last().removeClass("editor-loading");
66
91
  next(serverResponse.url);
67
92
  },
68
93
  callbackKO: (serverError) => {
69
- $("div.ql-toolbar").last().removeClass("editor-loading")
94
+ $("div.ql-toolbar").last().removeClass("editor-loading");
70
95
  console.log(`Image upload error: ${serverError.message}`);
71
96
  },
72
97
  checkBeforeSend: (file, next) => {
73
- $("div.ql-toolbar").last().addClass("editor-loading")
98
+ $("div.ql-toolbar").last().addClass("editor-loading");
74
99
  next(file);
75
100
  }
76
- }
101
+ };
102
+
103
+ const text = $(container).data("dragAndDropHelpText");
104
+ $(container).after(
105
+ `<p class="help-text" style="margin-top:-1.5rem;">${text}</p>`
106
+ );
77
107
  }
78
108
  const quill = new Quill(container, {
79
109
  modules: modules,
@@ -81,6 +111,11 @@ export default function createQuillEditor(container) {
81
111
  theme: "snow"
82
112
  });
83
113
 
114
+ if (addImage === false) {
115
+ // Firefox natively implements image drop in contenteditable which is why we need to disable that
116
+ quill.root.addEventListener("drop", (ev) => ev.preventDefault());
117
+ }
118
+
84
119
  if (disabled) {
85
120
  quill.disable();
86
121
  }
@@ -95,7 +130,10 @@ export default function createQuillEditor(container) {
95
130
  });
96
131
  container.dispatchEvent(event);
97
132
 
98
- if ((text === "\n" || text === "\n\n") && quill.root.querySelectorAll(allowedEmptyContentSelector).length === 0) {
133
+ if (
134
+ (text === "\n" || text === "\n\n") &&
135
+ quill.root.querySelectorAll(allowedEmptyContentSelector).length === 0
136
+ ) {
99
137
  $input.val("");
100
138
  } else {
101
139
  const emptyParagraph = "<p><br></p>";
@@ -109,13 +147,5 @@ export default function createQuillEditor(container) {
109
147
  // After editor is ready, linebreak_module deletes two extraneous new lines
110
148
  quill.emitter.emit("editor-ready");
111
149
 
112
- if (addImage) {
113
- const text = $(container).data("dragAndDropHelpText");
114
- $(container).after(`<p class="help-text" style="margin-top:-1.5rem;">${text}</p>`);
115
- }
116
-
117
- // After editor is ready, linebreak_module deletes two extraneous new lines
118
- quill.emitter.emit("editor-ready");
119
-
120
150
  return quill;
121
151
  }
@@ -123,6 +123,10 @@
123
123
  }
124
124
  }
125
125
 
126
+ &.primary{
127
+ @include button-hollow-variant(var(--primary));
128
+ }
129
+
126
130
  &.secondary{
127
131
  @include button-hollow-variant(var(--secondary));
128
132
  }
@@ -257,12 +261,12 @@
257
261
 
258
262
  .button--shadow{
259
263
  $shadows: (
260
- primary: shade($primary, 50),
261
- secondary: shade($secondary, 50),
262
- success: shade($success, 50),
263
- warning: shade($warning, 50),
264
- alert: shade($alert, 50),
265
- muted: shade($muted, 50),
264
+ primary: shade($primary, 50%),
265
+ secondary: shade($secondary, 50%),
266
+ success: shade($success, 50%),
267
+ warning: shade($warning, 50%),
268
+ alert: shade($alert, 50%),
269
+ muted: shade($muted, 50%),
266
270
  );
267
271
 
268
272
  @include modifiers(background-color, $shadows){
@@ -934,7 +934,7 @@ a .card__title{
934
934
  @include modifiers(
935
935
  color,
936
936
  (
937
- muted: tint($muted, 50),
937
+ muted: tint($muted, 50%),
938
938
  )
939
939
  ){
940
940
  margin-top: -$global-margin * .95;
@@ -17,6 +17,30 @@ $comment-form-bg: $light-gray;
17
17
 
18
18
  .comment-thread{
19
19
  @extend .card;
20
+
21
+ .show-comment-replies{
22
+ display: none;
23
+ }
24
+
25
+ .hide-comment-replies{
26
+ display: inline;
27
+ }
28
+
29
+ .comment__hide{
30
+ float: left;
31
+ margin-bottom: 0;
32
+ }
33
+
34
+ .no-comments{
35
+ .show-comment-replies{
36
+ display: inline;
37
+ }
38
+
39
+ .hide-comment-replies,
40
+ .replies{
41
+ display: none;
42
+ }
43
+ }
20
44
  }
21
45
 
22
46
  .comment-thread__title{
@@ -26,7 +26,8 @@
26
26
  top: 2px;
27
27
  width: 2rem;
28
28
  height: 2rem;
29
- background: rgba(var(--primary-rgb), .8);
29
+ background: var(--primary);
30
+ opacity: .8;
30
31
  color: $white;
31
32
  border-radius: 50%;
32
33
  }
@@ -32,10 +32,6 @@
32
32
  border-width: 2px;
33
33
  border-radius: 4px;
34
34
 
35
- * >{
36
- display: none;
37
- }
38
-
39
35
  .form-error{
40
36
  margin: 0;
41
37
  }
@@ -36,8 +36,8 @@
36
36
  @mixin loop-colors-types($color, $max: 12){
37
37
  @for $i from 0 through ($max - 1){
38
38
  $interval: ($i % 4) * 24 + 1;
39
- $tints: tint($color, $interval);
40
- $shades: shade($color, $interval);
39
+ $tints: tint($color, $interval * 1%);
40
+ $shades: shade($color, $interval * 1%);
41
41
  $adjusts: adjust-color($color, $lightness: $interval * 1%, $hue: -$interval);
42
42
 
43
43
  .type-#{$i}:not(.legend){
@@ -27,8 +27,8 @@
27
27
  @mixin loop-colors-types($color, $max: 12){
28
28
  @for $i from 0 through ($max - 1){
29
29
  $interval: ($i % 4) * 24 + 1;
30
- $tints: tint($color, $interval);
31
- $shades: shade($color, $interval);
30
+ $tints: tint($color, $interval * 1%);
31
+ $shades: shade($color, $interval * 1%);
32
32
  $adjusts: adjust-color($color, $lightness: $interval * 1%, $hue: -$interval);
33
33
 
34
34
  .type-#{$i}{
@@ -11,7 +11,7 @@ module Decidim
11
11
 
12
12
  def created_at_in_words
13
13
  if created_at.between?(1.month.ago, Time.current)
14
- time_ago_in_words(created_at)
14
+ I18n.t("decidim.user_conversations.index.time_ago", time: time_ago_in_words(created_at))
15
15
  else
16
16
  format = created_at.year == Time.current.year ? :ddmm : :ddmmyyyy
17
17
  I18n.l(created_at, format: format)
@@ -27,6 +27,7 @@ module Decidim
27
27
  @event ||= event_class.constantize.new(
28
28
  resource: resource,
29
29
  user: user,
30
+ user_role: user_role,
30
31
  event_name: event_name,
31
32
  extra: extra
32
33
  )
@@ -16,7 +16,7 @@ module Decidim
16
16
  end
17
17
 
18
18
  def can_be_contacted?
19
- true
19
+ true unless blocked?
20
20
  end
21
21
 
22
22
  def officialization_text
@@ -61,7 +61,7 @@ module Decidim
61
61
  end
62
62
 
63
63
  def can_be_contacted?
64
- true
64
+ true unless blocked?
65
65
  end
66
66
 
67
67
  def officialization_text
@@ -14,12 +14,14 @@ module Decidim
14
14
  class AdminInputScrubber < UserInputScrubber
15
15
  private
16
16
 
17
+ DECIDIM_ALLOWED_TAGS = %w(img video audio source comment iframe).freeze
18
+
17
19
  def custom_allowed_attributes
18
20
  super + %w(frameborder allowfullscreen) - %w(onerror)
19
21
  end
20
22
 
21
23
  def custom_allowed_tags
22
- super + %w(comment iframe)
24
+ super + DECIDIM_ALLOWED_TAGS
23
25
  end
24
26
  end
25
27
  end