decidim-core 0.30.3 → 0.30.5

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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/activity_cell.rb +6 -0
  3. data/app/cells/decidim/card_g/show.erb +1 -1
  4. data/app/cells/decidim/notification/deleted.erb +12 -0
  5. data/app/cells/decidim/notification/not_available.erb +12 -0
  6. data/app/cells/decidim/notification_cell.rb +5 -1
  7. data/app/cells/decidim/share_widget/modal.erb +2 -1
  8. data/app/commands/decidim/destroy_account.rb +49 -4
  9. data/app/controllers/decidim/download_your_data_controller.rb +5 -2
  10. data/app/events/decidim/welcome_notification_event.rb +1 -1
  11. data/app/jobs/decidim/remove_search_indexes_job.rb +18 -0
  12. data/app/mailers/decidim/notifications_digest_mailer.rb +18 -1
  13. data/app/models/decidim/component.rb +3 -0
  14. data/app/models/decidim/notification.rb +10 -0
  15. data/app/models/decidim/private_export.rb +4 -0
  16. data/app/models/decidim/user.rb +4 -0
  17. data/app/models/decidim/user_base_entity.rb +1 -1
  18. data/app/packs/src/decidim/editor/extensions/link/index.js +0 -1
  19. data/app/packs/src/decidim/editor/index.js +5 -1
  20. data/app/packs/src/decidim/editor/test/extensions/link.test.js +2 -2
  21. data/app/packs/src/decidim/editor/test/toolbar/shared/behaves_like_basic_link.js +1 -1
  22. data/app/packs/src/decidim/map/controller/markers.js +3 -1
  23. data/app/packs/stylesheets/decidim/_footer.scss +5 -0
  24. data/app/packs/stylesheets/decidim/_modal.scss +5 -1
  25. data/app/packs/stylesheets/decidim/_modal_fingerprint.scss +1 -1
  26. data/app/presenters/decidim/log/user_presenter.rb +2 -1
  27. data/app/presenters/decidim/user_presenter.rb +6 -1
  28. data/app/services/decidim/download_your_data_exporter.rb +15 -4
  29. data/app/services/decidim/open_data_exporter.rb +2 -1
  30. data/app/views/decidim/download_your_data/_export.html.erb +1 -1
  31. data/app/views/decidim/export_mailer/download_your_data_export.html.erb +1 -1
  32. data/app/views/decidim/export_mailer/export.html.erb +1 -1
  33. data/app/views/decidim/pages/_tabbed.html.erb +1 -1
  34. data/app/views/decidim/shared/_orders.html.erb +1 -1
  35. data/app/views/layouts/decidim/header/_main_links_mobile_account.html.erb +15 -1
  36. data/config/locales/ar.yml +0 -4
  37. data/config/locales/bg.yml +0 -8
  38. data/config/locales/ca-IT.yml +17 -13
  39. data/config/locales/ca.yml +15 -11
  40. data/config/locales/cs.yml +9 -19
  41. data/config/locales/de.yml +5 -21
  42. data/config/locales/el.yml +0 -3
  43. data/config/locales/en.yml +21 -17
  44. data/config/locales/es-MX.yml +14 -10
  45. data/config/locales/es-PY.yml +14 -10
  46. data/config/locales/es.yml +13 -9
  47. data/config/locales/eu.yml +54 -51
  48. data/config/locales/fa-IR.yml +3 -0
  49. data/config/locales/fi-plain.yml +12 -8
  50. data/config/locales/fi.yml +12 -8
  51. data/config/locales/fr-CA.yml +23 -12
  52. data/config/locales/fr.yml +23 -12
  53. data/config/locales/ga-IE.yml +0 -4
  54. data/config/locales/gl.yml +0 -3
  55. data/config/locales/hu.yml +0 -6
  56. data/config/locales/id-ID.yml +0 -3
  57. data/config/locales/it.yml +3 -4
  58. data/config/locales/ja.yml +26 -22
  59. data/config/locales/ko.yml +3 -0
  60. data/config/locales/lb.yml +0 -3
  61. data/config/locales/lt.yml +0 -5
  62. data/config/locales/lv.yml +0 -3
  63. data/config/locales/mt.yml +3 -0
  64. data/config/locales/nl.yml +0 -3
  65. data/config/locales/no.yml +0 -3
  66. data/config/locales/pl.yml +11 -7
  67. data/config/locales/pt-BR.yml +552 -5
  68. data/config/locales/pt.yml +0 -3
  69. data/config/locales/ro-RO.yml +453 -126
  70. data/config/locales/ru.yml +0 -4
  71. data/config/locales/sk.yml +0 -3
  72. data/config/locales/sl.yml +0 -1
  73. data/config/locales/sv.yml +28 -13
  74. data/config/locales/tr-TR.yml +0 -5
  75. data/config/locales/uk.yml +0 -3
  76. data/config/locales/vi.yml +3 -0
  77. data/config/locales/zh-CN.yml +0 -3
  78. data/config/locales/zh-TW.yml +0 -5
  79. data/db/migrate/20250819110800_convert_private_exports_id_to_uuid.rb +55 -0
  80. data/decidim-core.gemspec +1 -0
  81. data/lib/decidim/core/engine.rb +1 -0
  82. data/lib/decidim/core/seeds.rb +3 -3
  83. data/lib/decidim/core/test/factories.rb +27 -2
  84. data/lib/decidim/core/test/shared_examples/comments_examples.rb +59 -5
  85. data/lib/decidim/core/test/shared_examples/fingerprint_examples.rb +13 -0
  86. data/lib/decidim/core/test/shared_examples/process_announcements_examples.rb +35 -1
  87. data/lib/decidim/core/version.rb +1 -1
  88. data/lib/decidim/events/base_event.rb +4 -0
  89. data/lib/decidim/has_private_users.rb +1 -0
  90. data/lib/decidim/seeds.rb +1 -1
  91. data/lib/decidim/webpacker/configuration.rb +5 -1
  92. data/lib/tasks/upgrade/clean.rake +11 -0
  93. data/lib/tasks/upgrade/decidim_fix_action_log.rake +28 -0
  94. data/lib/tasks/upgrade/decidim_remove_deleted_users_left_data_tasks.rake +30 -0
  95. data/lib/tasks/upgrade/fix_deleted_private_follows.rake +26 -0
  96. metadata +27 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df5ddb6f2f1e3c2557f89a0a2b9bab41c3108ad0ce0e728bfb317502cc6aa1a9
4
- data.tar.gz: ed7e8f76a6f52fd5040a7bfbb462276d891406a13198df8571d6e154fb504a2f
3
+ metadata.gz: f8792833d7ba900781bcd99e714b0fc117c8bf63b1d92894a12a4d8d9eebfcab
4
+ data.tar.gz: 21ae4185554af11eb516c1e7e0b6af616497331ab28234bd1c5199636fe40402
5
5
  SHA512:
6
- metadata.gz: e3ee0aa2b315e4038a9737c46b8453b075d4d058569d060b777c530a4cb62b71edd37f99bddecdf50fc810e7d9995a89729c60b10cbd10363b6c14d217b069d4
7
- data.tar.gz: 7f2b0baa65e1c613ee50d8ff74c4d1bb0a38c2224d51bfa76b34da5a44ec2024fd0e4bcba51a6664d283786ce62f0a4dbd05f4125011b8136750599106e6369d
6
+ metadata.gz: 8d56c970e80a667e474858352900d599b48dc392d99c239e3ea732af530f457cbe0a72276b9da630e5aff999eff6585dbe8ce7a8da66346df7ee491901366d59
7
+ data.tar.gz: aba95c516675ae9eff886fb93c4c29c94fede6adb809c26aa2fb2bf17fde468c7e1a7f9a8875708f13061595410f0dfaff83938ff7ff011682b8011f1e99d776
@@ -12,6 +12,12 @@ module Decidim
12
12
  return unless renderable?
13
13
 
14
14
  render
15
+ rescue NoMethodError => e
16
+ # Soft-deleted components or participatory spaces could cause errors
17
+ # when rendering activities. We log them for further inspection but
18
+ # avoid breaking the entire activity feed.
19
+ Rails.logger.error("Error rendering activity cell for #{model.id}: #{e.message}")
20
+ nil
15
21
  end
16
22
 
17
23
  # Since activity logs could be linked to resource no longer available
@@ -1,7 +1,7 @@
1
1
  <%= link_to resource_path, class: classes[:default], id: resource_id do %>
2
2
  <div class="<%= classes[:img] %>">
3
3
  <% if has_image? %>
4
- <%= image_tag resource_image_url, alt: alt_title %>
4
+ <%= image_tag resource_image_url, alt: "" %>
5
5
  <% else %>
6
6
  <%= external_icon "media/images/placeholder-card-g.svg", class: "card__placeholder-g" %>
7
7
  <% end %>
@@ -0,0 +1,12 @@
1
+ <div class="notification" data-notification>
2
+ <div class="notification__wrapper">
3
+ <div class="notification__time" title="<%= l(notification.created_at) %>"> <%= notification.created_at_in_words %></div>
4
+ <div class="notification__snippet">
5
+ <span class="notification__snippet-title text-gray"><%= t("decidim.notifications.show.deleted") %></span>
6
+ </div>
7
+ </div>
8
+ <%= link_to model, remote: true, method: :delete, class: "notification__button", data: { "notification-read": "" } do %>
9
+ <span class="sr-only md:not-sr-only"><%= t("mark_as_read", scope: "layouts.decidim.notifications_dashboard") %></span>
10
+ <%= icon "check-line", class: "fill-current" %>
11
+ <% end %>
12
+ </div>
@@ -0,0 +1,12 @@
1
+ <div class="notification" data-notification>
2
+ <div class="notification__wrapper">
3
+ <div class="notification__time" title="<%= l(notification.created_at) %>"> <%= notification.created_at_in_words %></div>
4
+ <div class="notification__snippet">
5
+ <span class="notification__snippet-title text-gray"><%= t("decidim.notifications.show.not_available") %></span>
6
+ </div>
7
+ </div>
8
+ <%= link_to model, remote: true, method: :delete, class: "notification__button", data: { "notification-read": "" } do %>
9
+ <span class="sr-only md:not-sr-only"><%= t("mark_as_read", scope: "layouts.decidim.notifications_dashboard") %></span>
10
+ <%= icon "check-line", class: "fill-current" %>
11
+ <% end %>
12
+ </div>
@@ -7,7 +7,11 @@ module Decidim
7
7
  include Decidim::Core::Engine.routes.url_helpers
8
8
 
9
9
  def show
10
- if notification.event_class_instance.try(:hidden_resource?)
10
+ if !notification.can_participate?(current_user)
11
+ render :not_available
12
+ elsif notification.deleted_resource?
13
+ render :deleted
14
+ elsif notification.hidden_resource?
11
15
  render :moderated
12
16
  else
13
17
  render :show
@@ -1,7 +1,8 @@
1
1
  <%= decidim_modal id: "socialShare", class: "share-modal" do %>
2
2
  <div data-dialog-container>
3
3
  <%= icon "share-line" %>
4
- <h2 id="dialog-title-socialShare" tabindex="-1" data-dialog-title><%= t("share", scope: "decidim.shared.share_modal") %></h2>
4
+ <h2 id="dialog-title-social-share-modal" tabindex="-1" data-dialog-title><%= t("share", scope: "decidim.shared.share_modal") %></h2>
5
+
5
6
  <div>
6
7
  <%= social_share_button_tag(decidim_page_title,
7
8
  url: decidim_meta_url,
@@ -20,6 +20,15 @@ module Decidim
20
20
  destroy_user_identities
21
21
  destroy_user_group_memberships
22
22
  destroy_follows
23
+ destroy_user_versions
24
+ destroy_user_private_exports
25
+ destroy_user_access_grants
26
+ destroy_user_access_tokens
27
+ destroy_user_reminders
28
+ destroy_user_notifications
29
+ destroy_user_badges
30
+ destroy_user_endorsements
31
+ destroy_user_reports
23
32
  destroy_participatory_space_private_user
24
33
  delegate_destroy_to_participatory_spaces
25
34
  end
@@ -48,8 +57,44 @@ module Decidim
48
57
  current_user.save!
49
58
  end
50
59
 
60
+ def destroy_user_badges
61
+ Decidim::Gamification::BadgeScore.where(user: current_user).find_each(&:destroy)
62
+ end
63
+
64
+ def destroy_user_reports
65
+ Decidim::UserModeration.where(user: current_user).find_each(&:destroy)
66
+ end
67
+
68
+ def destroy_user_endorsements
69
+ Decidim::Endorsement.where(author: current_user).find_each(&:destroy)
70
+ end
71
+
51
72
  def destroy_user_identities
52
- current_user.identities.destroy_all
73
+ current_user.identities.find_each(&:destroy)
74
+ end
75
+
76
+ def destroy_user_versions
77
+ current_user.versions.find_each(&:destroy)
78
+ end
79
+
80
+ def destroy_user_private_exports
81
+ current_user.private_exports.find_each(&:destroy)
82
+ end
83
+
84
+ def destroy_user_access_grants
85
+ current_user.access_grants.find_each(&:destroy)
86
+ end
87
+
88
+ def destroy_user_access_tokens
89
+ current_user.access_tokens.find_each(&:destroy)
90
+ end
91
+
92
+ def destroy_user_reminders
93
+ current_user.reminders.find_each(&:destroy)
94
+ end
95
+
96
+ def destroy_user_notifications
97
+ current_user.notifications.find_each(&:destroy)
53
98
  end
54
99
 
55
100
  def destroy_user_group_memberships
@@ -57,12 +102,12 @@ module Decidim
57
102
  end
58
103
 
59
104
  def destroy_follows
60
- Decidim::Follow.where(followable: current_user).destroy_all
61
- Decidim::Follow.where(user: current_user).destroy_all
105
+ Decidim::Follow.where(followable: current_user).find_each(&:destroy)
106
+ Decidim::Follow.where(user: current_user).find_each(&:destroy)
62
107
  end
63
108
 
64
109
  def destroy_participatory_space_private_user
65
- Decidim::ParticipatorySpacePrivateUser.where(user: current_user).destroy_all
110
+ Decidim::ParticipatorySpacePrivateUser.where(user: current_user).find_each(&:destroy)
66
111
  end
67
112
 
68
113
  def delegate_destroy_to_participatory_spaces
@@ -43,7 +43,10 @@ module Decidim
43
43
  def download_file
44
44
  enforce_permission_to(:download, :user, current_user:)
45
45
 
46
- if private_export.expired?
46
+ if private_export.blank?
47
+ flash[:error] = t("decidim.account.download_your_data_export.export_not_found")
48
+ redirect_to download_your_data_path
49
+ elsif private_export.expired?
47
50
  flash[:error] = t("decidim.account.download_your_data_export.export_expired")
48
51
  redirect_to download_your_data_path
49
52
  elsif private_export.file.attached?
@@ -57,7 +60,7 @@ module Decidim
57
60
  private
58
61
 
59
62
  def private_export
60
- @private_export ||= current_user.private_exports.find(params[:uuid])
63
+ @private_export ||= current_user.private_exports.where(uuid: params[:uuid]).first
61
64
  end
62
65
 
63
66
  def help_definitions
@@ -43,7 +43,7 @@ module Decidim
43
43
 
44
44
  def interpolate(template)
45
45
  template
46
- .gsub("{{name}}", user.name)
46
+ .gsub("{{name}}", user.presenter.name)
47
47
  .gsub("{{organization}}", organization_name(organization))
48
48
  .gsub("{{help_url}}", url_helpers.pages_url(host: organization.host))
49
49
  .gsub("{{badges_url}}", url_helpers.gamification_badges_url(host: organization.host))
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ class RemoveSearchIndexesJob < ApplicationJob
5
+ queue_as :default
6
+
7
+ def perform(elements)
8
+ elements.each do |element|
9
+ element.remove_from_index(element)
10
+ next unless element.respond_to?(:comments)
11
+
12
+ element.comments.each do |comment|
13
+ Decidim::RemoveSearchIndexesJob.perform_later([comment])
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -17,13 +17,30 @@ module Decidim
17
17
  # Note that this could be improved by adding a "type" column to the notifications table
18
18
  # This fix can generate lists of notifications that are below the SIZE_LIMIT
19
19
  @notifications = notifications[0...SIZE_LIMIT].filter_map do |notification|
20
+ # Check if is a notification that can be sent on email
20
21
  next unless notification.event_class_instance.respond_to?(:email_intro)
22
+ # checks if the resource exists, as we have implemented the possibility of soft deleting resources
23
+ next unless resource_is_present?(notification)
24
+ # checks if the resource is visible
25
+ next unless notification.can_participate?(@user)
26
+ # It usually checks if the resource is reportable and is not hidden, however, there are some exceptions
27
+ # like in the comments, where we check if the resource and intended comment is visible.
28
+ next if notification.hidden_resource?
29
+ # It usually checks if the resource is deletable and is not deleted, however, there are some exceptions
30
+ # like in the comments, where we check if the resource and intended comment is visible.
31
+ next if notification.deleted_resource?
21
32
 
22
33
  Decidim::NotificationToMailerPresenter.new(notification)
23
34
  end
24
35
 
25
- mail(to: user.email, subject: @notifications_digest.subject)
36
+ mail(to: user.email, subject: @notifications_digest.subject) if @notifications.any?
26
37
  end
27
38
  end
39
+
40
+ private
41
+
42
+ def resource_is_present?(notification)
43
+ notification.resource
44
+ end
28
45
  end
29
46
  end
@@ -97,11 +97,14 @@ module Decidim
97
97
  def resource_description; end
98
98
 
99
99
  def can_participate_in_space?(user)
100
+ return false unless published?
101
+ return false unless participatory_space.published?
100
102
  return true unless participatory_space.try(:private_space?)
101
103
  return false unless user
102
104
 
103
105
  participatory_space.can_participate?(user)
104
106
  end
107
+ alias can_participate? can_participate_in_space?
105
108
 
106
109
  def private_non_transparent_space?
107
110
  return false unless participatory_space.respond_to?(:private_space?)
@@ -7,6 +7,8 @@ module Decidim
7
7
  belongs_to :resource, foreign_key: "decidim_resource_id", foreign_type: "decidim_resource_type", polymorphic: true
8
8
  belongs_to :user, foreign_key: "decidim_user_id", class_name: "Decidim::User"
9
9
 
10
+ delegate :can_participate?, to: :resource
11
+
10
12
  # Daily notifications should contain all notifications within the previous
11
13
  # day from the given day.
12
14
  scope :daily, ->(time = Time.now.utc) { where(created_at: (time - 1.day).all_day) }
@@ -40,5 +42,13 @@ module Decidim
40
42
  def self.export_serializer
41
43
  Decidim::DownloadYourDataSerializers::DownloadYourDataNotificationSerializer
42
44
  end
45
+
46
+ def hidden_resource?
47
+ event_class_instance.respond_to?(:hidden_resource?) && event_class_instance.hidden_resource?
48
+ end
49
+
50
+ def deleted_resource?
51
+ event_class_instance.respond_to?(:deleted_resource?) && event_class_instance.deleted_resource?
52
+ end
43
53
  end
44
54
  end
@@ -12,6 +12,10 @@ module Decidim
12
12
 
13
13
  default_scope { order(created_at: :desc) }
14
14
 
15
+ before_create do |record|
16
+ record.uuid = SecureRandom.uuid
17
+ end
18
+
15
19
  def expired?
16
20
  expires_at < Time.zone.now
17
21
  end
@@ -130,6 +130,10 @@ module Decidim
130
130
  deleted_at.present?
131
131
  end
132
132
 
133
+ def can_participate?(_user)
134
+ true
135
+ end
136
+
133
137
  # Public: whether the user has been officialized or not
134
138
  def officialized?
135
139
  !officialized_at.nil?
@@ -20,7 +20,7 @@ module Decidim
20
20
  has_one :blocking, class_name: "Decidim::UserBlock", foreign_key: :id, primary_key: :block_id, dependent: :destroy
21
21
 
22
22
  # Regex for name & nickname format validations
23
- REGEXP_NAME = /\A(?!.*[<>?%&\^*#@()\[\]=+:;"{}\\|])/
23
+ REGEXP_NAME = /\A(?!.*[<>?%&\^*#@()\[\]=+:;"{}\\|\n\r])/m
24
24
  REGEXP_NICKNAME = /\A[a-z0-9_-]+\z/
25
25
 
26
26
  has_one_attached :avatar
@@ -28,7 +28,6 @@ export default Link.extend({
28
28
  ...this.parent?.(),
29
29
  allowTargetControl: false,
30
30
  HTMLAttributes: {
31
- target: "_blank",
32
31
  class: null
33
32
  }
34
33
  }
@@ -80,7 +80,11 @@ export default function createEditor(container) {
80
80
  const toolbar = createEditorToolbar(editor);
81
81
  container.insertBefore(toolbar, editorContainer);
82
82
 
83
- editor.on("update", () => (input.value = editor.getHTML()));
83
+ editor.on("update", () => {
84
+ input.value = editor.isEmpty
85
+ ? ""
86
+ : editor.getHTML();
87
+ });
84
88
 
85
89
  return editor;
86
90
  }
@@ -37,14 +37,14 @@ describe("Link", () => {
37
37
  await sleep(50);
38
38
 
39
39
  expect(editor.getHTML()).toEqual(
40
- '<p>Hello, <a target="_blank" href="https://decidim.org">world</a>!</p>'
40
+ '<p>Hello, <a href="https://decidim.org" target="_blank">world</a>!</p>'
41
41
  );
42
42
  });
43
43
 
44
44
  it("allows editing the link through the dialog", async () => {
45
45
  editorElement.focus();
46
46
  await updateContent(editorElement,
47
- '<p>Hello, <a target="_blank" href="https://decidim.org">world</a>!</p>'
47
+ '<p>Hello, <a href="https://decidim.org" target="_blank" >world</a>!</p>'
48
48
  );
49
49
 
50
50
  // Set the editor cursor inside the link
@@ -26,7 +26,7 @@ export default (ctx) => {
26
26
  await sleep(0);
27
27
 
28
28
  expect(ctx.prosemirror.innerHTML).toEqual(
29
- '<p>Hello, <a target="_blank" href="https://decidim.org">world</a>!</p>'
29
+ '<p>Hello, <a href="https://decidim.org" target="_blank">world</a>!</p>'
30
30
  );
31
31
  });
32
32
  });
@@ -67,7 +67,9 @@ export default class MapMarkersController extends MapController {
67
67
  }
68
68
 
69
69
  clearMarkers() {
70
- this.map.removeLayer(this.markerClusters);
70
+ if (this.markerClusters !== null) {
71
+ this.map.removeLayer(this.markerClusters);
72
+ }
71
73
  this.markerClusters = new L.MarkerClusterGroup();
72
74
  this.map.addLayer(this.markerClusters);
73
75
  }
@@ -4,6 +4,11 @@ footer {
4
4
 
5
5
  &__top {
6
6
  @apply hidden lg:flex flex-row gap-8 container py-10;
7
+
8
+ .prose a,
9
+ .prose strong {
10
+ @apply text-white;
11
+ }
7
12
  }
8
13
 
9
14
  &__down {
@@ -2,7 +2,11 @@
2
2
  @apply invisible opacity-0 fixed z-50 inset-0 bg-[rgba(0,0,0,0.25)] transition duration-300;
3
3
 
4
4
  & > * {
5
- @apply absolute inset-1/2 ltr:-translate-x-1/2 rtl:translate-x-1/2 -translate-y-1/2 w-[90%] lg:max-w-[900px] max-h-[95vh] h-fit overflow-y-auto p-6 bg-white z-50 rounded shadow-[0_4px_6px_rgba(211,211,211,0.25)];
5
+ @apply absolute inset-1/2 translate-x-[-50%] translate-y-[-50%] w-[90%] lg:max-w-[900px] max-h-[95vh] h-fit overflow-y-auto p-6 bg-white z-50 rounded shadow-[0_4px_6px_rgba(211,211,211,0.25)];
6
+
7
+ [dir="rtl"] & {
8
+ @apply translate-x-[50%];
9
+ }
6
10
 
7
11
  & > svg:only-child {
8
12
  @apply w-8 h-8 mx-auto text-gray-2 fill-current animate-spin;
@@ -8,7 +8,7 @@
8
8
  }
9
9
 
10
10
  &__code {
11
- @apply block max-h-24 overflow-auto border border-gray-3 bg-background p-2 break-all;
11
+ @apply block max-h-24 overflow-auto border border-gray-3 bg-background p-2 break-words whitespace-pre-wrap;
12
12
  }
13
13
 
14
14
  a {
@@ -10,6 +10,7 @@ module Decidim
10
10
  # overwrite `BasePresenter#user_presenter` to return your custom user presenter.
11
11
  # The only requirement for custom renderers is that they should respond to `present`.
12
12
  class UserPresenter
13
+ include Decidim::SanitizeHelper
13
14
  # Public: Initializes the presenter.
14
15
  #
15
16
  # user - An instance of Decidim::User
@@ -60,7 +61,7 @@ module Decidim
60
61
  #
61
62
  # Returns an HTML-safe String.
62
63
  def present_user_name
63
- extra["name"].html_safe
64
+ decidim_sanitize_translated(extra["name"]).html_safe
64
65
  end
65
66
 
66
67
  # Private: Presents the nickname of the user performing the action.
@@ -6,7 +6,12 @@ module Decidim
6
6
  #
7
7
  class UserPresenter < SimpleDelegator
8
8
  include ActionView::Helpers::UrlHelper
9
- include Decidim::TranslatableAttributes
9
+ include Decidim::SanitizeHelper
10
+
11
+ # name sanitized
12
+ def name
13
+ decidim_sanitize_translated(__getobj__.name)
14
+ end
10
15
 
11
16
  #
12
17
  # nickname presented in a twitter-like style
@@ -22,14 +22,21 @@ module Decidim
22
22
  end
23
23
 
24
24
  # i18n-tasks-use t("decidim.download_your_data.show.download_your_data")
25
- def export
26
- user_export = user.private_exports.build
25
+ def export(retries: 0)
26
+ data.rewind
27
+ user_export = user.private_exports.build(file_size: data.length)
27
28
  user_export.export_type = name
28
- user_export.file.attach(io: data, filename: "#{name}.zip", content_type: "application/zip")
29
+ user_export.content_type = "application/zip"
29
30
  user_export.expires_at = Decidim.download_your_data_expiry_time.from_now
30
31
  user_export.metadata = {}
31
32
  user_export.save!
32
- user_export
33
+ user_export.file.attach(io: data, filename: "#{name}.zip", content_type: "application/zip")
34
+
35
+ return user_export.reload if user_export.reload.file.attached?
36
+ return user_export.reload if retries >= 3
37
+
38
+ user_export.destroy!
39
+ export(retries: retries + 1)
33
40
  end
34
41
 
35
42
  private
@@ -37,6 +44,10 @@ module Decidim
37
44
  attr_reader :user, :export_format, :name
38
45
 
39
46
  def data
47
+ @data ||= load_data!
48
+ end
49
+
50
+ def load_data!
40
51
  user_data, user_attachments = data_and_attachments_for_user
41
52
  buffer = Zip::OutputStream.write_buffer do |out|
42
53
  save_user_data(out, user_data)
@@ -116,9 +116,10 @@ module Decidim
116
116
  end
117
117
 
118
118
  def data_for_participatory_space(export_manifest)
119
- collection = participatory_spaces.flat_map do |participatory_space|
119
+ collection = participatory_spaces.filter { |space| space.manifest.name == export_manifest.manifest.name }.flat_map do |participatory_space|
120
120
  export_manifest.collection.call(participatory_space)
121
121
  end
122
+
122
123
  serializer = export_manifest.open_data_serializer.nil? ? export_manifest.serializer : export_manifest.open_data_serializer
123
124
  exporter = Decidim::Exporters::CSV.new(collection, serializer)
124
125
  get_help_definition(:spaces, exporter, export_manifest) unless collection.empty?
@@ -2,7 +2,7 @@
2
2
  <td class="text-left p-4"><%= t(export.export_type, scope:"decidim.download_your_data.show") %></td>
3
3
  <td class="text-center"><%= export.expired? ? t("expired", scope:"decidim.download_your_data.show") : l(export.expires_at, format: :decidim_short) %></td>
4
4
  <td class="py-4">
5
- <%= button_to download_download_your_data_path(export),
5
+ <%= button_to download_download_your_data_path(uuid: export.uuid),
6
6
  class: "button button__sm button__transparent-secondary m-auto block",
7
7
  disabled: export.expired?,
8
8
  aria: { label: t("download", scope:"decidim.download_your_data.show") },
@@ -3,5 +3,5 @@
3
3
  </p>
4
4
 
5
5
  <p class="email-button email-button__cta">
6
- <%= link_to t(".download"), download_download_your_data_url(@private_export, host: @organization.host), class: "button expanded hollow button--sc" %>
6
+ <%= link_to t(".download"), download_download_your_data_url(uuid: @private_export.uuid, host: @organization.host), class: "button expanded hollow button--sc" %>
7
7
  </p>
@@ -3,5 +3,5 @@
3
3
  </p>
4
4
 
5
5
  <p class="email-button email-button__cta">
6
- <%= link_to t(".download"), download_download_your_data_url(@private_export, host: @organization.host), class: "button expanded hollow button--sc" %>
6
+ <%= link_to t(".download"), download_download_your_data_url(uuid: @private_export.uuid, host: @organization.host), class: "button expanded hollow button--sc" %>
7
7
  </p>
@@ -21,7 +21,7 @@
21
21
  <ul id="dropdown-menu-pages" class="vertical-tabs__list" role="menu">
22
22
  <% pages.each do |sibling| %>
23
23
  <li class="<%= "is-active" if page == sibling %>" role="menuitem">
24
- <%= link_to translated_attribute(sibling.title), page_path(sibling.slug), "aria-current": (page == sibling).to_s %>
24
+ <%= link_to translated_attribute(sibling.title), page_path(sibling.slug), "aria-current": ("page" if page == sibling) %>
25
25
  </li>
26
26
  <% end %>
27
27
  </ul>
@@ -9,6 +9,6 @@
9
9
  i18n_scope:,
10
10
  title: t("#{i18n_scope}.label"),
11
11
  role: :menuitem,
12
- class: "button button__sm button__text-secondary #{order_name == order ? "underline font-bold" : "font-normal"}" %>
12
+ class: "button button__sm button__text-secondary #{order_name == order ? "underline font-bold" : "font-normal"}", "aria-current": order_name == order ? "true" : "" %>
13
13
  <% end %>
14
14
  </div>
@@ -10,8 +10,9 @@
10
10
  </div>
11
11
 
12
12
  <div>
13
- <div class="main-bar__links-mobile__trigger" onclick="document.querySelector('#dropdown-trigger-links-mobile').click();document.querySelector('#dropdown-menu-account-mobile').removeAttribute('aria-modal')">
13
+ <div class="main-bar__links-mobile__trigger" tabindex="0" onclick="document.querySelector('#dropdown-trigger-links-mobile').click();document.querySelector('#dropdown-menu-account-mobile').removeAttribute('aria-modal')">
14
14
  <%= icon "close-line" %>
15
+ <p class="sr-only"><%= t("close", scope: "decidim.shared.flag_modal") %></p>
15
16
  </div>
16
17
  </div>
17
18
  </div>
@@ -37,3 +38,16 @@
37
38
  </ul>
38
39
  </div>
39
40
  </div>
41
+ <script type="text/javascript">
42
+ const closeDiv = document.querySelector('div.main-bar__links-mobile__trigger');
43
+ const menuDropdown = document.querySelector('#dropdown-menu-account-mobile');
44
+ const dropdownTrigger = document.querySelector('#dropdown-trigger-links-mobile');
45
+ // 32 is code for space bar and 13 is code for enter
46
+ closeDiv.addEventListener('keydown', function(e){
47
+ if (e.keyCode === 13 || e.keyCode === 32) {
48
+ menuDropdown.removeAttribute('aria-modal');
49
+ menuDropdown.setAttribute('aria-hidden', "true");
50
+ dropdownTrigger.focus();
51
+ }
52
+ })
53
+ </script>
@@ -1053,7 +1053,6 @@ ar:
1053
1053
  hello: مرحبا %{name}،
1054
1054
  intro:
1055
1055
  daily: 'هذه هي إخطارات اليوم الأخير بناءً على النشاط الذي تتابعه:'
1056
- real_time: 'هذا تنبيه عن النشاط الذي تتبعّه:'
1057
1056
  weekly: 'هذه هي إخطارات الأسبوع الأخير بناءً على النشاط الذي تتابعه:'
1058
1057
  outro: لقد تلقيت هذه الإشعارات لأنك تتبع هذا المحتوى أو مؤلفيه. يمكنك إلغاء متابعتها من صفحات كل منهم.
1059
1058
  see_more: عرض المزيد من الإشعارات
@@ -1102,15 +1101,12 @@ ar:
1102
1101
  proposals_explanation: تقديم مقترحات ، ودعم المقترحات الحالية وتعزيز التغييرات التي تريد رؤيتها.
1103
1102
  footer_sub_hero:
1104
1103
  footer_sub_hero_headline: مرحبًا بكم على المنصة التشاركية لـ %{organization}.
1105
- register: تسجيل
1106
1104
  hero:
1107
1105
  participate: مشاركة
1108
1106
  participate_title: المشاركة في مسارات المنصة
1109
1107
  welcome: مرحبًا بكم على %{organization}!
1110
1108
  statistics:
1111
1109
  headline: الحالة الحالية لـ %{organization}
1112
- sub_hero:
1113
- register: تسجيل
1114
1110
  index:
1115
1111
  standalone_pages: صفحات
1116
1112
  subheading: انتقل من خلال صفحات المساعدة من %{name}