decidim-core 0.28.1 → 0.28.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/address/online.erb +2 -2
  3. data/app/cells/decidim/address_cell.rb +4 -0
  4. data/app/cells/decidim/announcement/show.erb +2 -2
  5. data/app/cells/decidim/author/show.erb +5 -5
  6. data/app/cells/decidim/card/show.erb +1 -1
  7. data/app/cells/decidim/card_g/show.erb +1 -1
  8. data/app/cells/decidim/card_g_cell.rb +5 -2
  9. data/app/cells/decidim/card_l/image.erb +2 -2
  10. data/app/cells/decidim/card_l_cell.rb +5 -2
  11. data/app/cells/decidim/card_metadata/show.erb +2 -2
  12. data/app/cells/decidim/content_blocks/hero_cell.rb +1 -1
  13. data/app/cells/decidim/content_blocks/highlighted_content_banner/show.erb +1 -1
  14. data/app/cells/decidim/content_blocks/participatory_space_hero_cell.rb +2 -2
  15. data/app/cells/decidim/data_consent/category.erb +1 -1
  16. data/app/cells/decidim/nav_links/show.erb +2 -2
  17. data/app/cells/decidim/notification/moderated.erb +12 -0
  18. data/app/cells/decidim/notification_cell.rb +5 -1
  19. data/app/cells/decidim/profile/details.erb +1 -1
  20. data/app/cells/decidim/progress_bar/show.erb +1 -1
  21. data/app/cells/decidim/progress_bar_cell.rb +2 -0
  22. data/app/cells/decidim/report_button/flag_modal.erb +5 -1
  23. data/app/cells/decidim/resource_types_filter/show.erb +3 -3
  24. data/app/cells/decidim/statistic/show.erb +2 -2
  25. data/app/cells/decidim/upload_modal/modal.erb +3 -4
  26. data/app/commands/decidim/create_omniauth_registration.rb +10 -4
  27. data/app/controllers/concerns/decidim/devise_controllers.rb +1 -0
  28. data/app/controllers/concerns/decidim/force_authentication.rb +1 -1
  29. data/app/controllers/concerns/decidim/paginable.rb +1 -1
  30. data/app/controllers/concerns/decidim/use_organization_time_zone.rb +1 -1
  31. data/app/controllers/decidim/application_controller.rb +1 -0
  32. data/app/controllers/decidim/gamification/badges_controller.rb +2 -0
  33. data/app/controllers/decidim/links_controller.rb +15 -2
  34. data/app/helpers/concerns/decidim/flash_helper_extensions.rb +2 -2
  35. data/app/helpers/decidim/check_boxes_tree_helper.rb +1 -2
  36. data/app/helpers/decidim/paginate_helper.rb +3 -5
  37. data/app/mailers/decidim/application_mailer.rb +40 -6
  38. data/app/models/decidim/attachment.rb +3 -3
  39. data/app/models/decidim/component.rb +4 -1
  40. data/app/models/decidim/content_block.rb +2 -2
  41. data/app/models/decidim/user.rb +12 -12
  42. data/app/packs/src/decidim/a11y.js +14 -0
  43. data/app/packs/src/decidim/abide_form_validator_fixer.js +44 -0
  44. data/app/packs/src/decidim/direct_uploads/upload_modal.js +2 -6
  45. data/app/packs/src/decidim/index.js +29 -1
  46. data/app/packs/src/decidim/input_character_counter.js +1 -1
  47. data/app/packs/stylesheets/decidim/_accordion.scss +2 -2
  48. data/app/packs/stylesheets/decidim/_cards.scss +2 -2
  49. data/app/packs/stylesheets/decidim/_dropdown.scss +9 -9
  50. data/app/packs/stylesheets/decidim/_forms.scss +4 -4
  51. data/app/packs/stylesheets/decidim/_layout.scss +3 -3
  52. data/app/packs/stylesheets/decidim/_modal_update.scss +1 -3
  53. data/app/packs/stylesheets/decidim/_tooltip.scss +10 -10
  54. data/app/packs/stylesheets/decidim/editor.scss +1 -1
  55. data/app/presenters/decidim/admin_log/organization_presenter.rb +1 -1
  56. data/app/presenters/decidim/log/resource_presenter.rb +7 -1
  57. data/app/services/decidim/download_your_data_exporter.rb +36 -25
  58. data/app/services/decidim/log/diff_changeset_calculator.rb +1 -1
  59. data/app/services/decidim/open_data_exporter.rb +8 -7
  60. data/app/views/decidim/account/show.html.erb +2 -2
  61. data/app/views/decidim/application/_document.html.erb +2 -2
  62. data/app/views/decidim/endorsements/update_buttons_and_counters.js.erb +2 -1
  63. data/app/views/decidim/gamification/badges/index.html.erb +34 -33
  64. data/app/views/decidim/links/_modal.html.erb +1 -1
  65. data/app/views/decidim/links/new.html.erb +3 -1
  66. data/app/views/decidim/manifests/show.json.erb +5 -5
  67. data/app/views/decidim/messaging/conversations/create.js.erb +1 -1
  68. data/app/views/decidim/notifications_settings/show.html.erb +6 -6
  69. data/app/views/decidim/pages/_tabbed.html.erb +2 -2
  70. data/app/views/decidim/searches/_filters.html.erb +2 -2
  71. data/app/views/decidim/shared/_filters.html.erb +2 -2
  72. data/app/views/decidim/shared/_orders.html.erb +2 -2
  73. data/app/views/decidim/shared/filters/_collection.html.erb +5 -3
  74. data/app/views/decidim/shared/filters/_dropdown_label.html.erb +21 -19
  75. data/app/views/layouts/decidim/_logo.html.erb +1 -1
  76. data/app/views/layouts/decidim/_wrapper.html.erb +1 -1
  77. data/app/views/layouts/decidim/footer/_main_intro.html.erb +1 -1
  78. data/app/views/layouts/decidim/footer/_main_links.html.erb +3 -1
  79. data/app/views/layouts/decidim/header/_main_links_desktop.html.erb +5 -3
  80. data/app/views/layouts/decidim/header/_main_links_mobile_account.html.erb +1 -1
  81. data/app/views/layouts/decidim/header/_menu_breadcrumb_items.html.erb +2 -0
  82. data/app/views/layouts/decidim/shared/_layout_user_profile.html.erb +2 -2
  83. data/config/locales/ar.yml +1 -5
  84. data/config/locales/bg.yml +878 -1
  85. data/config/locales/ca.yml +5 -3
  86. data/config/locales/cs.yml +3 -1
  87. data/config/locales/de.yml +3 -1
  88. data/config/locales/el.yml +8 -1
  89. data/config/locales/en.yml +3 -1
  90. data/config/locales/es-MX.yml +8 -6
  91. data/config/locales/es-PY.yml +8 -6
  92. data/config/locales/es.yml +32 -30
  93. data/config/locales/eu.yml +4 -2
  94. data/config/locales/fi-plain.yml +6 -4
  95. data/config/locales/fi.yml +32 -30
  96. data/config/locales/fr-CA.yml +2 -0
  97. data/config/locales/fr.yml +2 -0
  98. data/config/locales/ga-IE.yml +8 -0
  99. data/config/locales/gl.yml +1 -0
  100. data/config/locales/hu.yml +1 -2
  101. data/config/locales/is-IS.yml +3 -0
  102. data/config/locales/it.yml +7 -1
  103. data/config/locales/ja.yml +3 -1
  104. data/config/locales/kaa.yml +5 -0
  105. data/config/locales/lb.yml +7 -1
  106. data/config/locales/lt.yml +8 -2
  107. data/config/locales/lv.yml +8 -1
  108. data/config/locales/nl.yml +7 -1
  109. data/config/locales/no.yml +7 -1
  110. data/config/locales/pl.yml +35 -0
  111. data/config/locales/pt-BR.yml +0 -1
  112. data/config/locales/pt.yml +7 -1
  113. data/config/locales/ro-RO.yml +8 -0
  114. data/config/locales/ru.yml +8 -0
  115. data/config/locales/sk.yml +8 -1
  116. data/config/locales/sl.yml +8 -0
  117. data/config/locales/sv.yml +132 -88
  118. data/config/locales/tr-TR.yml +21 -4
  119. data/config/locales/uk.yml +10 -0
  120. data/config/locales/zh-CN.yml +0 -1
  121. data/config/locales/zh-TW.yml +8 -1
  122. data/db/migrate/20181025082245_add_timestamps_to_components.rb +5 -1
  123. data/decidim-core.gemspec +0 -1
  124. data/lib/decidim/asset_router/storage.rb +214 -11
  125. data/lib/decidim/core/engine.rb +8 -0
  126. data/lib/decidim/core/seeds.rb +1 -1
  127. data/lib/decidim/core/test/shared_examples/attachable_interface_examples.rb +1 -1
  128. data/lib/decidim/core/test/shared_examples/comments_examples.rb +76 -6
  129. data/lib/decidim/core/test/shared_examples/follows_examples.rb +8 -3
  130. data/lib/decidim/core/test/shared_examples/logo_email.rb +2 -2
  131. data/lib/decidim/core/test/shared_examples/paginated_resource_examples.rb +5 -5
  132. data/lib/decidim/core/version.rb +1 -1
  133. data/lib/decidim/core.rb +6 -1
  134. data/lib/decidim/events/base_event.rb +4 -0
  135. data/lib/decidim/organization_settings.rb +10 -2
  136. data/lib/decidim/seven_zip_wrapper.rb +29 -0
  137. data/lib/tasks/upgrade/decidim_fix_categorization.rake +101 -1
  138. metadata +13 -25
  139. data/app/services/decidim/zip_stream/writer.rb +0 -39
@@ -278,6 +278,18 @@ module Decidim
278
278
  false
279
279
  end
280
280
 
281
+ def after_confirmation
282
+ return unless organization.send_welcome_notification?
283
+
284
+ Decidim::EventsManager.publish(
285
+ event: "decidim.events.core.welcome_notification",
286
+ event_class: WelcomeNotificationEvent,
287
+ resource: self,
288
+ affected_users: [self],
289
+ extra: { force_email: true }
290
+ )
291
+ end
292
+
281
293
  protected
282
294
 
283
295
  # Overrides devise email required validation.
@@ -296,18 +308,6 @@ module Decidim
296
308
  super
297
309
  end
298
310
 
299
- def after_confirmation
300
- return unless organization.send_welcome_notification?
301
-
302
- Decidim::EventsManager.publish(
303
- event: "decidim.events.core.welcome_notification",
304
- event_class: WelcomeNotificationEvent,
305
- resource: self,
306
- affected_users: [self],
307
- extra: { force_email: true }
308
- )
309
- end
310
-
311
311
  private
312
312
 
313
313
  # Changes default Devise behaviour to use ActiveJob to send async emails.
@@ -104,6 +104,20 @@ const createDropdown = (component) => {
104
104
  });
105
105
  }
106
106
 
107
+ // Disable focus on children elements so we can pass the AXE accessibility tests
108
+ const dropdownMenu = document.getElementById(dropdownOptions.dropdown);
109
+ if (dropdownMenu.getAttribute("aria-hidden") === "true") {
110
+ dropdownMenu.
111
+ querySelectorAll("a, input, button").
112
+ forEach((element) => { element.tabIndex = -1 })
113
+ }
114
+
115
+ component.addEventListener("click", () => {
116
+ dropdownMenu.
117
+ querySelectorAll("a, input, button").
118
+ forEach((element) => { element.tabIndex = 0 })
119
+ })
120
+
107
121
  Dropdowns.render(component.id, dropdownOptions);
108
122
  }
109
123
 
@@ -0,0 +1,44 @@
1
+ /**
2
+ * This script modifies the behavior of Abide form validation to address the issue of form validation errors
3
+ * appearing prematurely in input fields.
4
+ *
5
+ * The primary goal is to hide error messages until the input field loses focus.
6
+ */
7
+
8
+ class AbideFormValidatorFixer {
9
+ initialize() {
10
+ const forms = document.querySelectorAll("main [data-live-validate='true']");
11
+
12
+ forms.forEach((form) => {
13
+ if (this.isElementVisible(form)) {
14
+ this.setupForm(form);
15
+ }
16
+ });
17
+ }
18
+
19
+ isElementVisible(element) {
20
+ return element.offsetParent !== null && getComputedStyle(element).display !== "none";
21
+ }
22
+
23
+ setupForm(form) {
24
+ const inputs = form.querySelectorAll("input");
25
+
26
+ inputs.forEach((input) => {
27
+ const errorElement = input.closest("label")?.querySelector(".form-error") || input.parentElement.querySelector(".form-error");
28
+ if (!errorElement) {
29
+ return;
30
+ }
31
+ form.removeAttribute("data-live-validate");
32
+ input.addEventListener("input", this.hideErrorElement.bind(this, errorElement));
33
+ });
34
+ }
35
+
36
+ hideErrorElement(errorElement) {
37
+ errorElement.classList.remove("is-visible");
38
+ }
39
+ }
40
+
41
+ document.addEventListener("DOMContentLoaded", () => {
42
+ const validatorFixer = new AbideFormValidatorFixer();
43
+ validatorFixer.initialize();
44
+ });
@@ -154,20 +154,16 @@ export default class UploadModal {
154
154
  // Disabled save button when any children have data-state="error"
155
155
  this.saveButton.disabled = Array.from(files).filter(({ dataset: { state } }) => state === STATUS.ERROR).length > 0;
156
156
 
157
- const dataSelectFileButton = this.emptyItems.querySelector("[data-select-file-button]");
158
-
159
157
  // Only allow to continue the upload when the multiple option is true (default: false)
160
158
  const continueUpload = !files.length || this.options.multiple
161
159
  this.input.disabled = !continueUpload
162
160
  if (continueUpload) {
163
161
  this.emptyItems.classList.remove("is-disabled");
164
- dataSelectFileButton.removeAttribute("disabled");
162
+ this.emptyItems.querySelector("label").removeAttribute("disabled");
165
163
  } else {
166
164
  this.emptyItems.classList.add("is-disabled");
167
- dataSelectFileButton.disabled = true;
165
+ this.emptyItems.querySelector("label").disabled = true;
168
166
  }
169
-
170
- dataSelectFileButton.addEventListener("click", () => this.input.click());
171
167
  }
172
168
 
173
169
  createUploadItem(file, errors, opts = {}) {
@@ -50,6 +50,7 @@ import "src/decidim/impersonation"
50
50
  import "src/decidim/gallery"
51
51
  import "src/decidim/direct_uploads/upload_field"
52
52
  import "src/decidim/data_consent"
53
+ import "src/decidim/abide_form_validator_fixer"
53
54
  import "src/decidim/sw"
54
55
 
55
56
  // local deps that require initialization
@@ -90,6 +91,33 @@ window.Decidim = window.Decidim || {
90
91
 
91
92
  window.morphdom = morphdom
92
93
 
94
+ // REDESIGN_PENDING: deprecated
95
+ window.initFoundation = (element) => {
96
+ $(element).foundation();
97
+
98
+ // Fix compatibility issue with the `a11y-accordion-component` package that
99
+ // uses the `data-open` attribute to indicate the open state for the accordion
100
+ // trigger.
101
+ //
102
+ // In Foundation, these listeners are initiated on the document node always,
103
+ // regardless of the element for which foundation is initiated. Therefore, we
104
+ // need the document node here instead of the `element` passed to this
105
+ // function.
106
+ const $document = $(document);
107
+
108
+ $document.off("click.zf.trigger", window.Foundation.Triggers.Listeners.Basic.openListener);
109
+ $document.on("click.zf.trigger", "[data-open]", (ev, ...restArgs) => {
110
+ // Do not apply for the accordion triggers.
111
+ const accordion = ev.currentTarget?.closest("[data-component='accordion']");
112
+ if (accordion) {
113
+ return;
114
+ }
115
+
116
+ // Otherwise call the original implementation
117
+ Reflect.apply(window.Foundation.Triggers.Listeners.Basic.openListener, ev.currentTarget, [ev, ...restArgs]);
118
+ });
119
+ };
120
+
93
121
  Rails.start()
94
122
 
95
123
  /**
@@ -104,7 +132,7 @@ const initializer = (element = document) => {
104
132
  window.focusGuard = window.focusGuard || new FocusGuard(document.body);
105
133
 
106
134
  // REDESIGN_PENDING: deprecated
107
- $(element).foundation();
135
+ window.initFoundation(element);
108
136
 
109
137
  svg4everybody();
110
138
 
@@ -74,7 +74,7 @@ export default class InputCharacterCounter {
74
74
 
75
75
  // If input is a hidden for WYSIWYG editor add it at the end
76
76
  if (this.$input.parent().is(".editor")) {
77
- this.$input.parent().after(this.$target);
77
+ this.$input.parent().append(container);
78
78
  } else {
79
79
  const wrapper = document.createElement("span")
80
80
  wrapper.className = "input-character-counter"
@@ -4,10 +4,10 @@
4
4
 
5
5
  [data-component="accordion"]
6
6
  [id*="comment"][class="comment-reply"][aria-hidden="true"] {
7
- display: block;
7
+ display: none;
8
8
  }
9
9
 
10
10
  [data-component="accordion"]
11
11
  [id*="comment"][class="comment-reply"][aria-hidden="false"] {
12
- display: none;
12
+ display: block;
13
13
  }
@@ -134,7 +134,7 @@
134
134
  &-month,
135
135
  &-day,
136
136
  &-year {
137
- @apply inline-flex items-center justify-evenly empty:[&>span]:hidden;
137
+ @apply inline-flex items-center justify-evenly empty:[&>div]:hidden;
138
138
  }
139
139
  }
140
140
 
@@ -143,7 +143,7 @@
143
143
  &__list-metadata {
144
144
  @apply mt-auto inline-flex flex-wrap gap-x-4 md:gap-0;
145
145
 
146
- & > span {
146
+ & > div {
147
147
  @apply inline-flex items-center gap-1 px-0 md:px-6 border-gray-3 border-0 md:border-r first:pl-0 last:pr-0 last:border-r-0 text-sm text-gray-2;
148
148
 
149
149
  & > svg {
@@ -36,6 +36,15 @@
36
36
  .dropdown {
37
37
  @apply absolute border-2 border-gray-3 rounded min-w-max p-4 drop-shadow-md text-left z-10;
38
38
 
39
+ /*
40
+ NOTE: the calculated value is the sum of the arrow offset position plus the half of the arrow size:
41
+ - offset position: 20%
42
+ - arrow size: 1.5rem
43
+ */
44
+ --arrow-offset: 20%;
45
+ --arrow-size: 1.5rem;
46
+ --arrow-visible-size: var(--arrow-size) * 0.5;
47
+
39
48
  & > * {
40
49
  @apply relative z-10 p-3.5 first:pt-1.5 last:pb-1.5;
41
50
  }
@@ -54,15 +63,6 @@
54
63
  }
55
64
  }
56
65
 
57
- /*
58
- NOTE: the calculated value is the sum of the arrow offset position plus the half of the arrow size:
59
- - offset position: 20%
60
- - arrow size: 1.5rem
61
- */
62
- --arrow-offset: 20%;
63
- --arrow-size: 1.5rem;
64
- --arrow-visible-size: var(--arrow-size) * 0.5;
65
-
66
66
  &__bottom {
67
67
  @apply top-full right-0 mt-3 translate-x-[calc(var(--arrow-offset)-var(--arrow-visible-size))] before:content-[''] before:absolute before:right-[var(--arrow-offset)] before:-top-2 before:w-[var(--arrow-size)] before:h-[var(--arrow-size)] before:rotate-45 before:bg-white before:rounded before:border-2 before:border-gray-3 after:content-[''] after:absolute after:left-0 after:top-0 after:w-full after:h-full after:bg-white;
68
68
  }
@@ -45,16 +45,16 @@
45
45
  }
46
46
 
47
47
  select {
48
- &:not(.reset-defaults) {
49
- @apply pr-8;
50
- }
51
-
52
48
  @apply appearance-none;
53
49
 
54
50
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 12 8'%3e%3cpath fill='%233E4C5C' d='M5.99962 4.97656L10.1246 0.851562L11.303 2.0299L5.99962 7.33323L0.696289 2.0299L1.87462 0.851562L5.99962 4.97656Z'/%3e%3c/svg%3e");
55
51
  background-position: right 1rem center;
56
52
  background-repeat: no-repeat;
57
53
  background-size: 0.75rem;
54
+
55
+ &:not(.reset-defaults) {
56
+ @apply pr-8;
57
+ }
58
58
  }
59
59
  }
60
60
 
@@ -6,7 +6,7 @@
6
6
  }
7
7
 
8
8
  [data-content] {
9
- @apply min-h-[60vh] relative flex flex-col;
9
+ @apply relative flex flex-col;
10
10
  }
11
11
  }
12
12
 
@@ -31,14 +31,14 @@
31
31
  }
32
32
 
33
33
  .layout-2col {
34
- @apply md:grid grid-cols-12 container grow min-h-[60vh];
34
+ @apply md:grid grid-cols-12 container grow auto-rows-max;
35
35
 
36
36
  &__aside {
37
37
  @apply col-span-4 lg:col-span-3 md:pr-16 py-6 md:py-12 gap-6 md:gap-12 flex flex-col justify-between items-start md:justify-start before:content-[''] before:absolute before:top-0 before:left-0 before:h-full before:w-1/2 before:-z-10 md:before:bg-background;
38
38
  }
39
39
 
40
40
  &__main {
41
- @apply col-span-8 lg:col-span-9 bg-white md:pl-16 py-6 md:py-12;
41
+ @apply col-span-8 lg:col-span-9 bg-white md:pl-16 py-6 md:py-12 min-h-[60vh];
42
42
  }
43
43
 
44
44
  &__reverse &__aside {
@@ -28,9 +28,7 @@
28
28
  @apply w-24 flex-none flex justify-center;
29
29
  }
30
30
 
31
- img:not([src^="data:image"]):not(
32
- [src^="data:application/octet-stream"]
33
- ) {
31
+ img[src="data:,"] {
34
32
  @apply hidden;
35
33
  }
36
34
 
@@ -1,6 +1,16 @@
1
1
  [role="tooltip"] {
2
2
  @apply absolute bg-black z-10 px-4 py-2 w-max max-w-xs rounded text-left text-white;
3
3
 
4
+ /*
5
+ NOTE: the calculated value is the sum of the arrow offset position plus the half of the arrow size:
6
+ - offset position: 20%
7
+ - arrow size: 16px
8
+ */
9
+ --arrow-offset: 20%;
10
+ --arrow-size: 16px;
11
+ --arrow-visible-size: var(--arrow-size) * 0.5;
12
+ --arrow-margin: var(--arrow-visible-size) * 1.4142135623730951; // due to the rotation, the margin is SQRT2 times the visible size
13
+
4
14
  & > * {
5
15
  @apply relative z-20;
6
16
  }
@@ -41,16 +51,6 @@
41
51
  }
42
52
  }
43
53
 
44
- /*
45
- NOTE: the calculated value is the sum of the arrow offset position plus the half of the arrow size:
46
- - offset position: 20%
47
- - arrow size: 16px
48
- */
49
- --arrow-offset: 20%;
50
- --arrow-size: 16px;
51
- --arrow-visible-size: var(--arrow-size) * 0.5;
52
- --arrow-margin: var(--arrow-visible-size) * 1.4142135623730951; // due to the rotation, the margin is SQRT2 times the visible size
53
-
54
54
  &.top {
55
55
  @apply -translate-x-[calc(100%-var(--arrow-offset))] -translate-y-[calc(100%+var(--arrow-margin))] before:content-[''] before:absolute before:-z-10 before:right-[calc(var(--arrow-offset)-var(--arrow-visible-size))] before:-bottom-[var(--arrow-visible-size)] before:w-[var(--arrow-size)] before:h-[var(--arrow-size)] before:rotate-45 before:bg-black before:rounded-br;
56
56
  }
@@ -21,7 +21,7 @@
21
21
  }
22
22
 
23
23
  .editor-container {
24
- @apply editor-props editor-suggestions-props flex flex-col mb-6 border editor-border;
24
+ @apply editor-props editor-suggestions-props flex flex-col mt-4 border editor-border;
25
25
 
26
26
  &.editor-disabled {
27
27
  .editor-input .ProseMirror {
@@ -26,7 +26,7 @@ module Decidim
26
26
 
27
27
  def settings_attributes_mapping
28
28
  {
29
- name: :string,
29
+ name: :i18n,
30
30
  default_locale: :locale,
31
31
  reference_prefix: :string,
32
32
  twitter_handler: :string,
@@ -10,6 +10,8 @@ module Decidim
10
10
  # overwrite `BasePresenter#resource_presenter` to return your custom resource presenter.
11
11
  # The only requirement for custom renderers is that they should respond to `present`.
12
12
  class ResourcePresenter
13
+ include Decidim::SanitizeHelper
14
+
13
15
  # Public: Initializes the presenter.
14
16
  #
15
17
  # resource - An instance of a model that can be located by
@@ -65,7 +67,11 @@ module Decidim
65
67
  #
66
68
  # Returns an HTML-safe String.
67
69
  def present_resource_name
68
- h.translated_attribute extra["title"]
70
+ if resource.present? && resource.respond_to?(:presenter) && resource.presenter.respond_to?(:title)
71
+ resource.presenter.title(html_escape: true)
72
+ else
73
+ decidim_escape_translated(extra["title"]).html_safe
74
+ end
69
75
  end
70
76
  end
71
77
  end
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "seven_zip_ruby"
4
- require "zip"
5
- require_relative "zip_stream/writer"
3
+ require "decidim/seven_zip_wrapper"
6
4
 
7
5
  module Decidim
8
6
  # Public: Generates a 7z(seven zip) file with data files ready to be persisted
@@ -10,8 +8,6 @@ module Decidim
10
8
  #
11
9
  # In fact, the 7z file wraps a ZIP file which finally contains the data files.
12
10
  class DownloadYourDataExporter
13
- include ::Decidim::ZipStream::Writer
14
-
15
11
  DEFAULT_EXPORT_FORMAT = "CSV"
16
12
  ZIP_FILE_NAME = "download-your-data.zip"
17
13
 
@@ -29,36 +25,25 @@ module Decidim
29
25
  end
30
26
 
31
27
  def export
32
- dirname = File.dirname(@path)
33
- FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
34
- File.open(@path, "wb") do |file|
35
- SevenZipRuby::Writer.open(file, password: @password) do |szw|
36
- szw.header_encryption = true
37
- szw.add_data(data, ZIP_FILE_NAME)
38
- end
39
- end
28
+ tmpdir = Dir.mktmpdir("temporary-download-your-data-dir")
29
+ user_data, user_attachments = data_and_attachments_for_user
30
+ save_user_data(tmpdir, user_data)
31
+ save_user_attachments(tmpdir, user_attachments)
32
+
33
+ SevenZipWrapper.compress_and_encrypt(filename: @path, password: @password, input_directory: tmpdir)
40
34
  end
41
35
 
42
36
  private
43
37
 
44
- def data
45
- buffer = Zip::OutputStream.write_buffer do |out|
46
- user_data, attachments = data_for(@user, @export_format)
38
+ attr_reader :user, :export_format
47
39
 
48
- add_user_data_to_zip_stream(out, user_data)
49
- add_attachments_to_zip_stream(out, attachments)
50
- end
51
-
52
- buffer.string
53
- end
54
-
55
- def data_for(user, format)
40
+ def data_and_attachments_for_user
56
41
  export_data = []
57
42
  export_attachments = []
58
43
 
59
44
  download_your_data_entities.each do |object|
60
45
  klass = Object.const_get(object)
61
- export_data << [klass.model_name.name.parameterize.pluralize, Exporters.find_exporter(format).new(klass.user_collection(user), klass.export_serializer).export]
46
+ export_data << [klass.model_name.name.parameterize.pluralize, Exporters.find_exporter(export_format).new(klass.user_collection(user), klass.export_serializer).export]
62
47
  attachments = klass.download_your_data_images(user)
63
48
  export_attachments << [klass.model_name.name.parameterize.pluralize, attachments.flatten] unless attachments.nil?
64
49
  end
@@ -69,5 +54,31 @@ module Decidim
69
54
  def download_your_data_entities
70
55
  @download_your_data_entities ||= DownloadYourDataSerializers.data_entities
71
56
  end
57
+
58
+ def save_user_data(tmpdir, user_data)
59
+ user_data.each do |entity, exporter_data|
60
+ next if exporter_data.read == "\n"
61
+
62
+ file_name = File.join(tmpdir, "#{entity}-#{exporter_data.filename}")
63
+ File.write(file_name, exporter_data.read)
64
+ end
65
+ end
66
+
67
+ def save_user_attachments(tmpdir, user_attachments)
68
+ user_attachments.each do |entity, attachment_block|
69
+ attachment_block.each do |attachment|
70
+ next unless attachment.attached?
71
+
72
+ blobs = attachment.is_a?(ActiveStorage::Attached::One) ? [attachment.blob] : attachment.blobs
73
+ blobs.each do |blob|
74
+ Dir.mkdir(File.join(tmpdir, entity.parameterize))
75
+ file_name = File.join(tmpdir, entity.parameterize, blob.filename.to_s)
76
+ blob.open do |blob_file|
77
+ File.write(file_name, blob_file.read.force_encoding("UTF-8"))
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
72
83
  end
73
84
  end
@@ -86,7 +86,7 @@ module Decidim
86
86
  locales.flat_map do |locale|
87
87
  previous_value = values.first.try(:[], locale)
88
88
  new_value = values.last.try(:[], locale)
89
- if previous_value == new_value
89
+ if previous_value == new_value || (previous_value.nil? && new_value.blank?)
90
90
  nil
91
91
  else
92
92
  label = generate_label(attribute, locale)
@@ -51,13 +51,14 @@ module Decidim
51
51
  headers.push(*exporter.headers)
52
52
  exported = exporter.export
53
53
 
54
- tmpfile = Tempfile.new("#{export_manifest.name}-#{component.id}-")
55
- tmpfile.write(exported.read)
56
- # Do not delete the file when the reference is deleted
57
- ObjectSpace.undefine_finalizer(tmpfile)
58
- tmpfile.close
59
-
60
- collection.push(tmpfile.path)
54
+ tmpdir = Dir::Tmpname.create(export_manifest.name.to_s) do
55
+ # just get an empty file name
56
+ end
57
+ filename = File.join(tmpdir, "#{component.id}.csv")
58
+ Dir.mkdir(tmpdir)
59
+ File.write(filename, exported.read)
60
+
61
+ collection.push(filename)
61
62
  end
62
63
  end
63
64
  end
@@ -5,7 +5,7 @@
5
5
  <% if current_user.unconfirmed_email.present? %>
6
6
  <%# NOTE: announcement cell clean_body method will purge the button %>
7
7
  <div id="email-change-pending" class="flash secondary" data-announcement>
8
- <span class="flash__message flex-none block leading-relaxed">
8
+ <div class="flash__message flex-none block leading-relaxed">
9
9
  <p>
10
10
  <strong><%= t("decidim.account.email_change.title") %></strong>
11
11
  </p>
@@ -18,7 +18,7 @@
18
18
  resend_link: link_to(t("decidim.account.email_change.send_again"), resend_confirmation_instructions_account_path, role: :button, method: :post, remote: true),
19
19
  cancel_link: link_to(t("decidim.account.email_change.cancel"), cancel_email_change_account_path, role: :button, method: :post, remote: true)) %>
20
20
  </p>
21
- </span>
21
+ </div>
22
22
  </div>
23
23
  <% end %>
24
24
 
@@ -7,8 +7,8 @@
7
7
  <div class="card__list-text"><%= decidim_escape_translated(document.description) %></div>
8
8
  <% end %>
9
9
  <div class="card__list-metadata">
10
- <span><%= icon "file-text-line" %><%= document.file_type %></span>
11
- <span><%= icon "scales-2-line" %><%= number_to_human_size(document.file_size) %></span>
10
+ <div><%= icon "file-text-line" %><%= document.file_type %></div>
11
+ <div><%= icon "scales-2-line" %><%= number_to_human_size(document.file_size) %></div>
12
12
  </div>
13
13
  </div>
14
14
  <%= link_to document.url,
@@ -1,5 +1,4 @@
1
1
  updateEndorsementBlock();
2
-
3
2
  function updateEndorsementBlock() {
4
3
  var $endorsementBlock = $('#resource-<%= resource.id %>-endorsement-block');
5
4
  var $endorsementListTrigger = $('#resource-<%= resource.id %>-endorsement-block #dropdown-trigger');
@@ -24,4 +23,6 @@ function updateEndorsementBlock() {
24
23
  } else {
25
24
  $endorsementIdentitiesButton[0].innerHTML = '<%= j(cell("decidim/endorsement_buttons", resource).button_content).strip.html_safe %>';
26
25
  }
26
+ document.dispatchEvent(new CustomEvent("ajax:loaded", { detail: $endorsementBlock[0] }));
27
+ document.dispatchEvent(new CustomEvent("ajax:loaded", { detail: $endorsementListGrid[0] }));
27
28
  }
@@ -1,40 +1,41 @@
1
- <div class="wrapper">
2
- <div class="row column mb-m">
3
- <h1><%= t ".title" %></h1>
4
- <p>
5
- <%= t ".page_description" %>
1
+ <% add_decidim_meta_tags(
2
+ title: t(".title"),
3
+ description: t(".page_description")
4
+ ) %>
5
+
6
+ <main class="layout-1col cols-10">
7
+
8
+ <header class="text-center py-10">
9
+ <h1 class="title-decorator inline-block text-left mb-12">
10
+ <%= t ".title" %>
11
+ </h1>
12
+ <p class="text-lg text-gray-2">
13
+ <%= t ".page_description" %>
6
14
  </p>
7
- </div>
15
+ </header>
16
+
8
17
  <% @badges.each do |badge| %>
9
- <div class="row column mb-m">
10
- <div class="column medium-2">
11
- <div class="card card--badge absolutes">
12
- <div class="card__content">
13
- <div class="card__header">
14
- <div class="badge-container">
15
- <%= image_tag badge.image %>
16
- </div>
17
- </div>
18
- <div class="card__text text-center">
19
- <strong><%= badge.translated_name %></strong>
20
- </div>
18
+ <div class="mb-12">
19
+ <h2 class="h4 mb-4"><%= t ".badge_title", name: badge.translated_name %></h2>
20
+ <div class="grid grid-cols-10 text-gray-2 leading-relaxed">
21
+ <div class="col-span-2">
22
+ <div class="flex justify-center items-center">
23
+ <%= image_tag badge.image, class: "w-32", alt: badge.translated_name %>
24
+ </div>
25
+ <div class="text-center mt-4">
26
+ <strong><%= badge.translated_name %></strong>
21
27
  </div>
22
28
  </div>
23
- </div>
24
- <div class="column medium-10">
25
- <h6>
26
- <strong><%= t ".badge_title", name: badge.translated_name %></strong>
27
- </h6>
28
- <p><%= badge.description(current_organization.name) %></p>
29
- <h6>
30
- <strong><%= t ".how" %></strong>
31
- </h6>
32
- <ol>
33
- <% badge.conditions.each do |condition| %>
34
- <li><%= condition %></li>
35
- <% end %>
36
- </ol>
29
+ <div class="col-span-7 ml-2">
30
+ <p><%= badge.description(current_organization.name) %></p>
31
+ <p class="mt-4"><strong><%= t ".how" %></strong></p>
32
+ <ol class="list-decimal">
33
+ <% badge.conditions.each do |condition| %>
34
+ <li><%= condition %></li>
35
+ <% end %>
36
+ </ol>
37
+ </div>
37
38
  </div>
38
39
  </div>
39
40
  <% end %>
40
- </div>
41
+ </main>
@@ -16,6 +16,6 @@
16
16
  <button class="button button__lg button__transparent-secondary" data-dialog-close="external-domain-warning">
17
17
  <%= t("decidim.links.warning.cancel") %>
18
18
  </button>
19
- <%= link_to t("decidim.links.warning.proceed"), external_url.to_s , target: "_blank", class: "button button__lg button__secondary" %>
19
+ <%= link_to t("decidim.links.warning.proceed"), external_url.to_s , target: "_blank", class: "button button__lg button__secondary", rel: "nofollow noopener noreferrer" %>
20
20
  </div>
21
21
  <% end %>