decidim-core 0.29.1 → 0.29.3

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.
Files changed (169) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/activity_cell.rb +0 -3
  3. data/app/cells/decidim/author/show.erb +5 -4
  4. data/app/cells/decidim/author_cell.rb +26 -0
  5. data/app/cells/decidim/card_s/show.erb +5 -3
  6. data/app/cells/decidim/diff_cell.rb +4 -0
  7. data/app/cells/decidim/newsletter_templates/image_text_cta_cell.rb +1 -1
  8. data/app/cells/decidim/participatory_space_dropdown_metadata/show.erb +5 -3
  9. data/app/cells/decidim/profile_actions/show.erb +1 -1
  10. data/app/cells/decidim/report_button/already_reported_modal.erb +2 -2
  11. data/app/cells/decidim/report_button/flag_modal.erb +13 -27
  12. data/app/cells/decidim/report_button_cell.rb +2 -8
  13. data/app/cells/decidim/report_user_button/already_reported_modal.erb +11 -0
  14. data/app/cells/decidim/report_user_button/flag_modal.erb +46 -0
  15. data/app/cells/decidim/report_user_button/show.erb +2 -0
  16. data/app/cells/decidim/report_user_button_cell.rb +59 -0
  17. data/app/cells/decidim/resource_types_filter/show.erb +1 -1
  18. data/app/cells/decidim/resource_types_filter_cell.rb +6 -6
  19. data/app/cells/decidim/translation_bar/show.erb +2 -2
  20. data/app/cells/decidim/translation_bar_cell.rb +1 -1
  21. data/app/cells/decidim/user_activity/show.erb +1 -1
  22. data/app/commands/decidim/create_omniauth_registration.rb +14 -8
  23. data/app/commands/decidim/create_report.rb +1 -6
  24. data/app/commands/decidim/destroy_account.rb +3 -0
  25. data/app/commands/decidim/search.rb +14 -0
  26. data/app/controllers/decidim/doorkeeper/credentials_controller.rb +1 -1
  27. data/app/controllers/decidim/links_controller.rb +1 -1
  28. data/app/controllers/decidim/profiles_controller.rb +6 -2
  29. data/app/controllers/decidim/reports_controller.rb +1 -1
  30. data/app/controllers/decidim/user_activities_controller.rb +1 -1
  31. data/app/forms/decidim/account_form.rb +5 -2
  32. data/app/helpers/concerns/decidim/user_role_checker.rb +46 -0
  33. data/app/helpers/decidim/cta_button_helper.rb +1 -1
  34. data/app/helpers/decidim/map_helper.rb +6 -1
  35. data/app/helpers/decidim/orders_helper.rb +2 -1
  36. data/app/helpers/decidim/participatory_space_helpers.rb +1 -1
  37. data/app/helpers/decidim/sanitize_helper.rb +11 -2
  38. data/app/jobs/decidim/hide_child_resources_job.rb +24 -0
  39. data/app/mailers/decidim/reported_mailer.rb +1 -0
  40. data/app/models/decidim/action_log.rb +1 -9
  41. data/app/models/decidim/attachment.rb +1 -1
  42. data/app/models/decidim/report.rb +1 -1
  43. data/app/models/decidim/user.rb +0 -4
  44. data/app/models/decidim/user_base_entity.rb +4 -0
  45. data/app/packs/src/decidim/append_redirect_url_to_modals.js +14 -6
  46. data/app/packs/src/decidim/datepicker/datepicker_functions.js +3 -3
  47. data/app/packs/src/decidim/direct_uploads/upload_field.js +21 -8
  48. data/app/packs/src/decidim/index.js +5 -0
  49. data/app/packs/src/decidim/map/provider/here.js +1 -1
  50. data/app/packs/src/decidim/remote_tooltips.js +38 -0
  51. data/app/packs/src/decidim/toggle.js +1 -1
  52. data/app/packs/src/decidim/tooltips.js +42 -22
  53. data/app/packs/stylesheets/decidim/_content_blocks.scss +4 -0
  54. data/app/packs/stylesheets/decidim/_hashtags.scss +5 -0
  55. data/app/packs/stylesheets/decidim/_header.scss +11 -5
  56. data/app/packs/stylesheets/decidim/_labels.scss +1 -1
  57. data/app/packs/stylesheets/decidim/_profile.scss +1 -1
  58. data/app/packs/stylesheets/decidim/_progress-bar.scss +1 -1
  59. data/app/packs/stylesheets/decidim/application.scss +1 -0
  60. data/app/packs/stylesheets/decidim/legacy/conference-diploma.scss +2 -1
  61. data/app/presenters/decidim/attachment_presenter.rb +1 -1
  62. data/app/presenters/decidim/log/user_presenter.rb +1 -0
  63. data/app/presenters/decidim/user_presenter.rb +1 -1
  64. data/app/services/decidim/base_diff_renderer.rb +28 -2
  65. data/app/services/decidim/email_notification_generator.rb +14 -5
  66. data/app/services/decidim/static_map_generator.rb +1 -1
  67. data/app/views/decidim/last_activities/index.html.erb +1 -1
  68. data/app/views/decidim/pages/_tabbed.html.erb +2 -2
  69. data/app/views/decidim/reported_mailer/hide.html.erb +17 -1
  70. data/app/views/decidim/reported_mailer/report.html.erb +1 -1
  71. data/app/views/decidim/searches/_count.html.erb +1 -1
  72. data/app/views/decidim/searches/_filters.html.erb +40 -38
  73. data/app/views/decidim/shared/_orders.html.erb +2 -2
  74. data/app/views/layouts/decidim/footer/_main_legal.html.erb +1 -1
  75. data/app/views/layouts/decidim/header/_menu_breadcrumb_mobile_tablet.html.erb +1 -1
  76. data/config/locales/ar.yml +56 -27
  77. data/config/locales/bg.yml +10 -24
  78. data/config/locales/bn-BD.yml +1 -0
  79. data/config/locales/bs-BA.yml +100 -0
  80. data/config/locales/ca-IT.yml +2111 -0
  81. data/config/locales/ca.yml +70 -38
  82. data/config/locales/cs.yml +60 -32
  83. data/config/locales/de.yml +66 -38
  84. data/config/locales/el.yml +17 -15
  85. data/config/locales/en.yml +48 -16
  86. data/config/locales/eo.yml +2 -0
  87. data/config/locales/es-MX.yml +61 -29
  88. data/config/locales/es-PY.yml +66 -34
  89. data/config/locales/es.yml +71 -39
  90. data/config/locales/eu.yml +303 -261
  91. data/config/locales/fi-plain.yml +48 -28
  92. data/config/locales/fi.yml +85 -65
  93. data/config/locales/fr-CA.yml +64 -27
  94. data/config/locales/fr.yml +62 -25
  95. data/config/locales/ga-IE.yml +13 -4
  96. data/config/locales/gl.yml +33 -15
  97. data/config/locales/hu.yml +12 -26
  98. data/config/locales/id-ID.yml +32 -16
  99. data/config/locales/is-IS.yml +18 -2
  100. data/config/locales/it.yml +54 -27
  101. data/config/locales/ja.yml +70 -38
  102. data/config/locales/lb.yml +33 -22
  103. data/config/locales/lt.yml +10 -18
  104. data/config/locales/lv.yml +26 -15
  105. data/config/locales/nl.yml +33 -19
  106. data/config/locales/no.yml +27 -16
  107. data/config/locales/pl.yml +8 -22
  108. data/config/locales/pt-BR.yml +13 -25
  109. data/config/locales/pt.yml +32 -16
  110. data/config/locales/ro-RO.yml +500 -220
  111. data/config/locales/ru.yml +31 -8
  112. data/config/locales/sk.yml +38 -19
  113. data/config/locales/sl.yml +4 -0
  114. data/config/locales/sr-CS.yml +2 -0
  115. data/config/locales/sv.yml +29 -33
  116. data/config/locales/tr-TR.yml +34 -24
  117. data/config/locales/uk.yml +20 -3
  118. data/config/locales/zh-CN.yml +27 -15
  119. data/config/locales/zh-TW.yml +16 -16
  120. data/config/routes.rb +1 -0
  121. data/decidim-core.gemspec +4 -1
  122. data/lib/decidim/api/functions/component_list.rb +1 -1
  123. data/lib/decidim/api/functions/participatory_space_finder_base.rb +11 -1
  124. data/lib/decidim/api/interfaces/participatory_space_interface.rb +1 -1
  125. data/lib/decidim/api/types/component_type.rb +7 -0
  126. data/lib/decidim/api/types/user_group_type.rb +4 -0
  127. data/lib/decidim/api/types/user_type.rb +4 -0
  128. data/lib/decidim/asset_router/storage.rb +7 -2
  129. data/lib/decidim/attributes/rich_text.rb +38 -0
  130. data/lib/decidim/attributes/time_with_zone.rb +16 -2
  131. data/lib/decidim/attributes.rb +2 -0
  132. data/lib/decidim/content_parsers/blob_parser.rb +95 -0
  133. data/lib/decidim/content_parsers/user_parser.rb +1 -1
  134. data/lib/decidim/content_parsers.rb +1 -0
  135. data/lib/decidim/content_renderers/blob_renderer.rb +90 -0
  136. data/lib/decidim/content_renderers.rb +1 -0
  137. data/lib/decidim/core/engine.rb +29 -1
  138. data/lib/decidim/core/test/factories.rb +28 -0
  139. data/lib/decidim/core/test/shared_examples/authorable_interface_examples.rb +1 -1
  140. data/lib/decidim/core/test/shared_examples/comments_examples.rb +15 -2
  141. data/lib/decidim/core/test/shared_examples/reports_examples.rb +48 -6
  142. data/lib/decidim/core/test/shared_examples/social_share_examples.rb +32 -0
  143. data/lib/decidim/core/test/shared_examples/uncommentable_component_examples.rb +26 -0
  144. data/lib/decidim/core/test/shared_examples/versions_controller_examples.rb +26 -0
  145. data/lib/decidim/core/version.rb +1 -1
  146. data/lib/decidim/diffy_extension.rb +18 -0
  147. data/lib/decidim/form_builder.rb +1 -1
  148. data/lib/decidim/map/autocomplete.rb +1 -0
  149. data/lib/decidim/map/provider/dynamic_map/here.rb +1 -40
  150. data/lib/decidim/map/provider/static_map/here.rb +34 -0
  151. data/lib/decidim/moderation_tools.rb +16 -2
  152. data/lib/decidim/nicknamizable.rb +1 -1
  153. data/lib/decidim/participatory_space_user.rb +4 -0
  154. data/lib/decidim/query_extensions.rb +0 -26
  155. data/lib/decidim/reportable.rb +6 -2
  156. data/lib/decidim/settings_manifest.rb +2 -0
  157. data/lib/decidim/translatable_attributes.rb +10 -1
  158. data/lib/tasks/upgrade/clean_hidden_resources.rake +33 -0
  159. data/lib/tasks/upgrade/decidim_fix_categorization.rake +34 -8
  160. data/lib/tasks/upgrade/decidim_fix_nickname_uniqueness.rake +23 -20
  161. metadata +37 -15
  162. data/app/cells/decidim/author/flag.erb +0 -6
  163. data/app/cells/decidim/author/flag_user.erb +0 -14
  164. data/app/cells/decidim/flag_modal/flag_user.erb +0 -34
  165. data/app/cells/decidim/flag_modal/show.erb +0 -52
  166. data/app/cells/decidim/flag_modal_cell.rb +0 -56
  167. data/app/cells/decidim/profile_sidebar/show.erb +0 -167
  168. data/app/cells/decidim/profile_sidebar_cell.rb +0 -68
  169. data/app/packs/src/decidim/vendor/leaflet-tilelayer-here.js +0 -212
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module UserRoleChecker
5
+ # Shared behaviour for signed_in admins
6
+ extend ActiveSupport::Concern
7
+
8
+ private
9
+
10
+ def user_has_any_role?(user, participatory_space = nil, broad_check: false)
11
+ return false unless user
12
+
13
+ [
14
+ user.admin,
15
+ user.roles.any?,
16
+ participatory_process_user_role?(user, participatory_space, broad_check:),
17
+ assembly_user_role?(user, participatory_space, broad_check:),
18
+ conference_user_role?(user, participatory_space, broad_check:)
19
+ ].any?
20
+ end
21
+
22
+ def participatory_process_user_role?(user, participatory_process = nil, broad_check: false)
23
+ return false unless Decidim.module_installed?(:participatory_processes)
24
+ return Decidim::ParticipatoryProcessUserRole.exists?(user:) if broad_check
25
+ return false unless participatory_process.is_a?(Decidim::ParticipatoryProcess)
26
+
27
+ Decidim::ParticipatoryProcessUserRole.exists?(user:, participatory_process:)
28
+ end
29
+
30
+ def assembly_user_role?(user, assembly = nil, broad_check: false)
31
+ return false unless Decidim.module_installed?(:assemblies)
32
+ return Decidim::AssemblyUserRole.exists?(user:) if broad_check
33
+ return false unless assembly.is_a?(Decidim::Assembly)
34
+
35
+ Decidim::AssemblyUserRole.exists?(user:, assembly:)
36
+ end
37
+
38
+ def conference_user_role?(user, conference = nil, broad_check: false)
39
+ return false unless Decidim.module_installed?(:conferences)
40
+ return Decidim::ConferenceUserRole.exists?(user:) if broad_check
41
+ return false unless conference.is_a?(Decidim::Conference)
42
+
43
+ Decidim::ConferenceUserRole.exists?(user:, conference:)
44
+ end
45
+ end
46
+ end
@@ -16,7 +16,7 @@ module Decidim
16
16
  # Finds the CTA button path to reuse it in other places.
17
17
  def cta_button_path
18
18
  if current_organization.cta_button_path.present?
19
- current_organization.cta_button_path
19
+ "/#{current_organization.cta_button_path}"
20
20
  elsif Decidim::ParticipatoryProcess.where(organization: current_organization).published.any?
21
21
  decidim_participatory_processes.participatory_processes_path
22
22
  elsif current_user
@@ -35,7 +35,12 @@ module Decidim
35
35
  data: { "external-link": "text-only" }
36
36
  }.merge(map_html_options)
37
37
  return link_to(map_url, html_options) do
38
- image_tag decidim.static_map_path(sgid: resource.to_sgid.to_s), alt: "#{map_service_brand} - #{address_text}"
38
+ # We also add the latitude and the longitude to prevent the Workbox cache to be overly aggressive when updating a map
39
+ image_tag decidim.static_map_path(
40
+ sgid: resource.to_sgid.to_s,
41
+ latitude: resource.latitude,
42
+ longitude: resource.longitude
43
+ ), alt: "#{map_service_brand} - #{address_text}"
39
44
  end
40
45
  end
41
46
  end
@@ -13,7 +13,8 @@ module Decidim
13
13
  def order_selector(orders, options = {})
14
14
  render partial: "decidim/shared/orders", locals: {
15
15
  orders:,
16
- i18n_scope: options[:i18n_scope]
16
+ i18n_scope: options[:i18n_scope],
17
+ css_class: options[:css_class]
17
18
  }
18
19
  end
19
20
 
@@ -28,7 +28,7 @@ module Decidim
28
28
  end
29
29
 
30
30
  def participatory_space_floating_help
31
- return if help_section.blank?
31
+ return if help_section.blank? || strip_tags(translated_attribute(help_section).html_safe).blank?
32
32
 
33
33
  floating_help(help_id) { translated_attribute(help_section).html_safe }
34
34
  end
@@ -37,13 +37,22 @@ module Decidim
37
37
  end
38
38
  end
39
39
 
40
+ # Converts the blob and blob variant references to blob URLs.
41
+ def decidim_rich_text(html, **)
42
+ renderer = Decidim::ContentProcessor.renderer_klass(:blob).constantize.new(html)
43
+ renderer.render(**)
44
+ end
45
+
40
46
  def decidim_sanitize_editor(html, options = {})
41
47
  content_tag(:div, decidim_sanitize(html, options), class: %w(rich-text-display))
42
48
  end
43
49
 
44
50
  def decidim_sanitize_editor_admin(html, options = {})
45
51
  html = Decidim::IframeDisabler.new(html, options).perform
46
- decidim_sanitize_editor(html, { scrubber: Decidim::AdminInputScrubber.new }.merge(options))
52
+ decidim_sanitize_editor(
53
+ decidim_rich_text(html),
54
+ { scrubber: Decidim::AdminInputScrubber.new }.merge(options)
55
+ )
47
56
  end
48
57
 
49
58
  def decidim_html_escape(text)
@@ -51,7 +60,7 @@ module Decidim
51
60
  end
52
61
 
53
62
  def decidim_url_escape(text)
54
- decidim_html_escape(text).sub(/^javascript:/, "")
63
+ decidim_html_escape(text).sub(/^\s*javascript:/, "")
55
64
  end
56
65
 
57
66
  def decidim_sanitize_translated(text)
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ class HideChildResourcesJob < ApplicationJob
5
+ queue_as :user_report
6
+
7
+ def perform(resource, user_id)
8
+ spam_user = (resource.organization.users.find_by(email: Decidim::Ai::SpamDetection.reporting_user_email) if Decidim.module_installed?(:ai))
9
+ spam_user = resource.organization.admins.find(user_id) if spam_user.nil?
10
+
11
+ tool = Decidim::ModerationTools.new(resource, spam_user)
12
+
13
+ unless Decidim::Report.exists?("decidim_moderation_id" => tool.moderation.id, "decidim_user_id" => spam_user.id)
14
+ tool.create_report!({
15
+ reason: "parent_hidden",
16
+ details: I18n.t("report_details", scope: "decidim.reports.parent_hidden")
17
+ })
18
+ end
19
+
20
+ tool.update_report_count!
21
+ tool.hide!
22
+ end
23
+ end
24
+ end
@@ -25,6 +25,7 @@ module Decidim
25
25
  with_user(user) do
26
26
  @report = report
27
27
  @participatory_space = @report.moderation.participatory_space
28
+ @reportable = @report.moderation.reportable
28
29
  @organization = user.organization
29
30
  @user = user
30
31
  subject = I18n.t("hide.subject", scope: "decidim.reported_mailer")
@@ -156,15 +156,7 @@ module Decidim
156
156
 
157
157
  def self.publicable_public_resource_types
158
158
  @publicable_public_resource_types ||= public_resource_types
159
- .select { |klass| klass.constantize.column_names.include?("published_at") } - publicable_exceptions
160
- end
161
-
162
- def self.publicable_exceptions
163
- @publicable_exceptions = %w(
164
- Decidim::Blogs::Post
165
- ).select do |klass|
166
- klass.safe_constantize.present?
167
- end
159
+ .select { |klass| klass.constantize.column_names.include?("published_at") }
168
160
  end
169
161
 
170
162
  def self.ransackable_scopes(auth_object = nil)
@@ -74,7 +74,7 @@ module Decidim
74
74
  # Returns String.
75
75
  def file_type
76
76
  if file?
77
- url&.split(".")&.last&.downcase
77
+ url&.split(".")&.last&.split("&")&.first&.downcase
78
78
  elsif link?
79
79
  "link"
80
80
  end
@@ -5,7 +5,7 @@ module Decidim
5
5
  class Report < ApplicationRecord
6
6
  include Decidim::DownloadYourData
7
7
 
8
- REASONS = %w(spam offensive does_not_belong hidden_during_block).freeze
8
+ REASONS = %w(spam offensive does_not_belong hidden_during_block parent_hidden).freeze
9
9
 
10
10
  belongs_to :moderation, foreign_key: "decidim_moderation_id", class_name: "Decidim::Moderation"
11
11
  belongs_to :user, foreign_key: "decidim_user_id", class_name: "Decidim::User"
@@ -13,8 +13,6 @@ module Decidim
13
13
  include Decidim::UserReportable
14
14
  include Decidim::Traceable
15
15
 
16
- REGEXP_NICKNAME = /\A[\w-]+\z/
17
-
18
16
  class Roles
19
17
  def self.all
20
18
  Decidim.config.user_roles
@@ -51,8 +49,6 @@ module Decidim
51
49
 
52
50
  has_one_attached :download_your_data_file
53
51
 
54
- scope :not_deleted, -> { where(deleted_at: nil) }
55
-
56
52
  scope :managed, -> { where(managed: true) }
57
53
  scope :not_managed, -> { where(managed: false) }
58
54
 
@@ -21,11 +21,13 @@ module Decidim
21
21
 
22
22
  # Regex for name & nickname format validations
23
23
  REGEXP_NAME = /\A(?!.*[<>?%&\^*#@()\[\]=+:;"{}\\|])/
24
+ REGEXP_NICKNAME = /\A[a-z0-9_-]+\z/
24
25
 
25
26
  has_one_attached :avatar
26
27
  validates_avatar :avatar, uploader: Decidim::AvatarUploader
27
28
 
28
29
  validates :name, format: { with: REGEXP_NAME }
30
+ validates :nickname, format: { with: REGEXP_NICKNAME }, unless: -> { deleted? || managed? }
29
31
 
30
32
  scope :confirmed, -> { where.not(confirmed_at: nil) }
31
33
  scope :not_confirmed, -> { where(confirmed_at: nil) }
@@ -34,6 +36,8 @@ module Decidim
34
36
  scope :not_blocked, -> { where(blocked: false) }
35
37
  scope :available, -> { where(deleted_at: nil, blocked: false, managed: false) }
36
38
 
39
+ scope :not_deleted, -> { where(deleted_at: nil) }
40
+
37
41
  # Public: Returns a collection with all the public entities this user is following.
38
42
  #
39
43
  # This cannot be done as with a `has_many :following, through: :following_follows`
@@ -53,10 +53,18 @@ $(() => {
53
53
  }
54
54
 
55
55
  $(document).on("click.zf.trigger", (event) => {
56
- const target = `#${$(event.target).data("dialogOpen")}`;
57
- const redirectUrl = $(event.target).data("redirectUrl");
56
+ // Try to get the <a> directly or find the closest parent <a>
57
+ const $target = $(event.target).closest("a");
58
58
 
59
- if (!target || !redirectUrl) {
59
+ // Check if an <a> was found
60
+ if (!$target) {
61
+ return;
62
+ }
63
+
64
+ const dialogTarget = `#${$target.data("dialog-open")}`;
65
+ const redirectUrl = $target.data("redirectUrl");
66
+
67
+ if (!dialogTarget || !redirectUrl) {
60
68
  return;
61
69
  }
62
70
 
@@ -64,10 +72,10 @@ $(() => {
64
72
  attr("id", "redirect_url").
65
73
  attr("name", "redirect_url").
66
74
  attr("value", redirectUrl).
67
- appendTo(`${target} form`);
75
+ appendTo(`${dialogTarget} form`);
68
76
 
69
- $(`${target} a`).attr("href", (index, href) => {
70
- const querystring = jQuery.param({ "redirect_url": redirectUrl });
77
+ $(`${dialogTarget} a`).attr("href", (index, href) => {
78
+ const querystring = jQuery.param({"redirect_url": redirectUrl});
71
79
  return href + (href.match(/\?/) ? "&" : "?") + querystring;
72
80
  });
73
81
  });
@@ -29,12 +29,12 @@ export const formatInputDate = (date, formats) => {
29
29
  const month = dateList[1];
30
30
  const day = dateList[2];
31
31
 
32
- if (formats.order === "d-m-y") {
33
- return `${day}${formats.separator}${month}${formats.separator}${year}`;
32
+ if (formats.order === "m-d-y") {
33
+ return `${month}${formats.separator}${day}${formats.separator}${year}`;
34
34
  } else if (formats.order === "y-m-d") {
35
35
  return `${year}${formats.separator}${month}${formats.separator}${day}`;
36
36
  };
37
- return `${month}${formats.separator}${day}${formats.separator}${year}`;
37
+ return `${day}${formats.separator}${month}${formats.separator}${year}`;
38
38
  };
39
39
 
40
40
  export const formatInputTime = (time, format, input) => {
@@ -21,6 +21,7 @@ const updateActiveUploads = (modal) => {
21
21
  const files = document.querySelector(`[data-active-uploads=${modal.modal.id}]`)
22
22
  const previousId = Array.from(files.querySelectorAll("[type=hidden][id]"))
23
23
  const isMultiple = modal.options.multiple
24
+ const isTitled = modal.options.titled
24
25
 
25
26
  // fastest way to clean children nodes
26
27
  files.textContent = ""
@@ -34,16 +35,26 @@ const updateActiveUploads = (modal) => {
34
35
  let hidden = ""
35
36
  if (file.hiddenField) {
36
37
  // if there is hiddenField, this file is new
37
- const fileField = isMultiple
38
- ? `${modal.options.resourceName}[${modal.options.addAttribute}][${ix}][file]`
39
- : `${modal.options.resourceName}[${modal.options.addAttribute}]`
38
+ let fileField = null;
39
+ if (isMultiple) {
40
+ fileField = `${modal.options.resourceName}[${modal.options.addAttribute}][${ix}][file]`
41
+ } else if (isTitled) {
42
+ fileField = `${modal.options.resourceName}[${modal.options.addAttribute}][file]`
43
+ } else {
44
+ fileField = `${modal.options.resourceName}[${modal.options.addAttribute}]`;
45
+ }
40
46
 
41
47
  hidden = `<input type="hidden" name="${fileField}" value="${file.hiddenField}" />`
42
48
  } else {
43
49
  // otherwise, we keep the attachmentId
44
- const fileField = isMultiple
45
- ? `${modal.options.resourceName}[${modal.options.addAttribute}][${ix}][id]`
46
- : `${modal.options.resourceName}[${modal.options.addAttribute}]`
50
+ let fileField = null;
51
+ if (isMultiple) {
52
+ fileField = `${modal.options.resourceName}[${modal.options.addAttribute}][${ix}][id]`;
53
+ } else if (isTitled) {
54
+ fileField = `${modal.options.resourceName}[${modal.options.addAttribute}][id]`;
55
+ } else {
56
+ fileField = `${modal.options.resourceName}[${modal.options.addAttribute}]`;
57
+ }
47
58
 
48
59
  // convert all node attributes to string
49
60
  const attributes = Array.from(previousId.find(({ id }) => id === file.attachmentId).attributes).reduce((acc, { name, value }) => `${acc} ${name}="${value}"`, "")
@@ -51,10 +62,12 @@ const updateActiveUploads = (modal) => {
51
62
  hidden += `<input type="hidden" name="${fileField}" value="${file.attachmentId}" />`
52
63
  }
53
64
 
54
- if (modal.options.titled) {
65
+ if (isTitled) {
55
66
  const titleValue = modal.modal.querySelectorAll('input[type="text"]')[ix].value
56
67
  // NOTE - Renaming the attachment is not supported when multiple uploader is disabled
57
- const titleField = `${modal.options.resourceName}[${modal.options.addAttribute}][${ix}][title]`
68
+ const titleField = isMultiple
69
+ ? `${modal.options.resourceName}[${modal.options.addAttribute}][${ix}][title]`
70
+ : `${modal.options.resourceName}[${modal.options.addAttribute}][title]`
58
71
  hidden += `<input type="hidden" name="${titleField}" value="${escapeQuotes(titleValue)}" />`
59
72
 
60
73
  title = titleValue
@@ -69,6 +69,8 @@ import handleNotificationActions from "src/decidim/notifications_actions"
69
69
  import RemoteModal from "src/decidim/remote_modal"
70
70
  import selectActiveIdentity from "src/decidim/identity_selector_dialog"
71
71
  import createTooltip from "src/decidim/tooltips"
72
+ // Temporary disabling this feature because we have a poor performance. See https://github.com/decidim/decidim/issues/14431
73
+ // import fetchRemoteTooltip from "src/decidim/remote_tooltips"
72
74
  import createToggle from "src/decidim/toggle"
73
75
  import {
74
76
  createAccordion,
@@ -195,6 +197,9 @@ const initializer = (element = document) => {
195
197
  // Initialize data-toggles
196
198
  element.querySelectorAll("[data-toggle]").forEach((elem) => createToggle(elem))
197
199
 
200
+ // Temporary disabling this feature because we have a poor performance. See https://github.com/decidim/decidim/issues/14431
201
+ // element.querySelectorAll("[data-remote-tooltip]").forEach((elem) => fetchRemoteTooltip(elem))
202
+
198
203
  element.querySelectorAll(".new_report").forEach((elem) => changeReportFormBehavior(elem))
199
204
  }
200
205
 
@@ -1,5 +1,5 @@
1
1
  import "leaflet"
2
- import "src/decidim/vendor/leaflet-tilelayer-here"
2
+ import "leaflet-tilelayer-here"
3
3
 
4
4
  /**
5
5
  * NOTE:
@@ -0,0 +1,38 @@
1
+ import createTooltip from "src/decidim/tooltips"
2
+
3
+ /**
4
+ * Given the following HTML structure,
5
+ * <span data-remote-tooltip="true" tooltip-url="some url" data-author="true">
6
+ * <span></span>
7
+ * </span>
8
+ *
9
+ * This function will check if the HTMLElement where is attached to has a child, and will add a data tooltip attribute
10
+ * to the respective child in order to attach the fetched HTML content fetched under a json key as the content of the
11
+ * HTML tooltip. The DOM structure is expected to be like follows:
12
+ *
13
+ * <span data-remote-tooltip="true" tooltip-url="some url" data-author="true">
14
+ * <span data-tooltip="HTML content from json data field"></span>
15
+ * </span>
16
+ *
17
+ * @param {HTMLElement} node The element holding the initialization data
18
+ * @returns {void}
19
+ */
20
+ export default async function(node) {
21
+ const container = node.firstElementChild;
22
+
23
+ if (container) {
24
+ const response = await fetch(node.dataset.tooltipUrl, {
25
+ headers: {
26
+ "Content-Type": "application/json"
27
+ }
28
+ });
29
+ if (response.ok) {
30
+ const json = await response.json();
31
+
32
+ container.dataset.tooltip = json.data;
33
+ createTooltip(container);
34
+ } else {
35
+ console.error(response.status, response.statusText);
36
+ }
37
+ }
38
+ }
@@ -9,7 +9,7 @@ export default function createToggle(component) {
9
9
  const { toggle } = component.dataset
10
10
 
11
11
  if (!component.id) {
12
- // when component has no id, we enforce to have it one
12
+ // when component has no id, we enforce it to have one
13
13
  component.id = `toggle-${Math.random().toString(36).substring(7)}`
14
14
  }
15
15
 
@@ -63,9 +63,32 @@ export default function(node) {
63
63
  // append to dom hidden, to apply css transitions
64
64
  tooltip.setAttribute("aria-hidden", true)
65
65
 
66
- const append = () => {
67
- // do nothing if the tooltip is already present at the DOM
66
+ // used to detect if the user is on a mobile device by checking the user agent
67
+ const useMobile = (/Mobi|Android/i).test(navigator.userAgent);
68
+
69
+ // used not to collapse tooltip
70
+ let removeTooltip = () => {
71
+ tooltip.setAttribute("aria-hidden", "true");
72
+ }
73
+
74
+ // used to allow clicks outside the tooltip to take place on the page or device
75
+ const OutsideClick = (event) => {
76
+ if (!tooltip.contains(event.target) && event.target !== node) {
77
+ removeTooltip();
78
+ }
79
+ }
80
+
81
+ // function called again to allow clicks outside the tooltip to collapse the tooltip
82
+ removeTooltip = () => {
83
+ tooltip.setAttribute("aria-hidden", "true");
84
+ document.removeEventListener("click", OutsideClick)
85
+ }
86
+
87
+ const toggleTooltip = (event) => {
88
+ event.preventDefault();
89
+ // if the tooltip is visible in the DOM, hide it otherwise display
68
90
  if (tooltip.getAttribute("aria-hidden") === "false") {
91
+ tooltip.setAttribute("aria-hidden", "true");
69
92
  return
70
93
  }
71
94
 
@@ -106,27 +129,24 @@ export default function(node) {
106
129
  tooltip.style.left = `${positionX}px`
107
130
 
108
131
  tooltip.setAttribute("aria-hidden", false)
109
- }
110
-
111
- // in order to revoke the remove event when the mouse is over the trigger/tooltip
112
- let cancelRemove = false
113
132
 
114
- const remove = () => {
115
- cancelRemove = false
116
- // give some sleep time before hiding the element from the DOM
117
- setTimeout(() => !cancelRemove && tooltip.setAttribute("aria-hidden", true), 500);
133
+ // sleep time before hiding the element from the DOM
134
+ setTimeout(() => document.addEventListener("click", OutsideClick))
118
135
  }
119
136
 
120
- // keyboard listener is at root-level
121
- window.addEventListener("keydown", (event) => event.key === "Escape" && remove())
122
-
123
- node.addEventListener("mouseenter", append)
124
- node.addEventListener("mouseleave", remove)
125
- node.addEventListener("focus", append)
126
- node.addEventListener("blur", remove)
127
- tooltip.addEventListener("mouseenter", () => tooltip.setAttribute("aria-hidden", false))
128
- tooltip.addEventListener("mouseleave", remove)
129
-
130
- node.addEventListener("mouseover", () => (cancelRemove = true))
131
- tooltip.addEventListener("mouseover", () => (cancelRemove = true))
137
+ if (useMobile) {
138
+ // mobile use to click and toggle the tooltip
139
+ node.addEventListener("click", toggleTooltip);
140
+ window.addEventListener("keydown", (event) => event.key === "Escape" && removeTooltip())
141
+ } else {
142
+ // desktop use for hover and blur over tooltip
143
+ node.addEventListener("mouseenter", toggleTooltip)
144
+ node.addEventListener("mouseleave", removeTooltip)
145
+ node.addEventListener("focus", toggleTooltip)
146
+ node.addEventListener("blur", removeTooltip)
147
+
148
+ // tooltip hover listeners to prevent hiding when hovered
149
+ tooltip.addEventListener("mouseenter", () => tooltip.setAttribute("aria-hidden", false))
150
+ tooltip.addEventListener("mouseleave", removeTooltip)
151
+ }
132
152
  }
@@ -27,6 +27,10 @@
27
27
  @apply block;
28
28
  }
29
29
  }
30
+
31
+ > h3:not([class~="not-prose"]) {
32
+ @apply mb-[-0.4rem] pt-4 pb-2;
33
+ }
30
34
  }
31
35
 
32
36
  &__span {
@@ -0,0 +1,5 @@
1
+ .hashtags {
2
+ > label {
3
+ @apply ml-4;
4
+ }
5
+ }
@@ -115,7 +115,7 @@ header {
115
115
  @apply fixed bottom-0 left-0 z-40 bg-white w-full px-4 py-3 flex justify-between text-secondary shadow-[0_-4px_6px_rgba(198,198,198,0.25)];
116
116
 
117
117
  &__trigger {
118
- @apply flex flex-col items-center text-secondary cursor-pointer pl-4 border-l border-gray-3;
118
+ @apply flex flex-col items-center text-secondary cursor-pointer md:pl-4 md:border-l border-gray-3;
119
119
 
120
120
  svg {
121
121
  @apply w-6 h-6 fill-current;
@@ -167,7 +167,7 @@ header {
167
167
  }
168
168
 
169
169
  &__item {
170
- @apply relative;
170
+ @apply flex relative;
171
171
  }
172
172
 
173
173
  &__search {
@@ -175,14 +175,16 @@ header {
175
175
  }
176
176
 
177
177
  &__login {
178
- @apply w-20 h-full flex flex-row items-center relative text-secondary gap-1;
178
+ @apply w-auto h-full flex flex-row items-center relative text-secondary gap-1 pl-2 pr-2;
179
179
 
180
180
  svg {
181
181
  @apply w-8 h-6 fill-current;
182
182
  }
183
183
 
184
184
  svg + span {
185
- @apply text-sm first-letter:uppercase pr-4 w-16;
185
+ @apply text-sm first-letter:uppercase w-auto;
186
+
187
+ flex-shrink: 0;
186
188
  }
187
189
  }
188
190
  }
@@ -432,8 +434,12 @@ header {
432
434
  @apply h4 md:h3;
433
435
  }
434
436
 
437
+ nav {
438
+ @apply w-full md:w-1/4;
439
+ }
440
+
435
441
  &__menu {
436
- @apply w-full md:w-1/4 bg-primary px-4 md:px-8 pt-0 pb-3 md:py-3 divide-y divide-gray-3 text-white;
442
+ @apply w-full bg-primary px-4 md:px-8 pt-0 pb-3 md:py-3 divide-y divide-gray-3 text-white;
437
443
 
438
444
  > * {
439
445
  @apply py-3 md:py-3.5;
@@ -6,7 +6,7 @@
6
6
  --warning: #ad4910;
7
7
  --bg-warning: #ffeebd;
8
8
 
9
- @apply bg-background text-gray-2 rounded inline-flex items-center gap-1 px-2 font-semibold text-sm;
9
+ @apply bg-background text-gray-2 rounded inline-flex items-center gap-1 px-2 py-1 h-min font-semibold text-sm;
10
10
 
11
11
  &.success {
12
12
  @apply bg-[var(--bg-success)] text-[var(--success)];
@@ -45,7 +45,7 @@
45
45
 
46
46
  &__actions {
47
47
  &-main {
48
- @apply w-fit grid grid-cols-2 md:grid-cols-1 mx-auto gap-x-4 gap-y-6 md:gap-4;
48
+ @apply w-fit mx-auto gap-x-4 gap-y-6 md:gap-4;
49
49
 
50
50
  &__dropdown {
51
51
  @apply divide-y divide-gray-3 z-20 w-64;
@@ -29,7 +29,7 @@
29
29
  }
30
30
 
31
31
  &__sm {
32
- @apply w-16;
32
+ @apply w-16 pt-2;
33
33
  }
34
34
 
35
35
  &__sm &__number {
@@ -58,6 +58,7 @@
58
58
  @import "stylesheets/decidim/_hero.scss";
59
59
  @import "stylesheets/decidim/_actions.scss";
60
60
  @import "stylesheets/decidim/_emoji.scss";
61
+ @import "stylesheets/decidim/_hashtags.scss";
61
62
  }
62
63
 
63
64
  @import "stylesheets/decidim/vendor/datepicker_light.scss";
@@ -37,7 +37,6 @@
37
37
  }
38
38
 
39
39
  .conference-diploma .diploma__logo {
40
- border: 1px solid #333;
41
40
  padding: 2rem;
42
41
  }
43
42
 
@@ -47,10 +46,12 @@
47
46
  max-height: 100%;
48
47
  max-width: 100%;
49
48
  margin: 0;
49
+ padding-top: 10%;
50
50
  }
51
51
 
52
52
  .conference-diploma .diploma__border {
53
53
  margin: 0;
54
+ margin-top: 15%;
54
55
  -moz-border-image: url("../images/decidim/pattern.png") 20 repeat;
55
56
  -webkit-border-image: url("../images/decidim/pattern.png") 20 repeat;
56
57
  -o-border-image: url("../images/decidim/pattern.png") 20 repeat;
@@ -6,7 +6,7 @@ module Decidim
6
6
  #
7
7
  class AttachmentPresenter < SimpleDelegator
8
8
  def attachment_file_url
9
- attachment.attached_uploader(:file).url(host: attached_to.organization.host)
9
+ attachment.attached_uploader(:file).url
10
10
  end
11
11
 
12
12
  def attachment