decidim-core 0.26.5 → 0.26.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/announcement_cell.rb +1 -1
  3. data/app/cells/decidim/collapsible_list/show.erb +1 -1
  4. data/app/cells/decidim/content_blocks/cta_cell.rb +1 -1
  5. data/app/cells/decidim/content_blocks/hero/show.erb +1 -1
  6. data/app/cells/decidim/content_blocks/highlighted_content_banner/show.erb +1 -1
  7. data/app/cells/decidim/content_blocks/sub_hero_cell.rb +1 -1
  8. data/app/controllers/decidim/authorization_modals_controller.rb +1 -1
  9. data/app/controllers/decidim/links_controller.rb +8 -11
  10. data/app/forms/url_validator.rb +1 -1
  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/layout_helper.rb +15 -4
  14. data/app/helpers/decidim/layout_helper.rb.orig +225 -0
  15. data/app/helpers/decidim/sanitize_helper.rb +15 -5
  16. data/app/models/decidim/organization.rb +6 -0
  17. data/app/models/decidim/scope_type.rb +28 -0
  18. data/app/packs/src/decidim/editor/clipboard_override.js +6 -2
  19. data/app/packs/src/decidim/editor.js +63 -33
  20. data/app/packs/src/decidim/map/controller/drag_marker.js +0 -2
  21. data/app/packs/src/decidim/map/controller/markers.js +0 -1
  22. data/app/packs/src/decidim/map/controller/static.js +0 -1
  23. data/app/packs/src/decidim/map/controller.js +0 -2
  24. data/app/packs/src/decidim/map/factory.js +4 -1
  25. data/app/packs/src/decidim/map/icon.js +0 -1
  26. data/app/packs/src/decidim/map/legacy.js +0 -1
  27. data/app/packs/src/decidim/map/provider/default.js +2 -0
  28. data/app/packs/src/decidim/map/provider/here.js +2 -1
  29. data/app/packs/stylesheets/decidim/_editor.scss +129 -0
  30. data/app/packs/stylesheets/decidim/extras/_quill.scss +0 -6
  31. data/app/packs/stylesheets/decidim/modules/_buttons.scss +10 -6
  32. data/app/packs/stylesheets/decidim/modules/_cards.scss +1 -1
  33. data/app/packs/stylesheets/decidim/modules/_comments.scss +24 -0
  34. data/app/packs/stylesheets/decidim/modules/_dropdown_menu.scss +9 -0
  35. data/app/packs/stylesheets/decidim/vizzs/_linechart.scss +2 -2
  36. data/app/packs/stylesheets/decidim/vizzs/_rowchart.scss +2 -2
  37. data/app/presenters/decidim/notification_presenter.rb +1 -1
  38. data/app/presenters/decidim/user_group_presenter.rb +1 -1
  39. data/app/presenters/decidim/user_presenter.rb +1 -1
  40. data/app/queries/decidim/metrics/users_metric_manage.rb +6 -6
  41. data/app/scrubbers/decidim/admin_input_scrubber.rb +27 -0
  42. data/app/scrubbers/decidim/user_input_scrubber.rb +32 -5
  43. data/app/services/decidim/traceability.rb +1 -0
  44. data/app/views/decidim/devise/registrations/new.html.erb.orig +231 -0
  45. data/app/views/decidim/links/_invalid_url_modal.html.erb +17 -0
  46. data/app/views/decidim/links/_modal.html.erb +1 -1
  47. data/app/views/decidim/links/invalid_url.js.erb +24 -0
  48. data/app/views/decidim/links/new.html.erb +1 -1
  49. data/app/views/decidim/messaging/conversations/_conversation.html.erb +1 -5
  50. data/app/views/decidim/pages/_standalone.html.erb +1 -1
  51. data/app/views/decidim/pages/_tabbed.html.erb +1 -1
  52. data/config/environment.rb +0 -0
  53. data/config/locales/ar.yml +423 -8
  54. data/config/locales/bg.yml +1 -4
  55. data/config/locales/ca.yml +26 -23
  56. data/config/locales/cs.yml +40 -32
  57. data/config/locales/da.yml +3 -0
  58. data/config/locales/de.yml +39 -23
  59. data/config/locales/el.yml +100 -2
  60. data/config/locales/en.yml +16 -13
  61. data/config/locales/eo.yml +5 -1
  62. data/config/locales/es-MX.yml +21 -18
  63. data/config/locales/es-PY.yml +23 -20
  64. data/config/locales/es.yml +25 -22
  65. data/config/locales/et.yml +3 -0
  66. data/config/locales/eu.yml +91 -67
  67. data/config/locales/fa-IR.yml +1 -0
  68. data/config/locales/fi-plain.yml +16 -13
  69. data/config/locales/fi.yml +18 -15
  70. data/config/locales/fr-CA.yml +28 -22
  71. data/config/locales/fr.yml +27 -21
  72. data/config/locales/ga-IE.yml +1 -0
  73. data/config/locales/gl.yml +4 -23
  74. data/config/locales/gn-PY.yml +3 -0
  75. data/config/locales/hr.yml +3 -0
  76. data/config/locales/hu.yml +66 -21
  77. data/config/locales/id-ID.yml +6 -4
  78. data/config/locales/is-IS.yml +5 -2
  79. data/config/locales/it.yml +5 -15
  80. data/config/locales/ja.yml +16 -15
  81. data/config/locales/ka-GE.yml +3 -0
  82. data/config/locales/kaa.yml +5 -0
  83. data/config/locales/lb.yml +8 -12
  84. data/config/locales/lt.yml +1 -34
  85. data/config/locales/lv.yml +0 -3
  86. data/config/locales/nl.yml +5 -24
  87. data/config/locales/no.yml +5 -28
  88. data/config/locales/oc-FR.yml +2 -0
  89. data/config/locales/pl.yml +0 -33
  90. data/config/locales/pt-BR.yml +3 -7
  91. data/config/locales/pt.yml +1 -5
  92. data/config/locales/ro-RO.yml +5 -8
  93. data/config/locales/ru.yml +3 -3
  94. data/config/locales/sk.yml +18 -10
  95. data/config/locales/sl.yml +1 -0
  96. data/config/locales/sr-CS.yml +10 -0
  97. data/config/locales/sv.yml +11 -34
  98. data/config/locales/tr-TR.yml +3 -7
  99. data/config/locales/uk.yml +3 -3
  100. data/config/locales/zh-CN.yml +0 -4
  101. data/config/locales/zh-TW.yml +1726 -0
  102. data/db/migrate/20181030090144_destroy_deleted_users_follows.rb +1 -1
  103. data/db/migrate/20181204110723_remove_following_users_count_from_users.rb +11 -2
  104. data/db/migrate/20181214101250_add_notification_types_to_users.rb +6 -1
  105. data/db/migrate/20190412131728_fix_user_names.rb +13 -6
  106. data/db/migrate/20200211173227_add_direct_message_types_to_users.rb +6 -1
  107. data/db/migrate/20210302150803_invalidate_all_sessions_for_deleted_users.rb +10 -3
  108. data/db/migrate/20210310120640_add_followable_counter_cache_to_users.rb +13 -3
  109. data/lib/decidim/core/test/shared_examples/comments_examples.rb +36 -0
  110. data/lib/decidim/core/test/shared_examples/editor_shared_examples.rb +10 -0
  111. data/lib/decidim/core/test/shared_examples/map_examples.rb +4 -1
  112. data/lib/decidim/core/test/shared_examples/rich_text_editor_examples.rb +7 -3
  113. data/lib/decidim/core/test.rb +1 -0
  114. data/lib/decidim/core/version.rb +1 -1
  115. data/lib/decidim/core.rb +43 -0
  116. data/lib/decidim/dependency_resolver.rb +272 -0
  117. data/lib/decidim/form_builder.rb +6 -14
  118. data/lib/decidim/publicable.rb +4 -0
  119. data/lib/tasks/upgrade/decidim_user_moderation.rake +14 -0
  120. metadata +18 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8937550934aed6ed69fa84837edbbc2902c5fd6779654a5180a63abbfe25d700
4
- data.tar.gz: ba86b8da585b54db8845cf1d5e476086db70d03920f99ddf3a32cbe023521992
3
+ metadata.gz: 7ddc4e551d59fc9b95f5121333870c4e91a77eb2f53d03f659d63e16bea7ca7f
4
+ data.tar.gz: dd766575d88dfe729ce7f8ea151967642b40e875c0596443962d0a6d8d5cd101
5
5
  SHA512:
6
- metadata.gz: de40caba191fb9ff0b88bbf9055fbb4592fcc12352c6e35d21d812e87399b1772254d669438ade3ada960ca994247ab87b3eb4743e80df9121cb3bb07c6a9f18
7
- data.tar.gz: c115b3d2e531d0b0d8e43f7b41a863dc51eb2c076bc8c4f2c8731e642fb191115b2fdf6aebe6a3cf28c48fb746c3e9cec85ea9a5be630d382ee695cff238278f
6
+ metadata.gz: dcbcc2625fe80e646c9f5e05c0e187485004542bcb73c33978bd8f2b1f78f31c2e0a4f4dba6904ba0b660e5b4408977a21d13e0c02fe2d46cba8bdb13c399321
7
+ data.tar.gz: a1abc1c25bfe4c99875d00822beeefeadb20bd214672772b1491d07e515ce3990b6b24c96d5400e4bc71d1accb8c8484d7e57c6ed006f63fd19e23f8378234ce
@@ -62,7 +62,7 @@ module Decidim
62
62
  end
63
63
 
64
64
  def clean(value)
65
- decidim_sanitize(translated_attribute(value))
65
+ decidim_sanitize_admin(translated_attribute(value))
66
66
  end
67
67
  end
68
68
  end
@@ -16,7 +16,7 @@
16
16
  </span>
17
17
  </div>
18
18
  <% else %>
19
- <div class="collapsible-list <%= list_class %>">
19
+ <div class="<%= list_class %>">
20
20
  <% list.each do |element| %>
21
21
  <% if cell_name %>
22
22
  <%= cell cell_name, element, cell_options %>
@@ -16,7 +16,7 @@ module Decidim
16
16
  end
17
17
 
18
18
  def translated_description
19
- @translated_description ||= decidim_sanitize_editor(translated_attribute(model.settings.description))
19
+ @translated_description ||= decidim_sanitize_editor_admin(translated_attribute(model.settings.description))
20
20
  end
21
21
 
22
22
  def button_url
@@ -6,7 +6,7 @@
6
6
  <% if translated_welcome_text.blank? %>
7
7
  <%= t("decidim.pages.home.hero.welcome", organization: current_organization.name) %>
8
8
  <% else %>
9
- <%= decidim_sanitize translated_welcome_text %>
9
+ <%= decidim_sanitize_admin translated_welcome_text %>
10
10
  <% end %>
11
11
  </h1>
12
12
  </div>
@@ -7,7 +7,7 @@
7
7
  <%= translated_attribute current_organization.highlighted_content_banner_title %>
8
8
  </h1>
9
9
  <span class="text-highlight">
10
- <%= decidim_sanitize_editor translated_attribute current_organization.highlighted_content_banner_short_description %>
10
+ <%= decidim_sanitize_editor_admin translated_attribute current_organization.highlighted_content_banner_short_description %>
11
11
  </span>
12
12
  </div>
13
13
  <div class="columns large-2">
@@ -15,7 +15,7 @@ module Decidim
15
15
  private
16
16
 
17
17
  def organization_description
18
- desc = decidim_sanitize(translated_attribute(current_organization.description))
18
+ desc = decidim_sanitize_admin(translated_attribute(current_organization.description))
19
19
 
20
20
  # Strip the surrounding paragraph tag because it is not allowed within
21
21
  # a <hN> element.
@@ -17,7 +17,7 @@ module Decidim
17
17
  end
18
18
 
19
19
  def current_component
20
- @current_component ||= Decidim::Component.find(params[:component_id])
20
+ @current_component ||= Decidim::Component.where(participatory_space: current_organization.participatory_spaces).find(params[:component_id])
21
21
  end
22
22
 
23
23
  def authorization_action
@@ -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
@@ -6,7 +6,7 @@
6
6
  #
7
7
  class UrlValidator < ActiveModel::EachValidator
8
8
  def validate_each(record, attribute, value)
9
- record.errors[attribute] << (options[:message] || "must be a valid URL") unless url_valid?(value)
9
+ record.errors.add attribute, :url_format, **options unless url_valid?(value)
10
10
  end
11
11
 
12
12
  # a URL may be technically well-formed but may
@@ -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
@@ -74,8 +74,11 @@ module Decidim
74
74
  classes = _icon_classes(options) + ["external-icon"]
75
75
 
76
76
  if path.split(".").last == "svg"
77
+ icon_path = application_path(path)
78
+ return unless icon_path
79
+
77
80
  attributes = { class: classes.join(" ") }.merge(options)
78
- asset = File.read(application_path(path))
81
+ asset = File.read(icon_path)
79
82
  asset.gsub("<svg ", "<svg#{tag_builder.tag_options(attributes)} ").html_safe
80
83
  else
81
84
  image_pack_tag(path, class: classes.join(" "), style: "display: none")
@@ -83,9 +86,17 @@ module Decidim
83
86
  end
84
87
 
85
88
  def application_path(path)
86
- img_path = asset_pack_path(path)
87
- img_path = URI(img_path).path if Decidim.cors_enabled
88
- Rails.root.join("public/#{img_path}")
89
+ # Force the path to be returned without the protocol and host even when a
90
+ # custom asset host has been defined. The host parameter needs to be a
91
+ # non-nil because otherwise it will be set to the asset host at
92
+ # ActionView::Helpers::AssetUrlHelper#compute_asset_host.
93
+ img_path = asset_pack_path(path, host: "", protocol: :relative)
94
+ path = Rails.public_path.join(img_path.sub(%r{^/}, ""))
95
+ return unless File.exist?(path)
96
+
97
+ path
98
+ rescue ::Webpacker::Manifest::MissingEntryError
99
+ nil
89
100
  end
90
101
 
91
102
  # Allows to create role attribute according to accessibility rules
@@ -0,0 +1,225 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ # View helpers related to the layout.
5
+ module LayoutHelper
6
+ include Decidim::ModalHelper
7
+ include Decidim::TooltipHelper
8
+
9
+ # Public: Generates a set of meta tags that generate the different favicon
10
+ # versions for an organization.
11
+ #
12
+ # Returns a safe String with the versions.
13
+ def favicon
14
+ return if current_organization.favicon.blank?
15
+
16
+ safe_join(Decidim::OrganizationFaviconUploader::SIZES.map do |version, size|
17
+ favicon_link_tag(current_organization.attached_uploader(:favicon).variant_url(version, host: current_organization.host), sizes: "#{size}x#{size}")
18
+ end)
19
+ end
20
+
21
+ def apple_favicon
22
+ icon_image = current_organization.attached_uploader(:favicon).variant_url(:medium, host: current_organization.host)
23
+ return unless icon_image
24
+
25
+ favicon_link_tag(icon_image, rel: "apple-touch-icon", type: "image/png")
26
+ end
27
+
28
+ def legacy_favicon
29
+ variant = :favicon if current_organization.favicon.content_type != "image/vnd.microsoft.icon"
30
+ icon_image = current_organization.attached_uploader(:favicon).variant_url(variant, host: current_organization.host)
31
+ return unless icon_image
32
+
33
+ favicon_link_tag(icon_image, rel: "icon", sizes: "any", type: nil)
34
+ end
35
+
36
+ # Outputs an SVG-based icon.
37
+ #
38
+ # name - The String with the icon name.
39
+ # options - The Hash options used to customize the icon (default {}):
40
+ # :width - The Number of width in pixels (optional).
41
+ # :height - The Number of height in pixels (optional).
42
+ # :title - The title for the SVG element (optional, similar to alt for img)
43
+ # :aria_label - The String to set as aria label (optional).
44
+ # :aria_hidden - The Truthy value to enable aria_hidden (optional).
45
+ # :role - The String to set as the role (optional).
46
+ # :class - The String to add as a CSS class (optional).
47
+ #
48
+ # Returns a String.
49
+ def redesigned_icon(name, options = {})
50
+ default_html_properties = {
51
+ "width" => "1em",
52
+ "height" => "1em",
53
+ "role" => "img",
54
+ "aria-hidden" => "true"
55
+ }
56
+
57
+ html_properties = options.with_indifferent_access.transform_keys(&:dasherize).slice("width", "height", "aria-label", "role", "aria-hidden", "class", "style")
58
+ html_properties = default_html_properties.merge(html_properties)
59
+
60
+ href = Decidim.cors_enabled ? "" : asset_pack_path("media/images/remixicon.symbol.svg")
61
+
62
+ content_tag :svg, html_properties do
63
+ content_tag :use, nil, "href" => "#{href}#ri-#{name}", tabindex: -1
64
+ end
65
+ end
66
+
67
+ def legacy_icon(name, options = {})
68
+ options = options.with_indifferent_access
69
+ html_properties = {}
70
+
71
+ html_properties["width"] = options[:width]
72
+ html_properties["height"] = options[:height]
73
+ html_properties["aria-label"] = options[:aria_label] || options[:"aria-label"]
74
+ html_properties["role"] = options[:role] || "img"
75
+ html_properties["aria-hidden"] = options[:aria_hidden] || options[:"aria-hidden"]
76
+
77
+ html_properties["class"] = (["icon--#{name}"] + _icon_classes(options)).join(" ")
78
+
79
+ title = options["title"] || html_properties["aria-label"]
80
+ if title.blank? && html_properties["role"] == "img"
81
+ # This will make the accessibility audit tools happy as with the "img"
82
+ # role, the alternative text (aria-label) and title are required for the
83
+ # element. This will also force the SVG to be hidden because otherwise
84
+ # the screen reader would announce the icon name which can be in
85
+ # different language (English) than the page language which is not
86
+ # allowed.
87
+ title = name
88
+ html_properties["aria-label"] = title
89
+ html_properties["aria-hidden"] = true
90
+ end
91
+
92
+ href = Decidim.cors_enabled ? "" : asset_pack_path("media/images/icons.svg")
93
+
94
+ content_tag :svg, html_properties do
95
+ inner = content_tag :title, title
96
+ inner += content_tag :use, nil, "href" => "#{href}#icon-#{name}"
97
+
98
+ inner
99
+ end
100
+ end
101
+
102
+ def icon(*args)
103
+ redesign_enabled? ? redesigned_icon(*args) : legacy_icon(*args)
104
+ end
105
+
106
+ # Outputs a SVG icon from an external file. It apparently renders an image
107
+ # tag, but then a JS script kicks in and replaces it with an inlined SVG
108
+ # version.
109
+ #
110
+ # path - The asset's path
111
+ #
112
+ # Returns an <img /> tag with the SVG icon.
113
+ def external_icon(path, options = {})
114
+ classes = _icon_classes(options) + ["external-icon"]
115
+
116
+ if path.split(".").last == "svg"
117
+ icon_path = application_path(path)
118
+ return unless icon_path
119
+
120
+ attributes = { class: classes.join(" ") }.merge(options)
121
+ asset = File.read(icon_path)
122
+ asset.gsub("<svg ", "<svg#{tag_builder.tag_options(attributes)} ").html_safe
123
+ else
124
+ image_pack_tag(path, class: classes.join(" "), style: "display: none")
125
+ end
126
+ end
127
+
128
+ def application_path(path)
129
+ # Force the path to be returned without the protocol and host even when a
130
+ # custom asset host has been defined. The host parameter needs to be a
131
+ # non-nil because otherwise it will be set to the asset host at
132
+ # ActionView::Helpers::AssetUrlHelper#compute_asset_host.
133
+ img_path = asset_pack_path(path, host: "", protocol: :relative)
134
+ path = Rails.public_path.join(img_path.sub(%r{^/}, ""))
135
+ return unless File.exist?(path)
136
+
137
+ path
138
+ rescue ::Webpacker::Manifest::MissingEntryError
139
+ nil
140
+ end
141
+
142
+ # Allows to create role attribute according to accessibility rules
143
+ #
144
+ # Returns role attribute string if role option is specified
145
+ def role(options = {})
146
+ "role=\"#{options[:role]}\" " if options[:role]
147
+ end
148
+
149
+ def _icon_classes(options = {})
150
+ classes = options[:remove_icon_class] ? [] : ["icon"]
151
+ classes += [options[:class]]
152
+ classes.compact
153
+ end
154
+
155
+ def extended_navigation_bar(items, max_items: 5)
156
+ return unless items.any?
157
+
158
+ extra_items = items.slice((max_items + 1)..-1) || []
159
+ active_item = items.find { |item| item[:active] }
160
+
161
+ controller.view_context.render partial: "decidim/shared/extended_navigation_bar", locals: {
162
+ items:,
163
+ extra_items:,
164
+ active_item:,
165
+ max_items:
166
+ }
167
+ end
168
+
169
+ # Renders a view with the customizable CSS variables in two flavours:
170
+ # 1. as a hexadecimal valid CSS color (ie: #ff0000)
171
+ # 2. as a disassembled RGB components (ie: 255 0 0)
172
+ #
173
+ # Example:
174
+ #
175
+ # --primary: #ff0000;
176
+ # --primary-rgb: 255,0,0
177
+ #
178
+ # Hexadecimal variables can be used as a normal CSS color:
179
+ #
180
+ # color: var(--primary)
181
+ #
182
+ # While the disassembled variant can be used where you need to manipulate
183
+ # the color somehow (ie: adding a background transparency):
184
+ #
185
+ # background-color: rgba(var(--primary-rgb), 0.5)
186
+ def organization_colors
187
+ css = current_organization.colors.each.map { |k, v| "--#{k}: #{v};--#{k}-rgb: #{v[1..2].hex} #{v[3..4].hex} #{v[5..6].hex};" }.join
188
+ render partial: "layouts/decidim/organization_colors", locals: { css: }
189
+ end
190
+
191
+ <<<<<<< HEAD
192
+ def current_user_unread_data
193
+ return {} if current_user.blank?
194
+
195
+ {}.tap do |d|
196
+ d.merge!(unread_notifications: true) if current_user.notifications.any?
197
+ d.merge!(unread_conversations: true) if current_user.unread_conversations.any?
198
+ d.merge!(unread_items: d.present?)
199
+ end
200
+ end
201
+
202
+ ||||||| parent of 53b6893e5c (Use local emojibase data instead of CDN)
203
+ =======
204
+ # Public: Gets the name of the webpacker entrypoint that will be used
205
+ # for the locale of the Emojibase NPM package, used with @picmo/popup-picker
206
+ #
207
+ # Returns a string with the entrypoint name
208
+ def emojibase_entrypoint_locale
209
+ entrypoint = Decidim::Webpacker.configuration.entrypoints.keys.select do |entry|
210
+ entry == "decidim_emojibase_#{I18n.locale}"
211
+ end
212
+
213
+ return "decidim_emojibase_en" if entrypoint.empty?
214
+
215
+ entrypoint.first
216
+ end
217
+
218
+ >>>>>>> 53b6893e5c (Use local emojibase data instead of CDN)
219
+ private
220
+
221
+ def tag_builder
222
+ @tag_builder ||= ActionView::Helpers::TagHelper::TagBuilder.new(self)
223
+ end
224
+ end
225
+ end
@@ -16,13 +16,18 @@ module Decidim
16
16
  #
17
17
  # Returns an HTML-safe String.
18
18
  def decidim_sanitize(html, options = {})
19
+ scrubber = options[:scrubber] || Decidim::UserInputScrubber.new
19
20
  if options[:strip_tags]
20
- strip_tags sanitize(html, scrubber: Decidim::UserInputScrubber.new)
21
+ strip_tags sanitize(html, scrubber: scrubber)
21
22
  else
22
- sanitize(html, scrubber: Decidim::UserInputScrubber.new)
23
+ sanitize(html, scrubber: scrubber)
23
24
  end
24
25
  end
25
26
 
27
+ def decidim_sanitize_admin(html, options = {})
28
+ decidim_sanitize(html, { scrubber: Decidim::AdminInputScrubber.new }.merge(options))
29
+ end
30
+
26
31
  def decidim_sanitize_newsletter(html, options = {})
27
32
  if options[:strip_tags]
28
33
  strip_tags sanitize(html, scrubber: Decidim::NewsletterScrubber.new)
@@ -32,7 +37,11 @@ module Decidim
32
37
  end
33
38
 
34
39
  def decidim_sanitize_editor(html, options = {})
35
- content_tag(:div, decidim_sanitize(html, options), class: %w(ql-editor ql-reset-decidim))
40
+ content_tag(:div, decidim_sanitize(html, options), class: %w(ql-editor-display))
41
+ end
42
+
43
+ def decidim_sanitize_editor_admin(html, options = {})
44
+ decidim_sanitize_editor(html, { scrubber: Decidim::AdminInputScrubber.new }.merge(options))
36
45
  end
37
46
 
38
47
  def decidim_html_escape(text)
@@ -102,9 +111,10 @@ module Decidim
102
111
  #
103
112
  # @return ActiveSupport::SafeBuffer
104
113
  def render_sanitized_content(resource, method)
105
- content = present(resource).send(method, links: true, strip_tags: !safe_content?)
114
+ content = present(resource).send(method, links: true, strip_tags: !try(:safe_content?))
106
115
 
107
- return decidim_sanitize(content, {}) unless safe_content?
116
+ return decidim_sanitize(content, {}) unless try(:safe_content?)
117
+ return decidim_sanitize_editor_admin(content, {}) if try(:safe_content_admin?)
108
118
 
109
119
  decidim_sanitize_editor(content)
110
120
  end
@@ -87,6 +87,12 @@ module Decidim
87
87
  @top_scopes ||= scopes.top_level
88
88
  end
89
89
 
90
+ def participatory_spaces
91
+ @participatory_spaces ||= Decidim.participatory_space_manifests.flat_map do |manifest|
92
+ manifest.participatory_spaces.call(self)
93
+ end
94
+ end
95
+
90
96
  def public_participatory_spaces
91
97
  @public_participatory_spaces ||= Decidim.participatory_space_manifests.flat_map do |manifest|
92
98
  manifest.participatory_spaces.call(self).public_spaces
@@ -15,5 +15,33 @@ module Decidim
15
15
  has_many :scopes, class_name: "Decidim::Scope", inverse_of: :scope_type, dependent: :nullify
16
16
 
17
17
  validates :name, presence: true
18
+
19
+ before_destroy :detach_dynamic_associations
20
+
21
+ def self.log_presenter_class_for(_log)
22
+ Decidim::AdminLog::ScopeTypePresenter
23
+ end
24
+
25
+ private
26
+
27
+ # This method detaches all records that may have association with the scope
28
+ # type. This cannot be done directly using the `dependent` option in the
29
+ # `has_many` relation in order to avoid tight coupling between the modules.
30
+ #
31
+ # This logic does not have to be applied to any classes that have been
32
+ # defined as `has_many` associations within this model already as they are
33
+ # already handled by the `dependent` option.
34
+ def detach_dynamic_associations
35
+ ActiveRecord::Base.descendants.each do |cls|
36
+ next if cls.abstract_class? || !cls.name&.match?(/^Decidim::/)
37
+ next if [self.class, Decidim::Scope].include?(cls)
38
+
39
+ cls.reflect_on_all_associations(:belongs_to).each do |ref|
40
+ next unless ref.options[:class_name] == self.class.name
41
+
42
+ cls.where(ref.options[:foreign_key] => id).update_all(ref.options[:foreign_key] => nil) # rubocop:disable Rails/SkipsModelValidations
43
+ end
44
+ end
45
+ end
18
46
  end
19
47
  end
@@ -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
  }