decidim-core 0.28.4 → 0.28.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/activity_cell.rb +1 -4
  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 +3 -3
  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/_profile.scss +1 -1
  26. data/app/packs/stylesheets/decidim/_progress-bar.scss +1 -1
  27. data/app/packs/stylesheets/decidim/legacy/conference-diploma.scss +2 -1
  28. data/app/presenters/decidim/attachment_presenter.rb +1 -1
  29. data/app/services/decidim/base_diff_renderer.rb +26 -2
  30. data/app/services/decidim/email_notification_generator.rb +14 -5
  31. data/app/views/decidim/pages/_tabbed.html.erb +2 -2
  32. data/app/views/layouts/decidim/header/_menu_breadcrumb_mobile_tablet.html.erb +1 -1
  33. data/config/locales/ar.yml +12 -0
  34. data/config/locales/bn-BD.yml +1 -0
  35. data/config/locales/bs-BA.yml +98 -0
  36. data/config/locales/ca.yml +13 -9
  37. data/config/locales/cs.yml +5 -0
  38. data/config/locales/de.yml +16 -12
  39. data/config/locales/el.yml +3 -0
  40. data/config/locales/en.yml +4 -0
  41. data/config/locales/es-MX.yml +5 -1
  42. data/config/locales/es-PY.yml +5 -1
  43. data/config/locales/es.yml +11 -7
  44. data/config/locales/eu.yml +198 -181
  45. data/config/locales/fi-plain.yml +4 -0
  46. data/config/locales/fi.yml +39 -35
  47. data/config/locales/fr-CA.yml +5 -1
  48. data/config/locales/fr.yml +4 -0
  49. data/config/locales/ga-IE.yml +5 -0
  50. data/config/locales/gl.yml +4 -0
  51. data/config/locales/hu.yml +1 -1
  52. data/config/locales/id-ID.yml +4 -0
  53. data/config/locales/is-IS.yml +4 -1
  54. data/config/locales/it.yml +40 -0
  55. data/config/locales/ja.yml +15 -13
  56. data/config/locales/lb.yml +5 -0
  57. data/config/locales/lt.yml +1 -1
  58. data/config/locales/lv.yml +4 -0
  59. data/config/locales/nl.yml +6 -1
  60. data/config/locales/no.yml +5 -0
  61. data/config/locales/pl.yml +1 -1
  62. data/config/locales/pt-BR.yml +1 -1
  63. data/config/locales/pt.yml +11 -0
  64. data/config/locales/ro-RO.yml +243 -134
  65. data/config/locales/ru.yml +4 -0
  66. data/config/locales/sk.yml +5 -1
  67. data/config/locales/sv.yml +7 -7
  68. data/config/locales/tr-TR.yml +6 -1
  69. data/config/locales/uk.yml +4 -1
  70. data/config/locales/zh-CN.yml +5 -0
  71. data/config/locales/zh-TW.yml +4 -0
  72. data/config/routes.rb +1 -0
  73. data/decidim-core.gemspec +4 -1
  74. data/lib/decidim/api/functions/component_list.rb +1 -1
  75. data/lib/decidim/api/functions/participatory_space_finder_base.rb +11 -1
  76. data/lib/decidim/api/interfaces/participatory_space_interface.rb +1 -1
  77. data/lib/decidim/api/types/component_type.rb +7 -0
  78. data/lib/decidim/api/types/user_group_type.rb +4 -0
  79. data/lib/decidim/api/types/user_type.rb +4 -0
  80. data/lib/decidim/attributes/rich_text.rb +38 -0
  81. data/lib/decidim/attributes/time_with_zone.rb +11 -1
  82. data/lib/decidim/attributes.rb +2 -0
  83. data/lib/decidim/content_parsers/blob_parser.rb +93 -0
  84. data/lib/decidim/content_parsers.rb +1 -0
  85. data/lib/decidim/content_renderers/blob_renderer.rb +90 -0
  86. data/lib/decidim/content_renderers.rb +1 -0
  87. data/lib/decidim/core/engine.rb +29 -1
  88. data/lib/decidim/core/test/factories.rb +28 -0
  89. data/lib/decidim/core/test/shared_examples/authorable_interface_examples.rb +1 -1
  90. data/lib/decidim/core/test/shared_examples/comments_examples.rb +15 -2
  91. data/lib/decidim/core/version.rb +1 -1
  92. data/lib/decidim/diffy_extension.rb +18 -0
  93. data/lib/decidim/form_builder.rb +1 -1
  94. data/lib/decidim/map/autocomplete.rb +1 -0
  95. data/lib/decidim/participatory_space_user.rb +4 -0
  96. data/lib/decidim/query_extensions.rb +0 -26
  97. data/lib/decidim/settings_manifest.rb +2 -0
  98. data/lib/decidim/translatable_attributes.rb +6 -1
  99. data/lib/tasks/upgrade/decidim_fix_categorization.rake +34 -8
  100. metadata +28 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3ec3e95f4dbf1ac0e6178188a46ddf5d702bea045e85d76f64c1193af3c0e2ed
4
- data.tar.gz: 684aeb5ae98c9d8fe9fa9488721d153fbc42be5dcd1cd13d1328e34da3ebdef8
3
+ metadata.gz: 30504edbc5451f226b04213804ae8b02392132d8f66f9d5ebe9ea58ad264031e
4
+ data.tar.gz: 0ff217364122ef3812f7aa950e4908364126ceca46f135d0d5dc049b1ee19537
5
5
  SHA512:
6
- metadata.gz: fee3f0d928f2853e9953dc65e5232aae720997c23d7933e4e08a3154246ae6612a7688e4b9dc0c7af196b9f7043aacd08b7bee72e60ebe69b6b9ce057e17149c
7
- data.tar.gz: 02fb38ba880953a3b819f4ff1b3d88d6cde4dcd66a2753ddb5178fa524d73f8e8db14b4bcf4e862651ff16c45c1f6be4044520a6daf8d8f94b1ff9dfae299ec1
6
+ metadata.gz: 897aec8c06d1dd2110a5bd880e07061cab5e12495b707ed175afa81468c5ac162cf3d7a6643879eab83a82a9ca76232ff82dcb0d9b29ccf277d328fec6539a35
7
+ data.tar.gz: 88e9607861160a270e3604733a323fc2b1d359779c335ff8ccce97c9e587062c9c916d07682ad0ddda2b50abe2f718d3ce4baeb5bf77cbf3aab77ea231160014
@@ -106,10 +106,7 @@ module Decidim
106
106
  hash << id_prefix
107
107
  hash << I18n.locale.to_s
108
108
  hash << model.class.name.underscore
109
- hash << model.cache_key_with_version
110
- if (author_cell = author)
111
- hash.push(Digest::MD5.hexdigest(author_cell.send(:cache_hash)))
112
- end
109
+ hash << model.cache_key_with_version if model.respond_to?(:cache_key_with_version)
113
110
 
114
111
  hash.join(Decidim.cache_key_separator)
115
112
  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 %>
@@ -49,8 +49,26 @@ module Decidim
49
49
  @context_actions_options ||= options[:context_actions].map(&:to_sym)
50
50
  end
51
51
 
52
+ def profile_minicard
53
+ render
54
+ end
55
+
52
56
  private
53
57
 
58
+ # If the options hash has the demo key it means we are in the decidim-design engine,
59
+ # so it is not a real-world scenario with actual users
60
+ def data
61
+ @data ||= begin
62
+ internal_data = { author: true }
63
+ if has_tooltip? && !options.has_key?(:demo)
64
+ internal_data["remote_tooltip"] = true
65
+ internal_data["tooltip-url"] = decidim.profile_tooltip_path(raw_model.nickname)
66
+ end
67
+
68
+ internal_data
69
+ end
70
+ end
71
+
54
72
  def layout
55
73
  @layout ||= LAYOUTS.include?(options[:layout]) ? options[:layout] : :default
56
74
  end
@@ -162,5 +180,13 @@ module Decidim
162
180
  def resource_name
163
181
  @resource_name ||= from_context.class.name.demodulize.underscore
164
182
  end
183
+
184
+ def has_tooltip?
185
+ return false if model.deleted?
186
+ return false if model.respond_to?(:blocked?) && model.blocked?
187
+ return true if options.has_key?(:tooltip)
188
+
189
+ model.has_tooltip?
190
+ end
165
191
  end
166
192
  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 %>
@@ -71,6 +71,10 @@ module Decidim
71
71
 
72
72
  # DiffRenderer class for the current_version's item; falls back to `BaseDiffRenderer`.
73
73
  def diff_renderer_class
74
+ renderer_class = "#{current_version.item_type}DiffRenderer".safe_constantize
75
+
76
+ return renderer_class if renderer_class
77
+
74
78
  if current_version.item_type.deconstantize == "Decidim"
75
79
  "#{current_version.item_type.pluralize}::DiffRenderer".constantize
76
80
  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
- <div class="wrapper-mini translation-bar">
2
- <div class="row column">
1
+ <div>
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>
@@ -34,7 +34,7 @@ module Decidim
34
34
  parsed_url.query = new_query
35
35
  url = parsed_url.to_s
36
36
 
37
- link_to button_text, url, class: "button small hollow"
37
+ link_to button_text, url, class: "button button__sm button__transparent-secondary"
38
38
  end
39
39
 
40
40
  def button_text
@@ -35,6 +35,9 @@ module Decidim
35
35
  @user.name = ""
36
36
  @user.nickname = ""
37
37
  @user.email = ""
38
+ @user.personal_url = ""
39
+ @user.about = ""
40
+ @user.notifications_sending_frequency = "none"
38
41
  @user.delete_reason = @form.delete_reason
39
42
  @user.admin = false if @user.admin?
40
43
  @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, options = {})
42
+ renderer = Decidim::ContentProcessor.renderer_klass(:blob).constantize.new(html)
43
+ renderer.render(options)
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)
@@ -65,7 +65,7 @@ module Decidim
65
65
  #
66
66
  # Returns String.
67
67
  def file_type
68
- url&.split(".")&.last&.downcase
68
+ url&.split(".")&.last&.split("&")&.first&.downcase
69
69
  end
70
70
 
71
71
  def url
@@ -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
@@ -68,6 +68,7 @@ import markAsReadNotifications from "src/decidim/notifications"
68
68
  import RemoteModal from "src/decidim/remote_modal"
69
69
  import selectActiveIdentity from "src/decidim/identity_selector_dialog"
70
70
  import createTooltip from "src/decidim/tooltips"
71
+ import fetchRemoteTooltip from "src/decidim/remote_tooltips"
71
72
  import createToggle from "src/decidim/toggle"
72
73
  import {
73
74
  createAccordion,
@@ -189,6 +190,8 @@ const initializer = (element = document) => {
189
190
  // Initialize data-toggles
190
191
  element.querySelectorAll("[data-toggle]").forEach((elem) => createToggle(elem))
191
192
 
193
+ element.querySelectorAll("[data-remote-tooltip]").forEach((elem) => fetchRemoteTooltip(elem))
194
+
192
195
  element.querySelectorAll(".new_report").forEach((elem) => changeReportFormBehavior(elem))
193
196
  }
194
197
 
@@ -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
  }
@@ -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
@@ -41,7 +41,7 @@ module Decidim
41
41
  end
42
42
 
43
43
  def parse_i18n_changeset(attribute, values, type, diff)
44
- values.last.keys.each do |locale, _value|
44
+ (values.last.keys - ["machine_translations"]).each do |locale, _value|
45
45
  first_value = values.first.try(:[], locale)
46
46
  last_value = values.last.try(:[], locale)
47
47
  next if first_value == last_value
@@ -56,6 +56,27 @@ module Decidim
56
56
  }
57
57
  )
58
58
  end
59
+
60
+ return diff unless values.last.has_key?("machine_translations")
61
+
62
+ values.last.fetch("machine_translations").each_key do |locale, _value|
63
+ next unless I18n.available_locales.include?(locale.to_sym)
64
+
65
+ first_value = values.first.try(:[], "machine_translations").try(:[], locale)
66
+ last_value = values.last.try(:[], "machine_translations").try(:[], locale)
67
+
68
+ attribute_locale = :"#{attribute}_machine_translations_#{locale}"
69
+
70
+ diff.update(
71
+ attribute_locale => {
72
+ type:,
73
+ label: generate_i18n_label(attribute, locale, "decidim.machine_translations.automatic"),
74
+ old_value: first_value,
75
+ new_value: last_value
76
+ }
77
+ )
78
+ end
79
+
59
80
  diff
60
81
  end
61
82
 
@@ -108,7 +129,8 @@ module Decidim
108
129
  end
109
130
 
110
131
  # Returns a String.
111
- def generate_i18n_label(attribute, locale)
132
+ # i18n-tasks-use t("decidim.machine_translations.automatic")
133
+ def generate_i18n_label(attribute, locale, postfix = "")
112
134
  label = I18n.t(attribute, scope: i18n_scope)
113
135
  locale_name = if I18n.available_locales.include?(locale.to_sym)
114
136
  I18n.t("locale.name", locale:)
@@ -116,6 +138,8 @@ module Decidim
116
138
  locale
117
139
  end
118
140
 
141
+ locale_name = I18n.t(postfix, locale_name:, locale:) if postfix.present?
142
+
119
143
  "#{label} (#{locale_name})"
120
144
  end
121
145