decidim-core 0.29.1 → 0.29.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) 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/translation_bar/show.erb +2 -2
  9. data/app/cells/decidim/translation_bar_cell.rb +1 -1
  10. data/app/commands/decidim/destroy_account.rb +3 -0
  11. data/app/controllers/decidim/doorkeeper/credentials_controller.rb +1 -1
  12. data/app/controllers/decidim/links_controller.rb +1 -1
  13. data/app/controllers/decidim/profiles_controller.rb +4 -0
  14. data/app/helpers/concerns/decidim/user_role_checker.rb +46 -0
  15. data/app/helpers/decidim/cta_button_helper.rb +1 -1
  16. data/app/helpers/decidim/map_helper.rb +6 -1
  17. data/app/helpers/decidim/sanitize_helper.rb +11 -2
  18. data/app/models/decidim/attachment.rb +1 -1
  19. data/app/packs/src/decidim/append_redirect_url_to_modals.js +14 -6
  20. data/app/packs/src/decidim/direct_uploads/upload_field.js +21 -8
  21. data/app/packs/src/decidim/index.js +3 -0
  22. data/app/packs/src/decidim/remote_tooltips.js +38 -0
  23. data/app/packs/src/decidim/toggle.js +1 -1
  24. data/app/packs/src/decidim/tooltips.js +42 -22
  25. data/app/packs/stylesheets/decidim/_labels.scss +1 -1
  26. data/app/packs/stylesheets/decidim/_profile.scss +1 -1
  27. data/app/packs/stylesheets/decidim/_progress-bar.scss +1 -1
  28. data/app/packs/stylesheets/decidim/legacy/conference-diploma.scss +2 -1
  29. data/app/presenters/decidim/attachment_presenter.rb +1 -1
  30. data/app/services/decidim/base_diff_renderer.rb +26 -2
  31. data/app/services/decidim/email_notification_generator.rb +14 -5
  32. data/app/views/decidim/pages/_tabbed.html.erb +2 -2
  33. data/app/views/layouts/decidim/header/_menu_breadcrumb_mobile_tablet.html.erb +1 -1
  34. data/config/locales/ar.yml +16 -0
  35. data/config/locales/bn-BD.yml +1 -0
  36. data/config/locales/bs-BA.yml +98 -0
  37. data/config/locales/ca.yml +13 -9
  38. data/config/locales/cs.yml +5 -0
  39. data/config/locales/de.yml +18 -14
  40. data/config/locales/el.yml +7 -0
  41. data/config/locales/en.yml +4 -0
  42. data/config/locales/es-MX.yml +5 -1
  43. data/config/locales/es-PY.yml +5 -1
  44. data/config/locales/es.yml +11 -7
  45. data/config/locales/eu.yml +198 -181
  46. data/config/locales/fi-plain.yml +4 -0
  47. data/config/locales/fi.yml +39 -35
  48. data/config/locales/fr-CA.yml +6 -2
  49. data/config/locales/fr.yml +5 -1
  50. data/config/locales/ga-IE.yml +9 -0
  51. data/config/locales/gl.yml +8 -0
  52. data/config/locales/hu.yml +3 -3
  53. data/config/locales/id-ID.yml +8 -0
  54. data/config/locales/is-IS.yml +8 -1
  55. data/config/locales/it.yml +19 -0
  56. data/config/locales/ja.yml +15 -13
  57. data/config/locales/lb.yml +9 -0
  58. data/config/locales/lt.yml +5 -1
  59. data/config/locales/lv.yml +8 -0
  60. data/config/locales/nl.yml +10 -1
  61. data/config/locales/no.yml +9 -0
  62. data/config/locales/pl.yml +1 -1
  63. data/config/locales/pt-BR.yml +2 -1
  64. data/config/locales/pt.yml +14 -0
  65. data/config/locales/ro-RO.yml +258 -135
  66. data/config/locales/ru.yml +8 -0
  67. data/config/locales/sk.yml +9 -1
  68. data/config/locales/sv.yml +7 -7
  69. data/config/locales/tr-TR.yml +10 -1
  70. data/config/locales/uk.yml +8 -1
  71. data/config/locales/zh-CN.yml +9 -0
  72. data/config/locales/zh-TW.yml +8 -0
  73. data/config/routes.rb +1 -0
  74. data/decidim-core.gemspec +4 -1
  75. data/lib/decidim/api/functions/component_list.rb +1 -1
  76. data/lib/decidim/api/functions/participatory_space_finder_base.rb +11 -1
  77. data/lib/decidim/api/interfaces/participatory_space_interface.rb +1 -1
  78. data/lib/decidim/api/types/component_type.rb +7 -0
  79. data/lib/decidim/api/types/user_group_type.rb +4 -0
  80. data/lib/decidim/api/types/user_type.rb +4 -0
  81. data/lib/decidim/attributes/rich_text.rb +38 -0
  82. data/lib/decidim/attributes/time_with_zone.rb +11 -1
  83. data/lib/decidim/attributes.rb +2 -0
  84. data/lib/decidim/content_parsers/blob_parser.rb +93 -0
  85. data/lib/decidim/content_parsers.rb +1 -0
  86. data/lib/decidim/content_renderers/blob_renderer.rb +90 -0
  87. data/lib/decidim/content_renderers.rb +1 -0
  88. data/lib/decidim/core/engine.rb +29 -1
  89. data/lib/decidim/core/test/factories.rb +28 -0
  90. data/lib/decidim/core/test/shared_examples/authorable_interface_examples.rb +1 -1
  91. data/lib/decidim/core/test/shared_examples/comments_examples.rb +15 -2
  92. data/lib/decidim/core/version.rb +1 -1
  93. data/lib/decidim/diffy_extension.rb +18 -0
  94. data/lib/decidim/form_builder.rb +1 -1
  95. data/lib/decidim/map/autocomplete.rb +1 -0
  96. data/lib/decidim/participatory_space_user.rb +4 -0
  97. data/lib/decidim/query_extensions.rb +0 -26
  98. data/lib/decidim/settings_manifest.rb +2 -0
  99. data/lib/decidim/translatable_attributes.rb +6 -1
  100. data/lib/tasks/upgrade/decidim_fix_categorization.rake +34 -8
  101. metadata +28 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52a3dc5c9feb9a081fb911d75e25eba2847b1d159837b23853140d96f3c6754b
4
- data.tar.gz: 18e41bc984ad456282a1b0c9d3ec3fef7fc89376b330b79198fe3e650cee1301
3
+ metadata.gz: 83ba22655575833dce2ce20e91060aa195abccf10cd5d185dbadbc04e79fcd93
4
+ data.tar.gz: a53c89d6b0c01046408b2872119406ffbd1a80a50b943256b872c8575b069e4a
5
5
  SHA512:
6
- metadata.gz: 1c3e01dc59cb7b999828421bfad4e0bdbbddc813200b4b99de17bd89c4b6a664ad0f98af080ec0f84bcd335b89f99fa871c851d56429ee998979ad68bbec4c3c
7
- data.tar.gz: 593fff36a401ad9116314865972323861374a70b53883c5c7421f902899d3ea0b934f64aaec1f2c9ed8879440fd647344e60a67beab12e76924851f7b377e788
6
+ metadata.gz: 07db0787cc696b27044eb8765477b338a16f7263cd29d9231dfd8e7145f5c4b63b19753a90b4d35c5e7614c1a69d47104435fcb2ecf12badea9a1163709ccbef
7
+ data.tar.gz: 034e07f956b0ad613172afdd3a56cbb3e37e273555537919073bf75b63dfe8b2ab7ec5502ecf87fb224cd5d525ad009a01e0db6742815ce61eec643c0fa1f1ae
@@ -105,9 +105,6 @@ module Decidim
105
105
  hash << I18n.locale.to_s
106
106
  hash << model.class.name.underscore
107
107
  hash << model.cache_key_with_version if model.respond_to?(:cache_key_with_version)
108
- if (author_cell = author)
109
- hash.push(Digest::MD5.hexdigest(author_cell.send(:cache_hash)))
110
- end
111
108
 
112
109
  hash.join(Decidim.cache_key_separator)
113
110
  end
@@ -1,6 +1,7 @@
1
- <% data = has_tooltip? ? { tooltip: render(:profile_minicard).html_safe } : nil %>
2
- <span class="author" data-author>
3
- <%= content_tag :span, class: "author__container#{" is-compact" if layout == :compact}", data: do %>
1
+ <%# If the options hash has the demo key it means we are in the decidim-design engine, so it is not a real-world scenario with actual users %>
2
+ <% tooltip = options.has_key?(:demo) ? { tooltip: render(:profile_minicard).html_safe } : nil %>
3
+ <%= content_tag(:span, class: :author, data: ) do %>
4
+ <%= content_tag :span, class: "author__container#{" is-compact" if layout == :compact}", data: tooltip do %>
4
5
  <% if layout == :compact %>
5
6
  <%= render :avatar %>
6
7
 
@@ -24,4 +25,4 @@
24
25
  <%= render action %>
25
26
  <% end %>
26
27
  <% end %>
27
- </span>
28
+ <% end %>
@@ -47,8 +47,26 @@ module Decidim
47
47
  @context_actions_options ||= options[:context_actions].map(&:to_sym)
48
48
  end
49
49
 
50
+ def profile_minicard
51
+ render
52
+ end
53
+
50
54
  private
51
55
 
56
+ # If the options hash has the demo key it means we are in the decidim-design engine,
57
+ # so it is not a real-world scenario with actual users
58
+ def data
59
+ @data ||= begin
60
+ internal_data = { author: true }
61
+ if has_tooltip? && !options.has_key?(:demo)
62
+ internal_data["remote_tooltip"] = true
63
+ internal_data["tooltip-url"] = decidim.profile_tooltip_path(raw_model.nickname)
64
+ end
65
+
66
+ internal_data
67
+ end
68
+ end
69
+
52
70
  def layout
53
71
  @layout ||= LAYOUTS.include?(options[:layout]) ? options[:layout] : :default
54
72
  end
@@ -160,5 +178,13 @@ module Decidim
160
178
  def resource_name
161
179
  @resource_name ||= from_context.class.name.demodulize.underscore
162
180
  end
181
+
182
+ def has_tooltip?
183
+ return false if model.deleted?
184
+ return false if model.respond_to?(:blocked?) && model.blocked?
185
+ return true if options.has_key?(:tooltip)
186
+
187
+ model.has_tooltip?
188
+ end
163
189
  end
164
190
  end
@@ -1,10 +1,12 @@
1
- <%= link_to resource_path, class: "card__search", id: dom_id(resource) do %>
1
+ <%= content_tag :div, id: dom_id(resource), class: "card__search" do %>
2
2
  <%= content_tag title_tag, class: "h4 card__search-title" do %>
3
- <%= title %>
3
+ <%= link_to resource_path, class: "card__search" do %>
4
+ <%= title %>
5
+ <% end %>
4
6
  <% end %>
5
7
  <% if metadata_cell.present? %>
6
8
  <div class="card__search-metadata">
7
- <%= cell metadata_cell, resource, links: false %>
9
+ <%= cell metadata_cell, resource, links: false %>
8
10
  </div>
9
11
  <% end %>
10
12
  <% end %>
@@ -70,6 +70,10 @@ module Decidim
70
70
 
71
71
  # DiffRenderer class for the current_version's item; falls back to `BaseDiffRenderer`.
72
72
  def diff_renderer_class
73
+ renderer_class = "#{current_version.item_type}DiffRenderer".safe_constantize
74
+
75
+ return renderer_class if renderer_class
76
+
73
77
  if current_version.item_type.deconstantize == "Decidim"
74
78
  "#{current_version.item_type.pluralize}::DiffRenderer".constantize
75
79
  else
@@ -50,7 +50,7 @@ module Decidim
50
50
  end
51
51
 
52
52
  def main_image_url
53
- newsletter.template.images_container.attached_uploader(:main_image).url(host: organization.host)
53
+ newsletter.template.images_container.attached_uploader(:main_image).url
54
54
  end
55
55
 
56
56
  def organization_primary_color
@@ -1,6 +1,6 @@
1
1
  <div>
2
- <div class="row column">
2
+ <div class="pt-4 pb-4 text-center bg-background">
3
3
  <%= link %>
4
- <span class="translation-button-help"><%= help_text %></span>
4
+ <span class="translation-button-help ml-4"><%= help_text %></span>
5
5
  </div>
6
6
  </div>
@@ -32,7 +32,7 @@ module Decidim
32
32
  parsed_url.query = new_query
33
33
  url = parsed_url.to_s
34
34
 
35
- link_to button_text, url, class: "button small hollow"
35
+ link_to button_text, url, class: "button button__sm button__transparent-secondary"
36
36
  end
37
37
 
38
38
  def button_text
@@ -37,6 +37,9 @@ module Decidim
37
37
  current_user.name = ""
38
38
  current_user.nickname = ""
39
39
  current_user.email = ""
40
+ current_user.personal_url = ""
41
+ current_user.about = ""
42
+ current_user.notifications_sending_frequency = "none"
40
43
  current_user.delete_reason = @form.delete_reason
41
44
  current_user.admin = false if current_user.admin?
42
45
  current_user.deleted_at = Time.current
@@ -28,7 +28,7 @@ module Decidim
28
28
  end
29
29
 
30
30
  def avatar_url
31
- avatar_url = current_resource_owner.attached_uploader(:avatar).url(host: current_resource_owner.organization.host)
31
+ avatar_url = current_resource_owner.attached_uploader(:avatar).url
32
32
  return unless avatar_url
33
33
 
34
34
  unless %r{^https?://}.match? avatar_url
@@ -41,7 +41,7 @@ module Decidim
41
41
  end
42
42
 
43
43
  def escape_url(external_url)
44
- before_fragment, fragment = external_url.split("#", 2)
44
+ before_fragment, fragment = URI.decode_www_form_component(external_url).split("#", 2)
45
45
  escaped_before_fragment = URI::Parser.new.escape(before_fragment)
46
46
 
47
47
  if fragment
@@ -27,6 +27,10 @@ module Decidim
27
27
  redirect_to profile_activity_path(nickname: params[:nickname])
28
28
  end
29
29
 
30
+ def tooltip
31
+ render json: { data: cell("decidim/author", profile_holder.presenter).profile_minicard }
32
+ end
33
+
30
34
  def following
31
35
  @content_cell = "decidim/following"
32
36
  @title_key = "following"
@@ -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
@@ -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)
@@ -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
@@ -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
  });
@@ -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,7 @@ 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
+ import fetchRemoteTooltip from "src/decidim/remote_tooltips"
72
73
  import createToggle from "src/decidim/toggle"
73
74
  import {
74
75
  createAccordion,
@@ -195,6 +196,8 @@ const initializer = (element = document) => {
195
196
  // Initialize data-toggles
196
197
  element.querySelectorAll("[data-toggle]").forEach((elem) => createToggle(elem))
197
198
 
199
+ element.querySelectorAll("[data-remote-tooltip]").forEach((elem) => fetchRemoteTooltip(elem))
200
+
198
201
  element.querySelectorAll(".new_report").forEach((elem) => changeReportFormBehavior(elem))
199
202
  }
200
203
 
@@ -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
  }
@@ -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 {
@@ -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