decidim-core 0.28.3 → 0.28.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (132) 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/content_blocks/stats_cell.rb +1 -1
  7. data/app/cells/decidim/diff_cell.rb +4 -0
  8. data/app/cells/decidim/endorsement_buttons_cell.rb +1 -1
  9. data/app/cells/decidim/nav_links/show.erb +3 -3
  10. data/app/cells/decidim/newsletter_templates/image_text_cta_cell.rb +1 -1
  11. data/app/cells/decidim/resource_types_filter/show.erb +11 -12
  12. data/app/cells/decidim/translation_bar/show.erb +3 -3
  13. data/app/cells/decidim/translation_bar_cell.rb +1 -1
  14. data/app/commands/decidim/amendable/create_draft.rb +1 -0
  15. data/app/commands/decidim/destroy_account.rb +3 -0
  16. data/app/controllers/concerns/decidim/devise_authentication_methods.rb +1 -1
  17. data/app/controllers/concerns/decidim/direct_upload.rb +82 -0
  18. data/app/controllers/decidim/doorkeeper/credentials_controller.rb +1 -1
  19. data/app/controllers/decidim/links_controller.rb +1 -1
  20. data/app/controllers/decidim/profiles_controller.rb +4 -0
  21. data/app/forms/decidim/upload_validation_form.rb +1 -1
  22. data/app/helpers/concerns/decidim/user_role_checker.rb +46 -0
  23. data/app/helpers/decidim/cta_button_helper.rb +1 -1
  24. data/app/helpers/decidim/layout_helper.rb +28 -0
  25. data/app/helpers/decidim/map_helper.rb +6 -1
  26. data/app/helpers/decidim/menu_helper.rb +1 -1
  27. data/app/helpers/decidim/sanitize_helper.rb +11 -2
  28. data/app/helpers/decidim/scopes_helper.rb +5 -2
  29. data/app/models/decidim/action_log.rb +11 -1
  30. data/app/models/decidim/attachment.rb +1 -1
  31. data/app/packs/src/decidim/a11y.js +11 -15
  32. data/app/packs/src/decidim/append_redirect_url_to_modals.js +24 -14
  33. data/app/packs/src/decidim/direct_uploads/upload_field.js +21 -8
  34. data/app/packs/src/decidim/direct_uploads/upload_modal.js +3 -0
  35. data/app/packs/src/decidim/index.js +3 -0
  36. data/app/packs/src/decidim/remote_tooltips.js +38 -0
  37. data/app/packs/src/decidim/toggle.js +1 -1
  38. data/app/packs/src/decidim/tooltips.js +42 -22
  39. data/app/packs/stylesheets/decidim/_buttons.scss +1 -1
  40. data/app/packs/stylesheets/decidim/_modal_update.scss +4 -0
  41. data/app/packs/stylesheets/decidim/_profile.scss +1 -1
  42. data/app/packs/stylesheets/decidim/_progress-bar.scss +1 -1
  43. data/app/packs/stylesheets/decidim/legacy/conference-diploma.scss +2 -1
  44. data/app/presenters/decidim/attachment_presenter.rb +1 -1
  45. data/app/presenters/decidim/menu_item_presenter.rb +1 -1
  46. data/app/queries/decidim/last_activity.rb +16 -5
  47. data/app/services/decidim/base_diff_renderer.rb +26 -2
  48. data/app/services/decidim/email_notification_generator.rb +14 -5
  49. data/app/views/decidim/devise/omniauth_registrations/new.html.erb +1 -1
  50. data/app/views/decidim/offline/show.html.erb +1 -1
  51. data/app/views/decidim/pages/_tabbed.html.erb +5 -5
  52. data/app/views/decidim/shared/_filters.html.erb +5 -5
  53. data/app/views/decidim/shared/_orders.html.erb +3 -2
  54. data/app/views/decidim/shared/filters/_check_boxes_tree.html.erb +1 -1
  55. data/app/views/decidim/shared/filters/_collection.html.erb +1 -1
  56. data/app/views/layouts/decidim/_head.html.erb +1 -1
  57. data/app/views/layouts/decidim/header/_menu_breadcrumb_mobile_tablet.html.erb +1 -1
  58. data/app/views/layouts/decidim/shared/_layout_user_profile.html.erb +2 -2
  59. data/config/locales/ar.yml +12 -1
  60. data/config/locales/bg.yml +0 -1
  61. data/config/locales/bn-BD.yml +1 -0
  62. data/config/locales/bs-BA.yml +98 -0
  63. data/config/locales/ca.yml +14 -10
  64. data/config/locales/cs.yml +6 -1
  65. data/config/locales/de.yml +18 -14
  66. data/config/locales/el.yml +3 -1
  67. data/config/locales/en.yml +5 -1
  68. data/config/locales/es-MX.yml +6 -2
  69. data/config/locales/es-PY.yml +6 -2
  70. data/config/locales/es.yml +12 -8
  71. data/config/locales/eu.yml +202 -185
  72. data/config/locales/fi-plain.yml +5 -1
  73. data/config/locales/fi.yml +40 -36
  74. data/config/locales/fr-CA.yml +6 -2
  75. data/config/locales/fr.yml +5 -1
  76. data/config/locales/ga-IE.yml +5 -0
  77. data/config/locales/gl.yml +4 -1
  78. data/config/locales/hu.yml +1 -2
  79. data/config/locales/id-ID.yml +4 -0
  80. data/config/locales/is-IS.yml +4 -1
  81. data/config/locales/it.yml +40 -0
  82. data/config/locales/ja.yml +18 -16
  83. data/config/locales/lb.yml +5 -0
  84. data/config/locales/lt.yml +1 -2
  85. data/config/locales/lv.yml +4 -0
  86. data/config/locales/nl.yml +6 -1
  87. data/config/locales/no.yml +5 -0
  88. data/config/locales/pl.yml +1 -2
  89. data/config/locales/pt-BR.yml +199 -1
  90. data/config/locales/pt.yml +11 -0
  91. data/config/locales/ro-RO.yml +302 -180
  92. data/config/locales/ru.yml +4 -0
  93. data/config/locales/sk.yml +5 -1
  94. data/config/locales/sv.yml +452 -81
  95. data/config/locales/tr-TR.yml +6 -1
  96. data/config/locales/uk.yml +4 -1
  97. data/config/locales/zh-CN.yml +5 -0
  98. data/config/locales/zh-TW.yml +4 -1
  99. data/config/routes.rb +1 -0
  100. data/decidim-core.gemspec +4 -1
  101. data/lib/decidim/api/functions/component_list.rb +1 -1
  102. data/lib/decidim/api/functions/participatory_space_finder_base.rb +11 -1
  103. data/lib/decidim/api/interfaces/participatory_space_interface.rb +1 -1
  104. data/lib/decidim/api/types/component_type.rb +7 -0
  105. data/lib/decidim/api/types/user_group_type.rb +4 -0
  106. data/lib/decidim/api/types/user_type.rb +4 -0
  107. data/lib/decidim/attributes/rich_text.rb +38 -0
  108. data/lib/decidim/attributes/time_with_zone.rb +11 -1
  109. data/lib/decidim/attributes.rb +2 -0
  110. data/lib/decidim/content_parsers/blob_parser.rb +93 -0
  111. data/lib/decidim/content_parsers.rb +1 -0
  112. data/lib/decidim/content_renderers/blob_renderer.rb +90 -0
  113. data/lib/decidim/content_renderers.rb +1 -0
  114. data/lib/decidim/core/engine.rb +35 -1
  115. data/lib/decidim/core/test/factories.rb +28 -0
  116. data/lib/decidim/core/test/shared_examples/authorable_interface_examples.rb +1 -1
  117. data/lib/decidim/core/test/shared_examples/comments_examples.rb +25 -2
  118. data/lib/decidim/core/test/shared_examples/system_endorse_resource_examples.rb +112 -14
  119. data/lib/decidim/core/version.rb +1 -1
  120. data/lib/decidim/core.rb +11 -0
  121. data/lib/decidim/diffy_extension.rb +18 -0
  122. data/lib/decidim/form_builder.rb +1 -1
  123. data/lib/decidim/map/autocomplete.rb +1 -0
  124. data/lib/decidim/organization_settings.rb +4 -1
  125. data/lib/decidim/participatory_space_user.rb +4 -0
  126. data/lib/decidim/query_extensions.rb +0 -26
  127. data/lib/decidim/settings_manifest.rb +2 -0
  128. data/lib/decidim/translatable_attributes.rb +6 -1
  129. data/lib/decidim/view_model.rb +1 -1
  130. data/lib/tasks/upgrade/decidim_attachments.rake +14 -0
  131. data/lib/tasks/upgrade/decidim_fix_categorization.rake +34 -8
  132. metadata +30 -7
@@ -53,21 +53,31 @@ $(() => {
53
53
  }
54
54
 
55
55
  $(document).on("click.zf.trigger", (event) => {
56
- const target = `#${$(event.target).data("open")}`;
57
- const redirectUrl = $(event.target).data("redirectUrl");
58
-
59
- if (target && redirectUrl) {
60
- $("<input type='hidden' />").
61
- attr("id", "redirect_url").
62
- attr("name", "redirect_url").
63
- attr("value", redirectUrl).
64
- appendTo(`${target} form`);
65
-
66
- $(`${target} a`).attr("href", (index, href) => {
67
- const querystring = jQuery.param({"redirect_url": redirectUrl});
68
- return href + (href.match(/\?/) ? "&" : "?") + querystring;
69
- });
56
+ // Try to get the <a> directly or find the closest parent <a>
57
+ const $target = $(event.target).closest("a");
58
+
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) {
68
+ return;
70
69
  }
70
+
71
+ $("<input type='hidden' />").
72
+ attr("id", "redirect_url").
73
+ attr("name", "redirect_url").
74
+ attr("value", redirectUrl).
75
+ appendTo(`${dialogTarget} form`);
76
+
77
+ $(`${dialogTarget} a`).attr("href", (index, href) => {
78
+ const querystring = jQuery.param({"redirect_url": redirectUrl});
79
+ return href + (href.match(/\?/) ? "&" : "?") + querystring;
80
+ });
71
81
  });
72
82
 
73
83
  $(document).on("closed.zf.reveal", (event) => {
@@ -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
@@ -77,6 +77,9 @@ export default class UploadModal {
77
77
  uploader.upload.create((error, blob) => {
78
78
  if (error) {
79
79
  uploader.errors = [error]
80
+ this.uploadItems.replaceChild(this.createUploadItem(file, [error], { value: 100 }), item);
81
+ this.updateDropZone();
82
+
80
83
  } else {
81
84
  // attach the file hash to submit the form, when the file has been uploaded
82
85
  file.hiddenField = blob.signed_id
@@ -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
  }
@@ -67,7 +67,7 @@
67
67
  }
68
68
 
69
69
  &.is-hover,
70
- &:not([disabled]):not([class*="button__text"]):hover {
70
+ &:not([disabled]):not([class*="button__text"]):not(label):hover {
71
71
  @apply text-white;
72
72
 
73
73
  background: linear-gradient(0deg, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3)),
@@ -6,6 +6,10 @@
6
6
  @apply py-14;
7
7
  }
8
8
 
9
+ &.is-disabled label.button__secondary {
10
+ @apply text-gray-2 bg-background-3 border border-gray-2 cursor-not-allowed;
11
+ }
12
+
9
13
  &-container {
10
14
  @apply mt-4 md:mt-12 space-y-4;
11
15
  }
@@ -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
@@ -26,7 +26,7 @@ module Decidim
26
26
  delegate :content_tag, :safe_join, :link_to, :active_link_to_class, :is_active_link?, :icon, to: :@view
27
27
 
28
28
  def render
29
- content_tag :li, class: link_wrapper_classes do
29
+ content_tag :li, role: :menuitem, class: link_wrapper_classes do
30
30
  output = if url == "#"
31
31
  [content_tag(:span, composed_label, class: "sidebar-menu__item-disabled")]
32
32
  else
@@ -56,13 +56,24 @@ module Decidim
56
56
 
57
57
  condition = if klass.include?(Decidim::HasPrivateUsers)
58
58
  Arel.sql(
59
- [
60
- "decidim_action_logs.participatory_space_type = '#{manifest.model_class_name}'",
61
- "decidim_action_logs.participatory_space_id IN (#{Arel.sql(klass.visible_for(current_user).select(:id).to_sql)})"
62
- ].join(" AND ")
59
+ <<~SQL.squish
60
+ (
61
+ decidim_action_logs.participatory_space_type = '#{manifest.model_class_name}' AND#{" "}
62
+ decidim_action_logs.participatory_space_id IN (#{Arel.sql(klass.visible_for(current_user).select(:id).to_sql)})
63
+ ) OR#{" "}
64
+ (
65
+ decidim_action_logs.resource_type = '#{manifest.model_class_name}' AND#{" "}
66
+ decidim_action_logs.resource_id IN (#{Arel.sql(klass.visible_for(current_user).select(:id).to_sql)})
67
+ )
68
+ SQL
63
69
  ).to_s
64
70
  else
65
- Arel.sql("decidim_action_logs.participatory_space_type = '#{manifest.model_class_name}'").to_s
71
+ Arel.sql(
72
+ [
73
+ "decidim_action_logs.resource_type = '#{manifest.model_class_name}'",
74
+ "decidim_action_logs.participatory_space_type = '#{manifest.model_class_name}'"
75
+ ].join(" OR ")
76
+ ).to_s
66
77
  end
67
78
 
68
79
  conditions << "(#{condition})"
@@ -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
 
@@ -36,20 +36,29 @@ module Decidim
36
36
  return unless resource
37
37
  return unless event_class.types.include?(:email)
38
38
 
39
- followers.each do |recipient|
40
- next unless ["all", "followed-only"].include?(recipient.notification_types)
39
+ send_to_followers
40
+ send_to_affected_users
41
+ end
41
42
 
42
- send_email_to(recipient, user_role: :follower)
43
- end
43
+ private
44
44
 
45
+ def send_to_affected_users
45
46
  affected_users.each do |recipient|
46
47
  next unless ["all", "own-only"].include?(recipient.notification_types)
48
+ next if recipient.deleted? || recipient.blocked?
47
49
 
48
50
  send_email_to(recipient, user_role: :affected_user)
49
51
  end
50
52
  end
51
53
 
52
- private
54
+ def send_to_followers
55
+ followers.each do |recipient|
56
+ next unless ["all", "followed-only"].include?(recipient.notification_types)
57
+ next if recipient.deleted? || recipient.blocked?
58
+
59
+ send_email_to(recipient, user_role: :follower)
60
+ end
61
+ end
53
62
 
54
63
  attr_reader :event, :event_class, :resource, :followers, :affected_users, :extra
55
64
 
@@ -13,7 +13,7 @@
13
13
  <%= form_required_explanation %>
14
14
  </div>
15
15
 
16
- <%= decidim_form_for(@form, namespace: "registration", as: resource_name, url: omniauth_registrations_path(resource_name)) do |f| %>
16
+ <%= decidim_form_for(@form, namespace: "registration", as: resource_name, url: decidim.omniauth_registrations_path(resource_name)) do |f| %>
17
17
 
18
18
  <div class="form__wrapper">
19
19
  <%= f.text_field :name, help_text: t("decidim.devise.omniauth_registrations.new.username_help"), autocomplete: "name", placeholder: "John Doe" %>
@@ -1,6 +1,6 @@
1
1
  <% add_decidim_page_title(t("decidim.offline.name")) %>
2
2
 
3
- <main id="offline-fallback-html" class="text-center mt-8">
3
+ <main id="offline-fallback-html" class="text-center my-8">
4
4
  <div class="flex justify-center">
5
5
  <svg xmlns="http://www.w3.org/2000/svg" width="5rem" height="r5em" viewBox="0 0 25 25">
6
6
  <path
@@ -10,18 +10,18 @@
10
10
  </header>
11
11
 
12
12
  <div class="vertical-tabs">
13
- <nav>
14
- <button id="dropdown-trigger-pages" data-component="dropdown" data-target="dropdown-menu-pages" data-auto-close="true">
13
+ <nav role="navigation" aria-label="<%= I18n.t("layouts.decidim.navigation.aria_label", title: translated_attribute(page.title)) %>">
14
+ <button id="dropdown-trigger-pages" data-component="dropdown" data-target="dropdown-menu-pages" data-open-md="true" data-auto-close="true">
15
15
  <span>
16
16
  <%= translated_attribute(page.title) %>
17
17
  </span>
18
18
  <%= icon "arrow-down-s-line" %>
19
19
  <%= icon "arrow-up-s-line" %>
20
20
  </button>
21
- <ul id="dropdown-menu-pages" class="vertical-tabs__list" aria-hidden="true">
21
+ <ul id="dropdown-menu-pages" class="vertical-tabs__list" role="menu">
22
22
  <% pages.each do |sibling| %>
23
- <li class="<%= "is-active" if page == sibling %>">
24
- <%= link_to translated_attribute(sibling.title), page_path(sibling.slug) %>
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 %>
25
25
  </li>
26
26
  <% end %>
27
27
  </ul>
@@ -4,7 +4,7 @@
4
4
  <% if filter_sections.present? || local_assigns.has_key?(:search_variable) %>
5
5
  <%= filter_form_for filter, url_for, class: "new_filter self-stretch", data: { filters: "", component: "accordion" } do |form| %>
6
6
 
7
- <button id="dropdown-trigger-filters" data-component="dropdown" data-target="dropdown-menu-filters">
7
+ <button id="dropdown-trigger-filters" data-component="dropdown" data-target="dropdown-menu-filters" data-open-md="true">
8
8
  <%= icon "arrow-down-s-line" %>
9
9
  <%= icon "arrow-up-s-line" %>
10
10
  <span>
@@ -12,14 +12,14 @@
12
12
  </span>
13
13
  </button>
14
14
 
15
- <div id="dropdown-menu-filters" aria-hidden="true">
15
+ <div id="dropdown-menu-filters">
16
16
  <% if local_assigns.has_key?(:skip_to_id) %>
17
- <%= link_to t("skip", scope: "decidim.shared.filter_form_help"), "##{skip_to_id}", class: "filter-skip" %>
17
+ <%= link_to t("skip", scope: "decidim.shared.filter_form_help"), "##{skip_to_id}", class: "filter-skip", role: "menuitem" %>
18
18
  <% end %>
19
- <p class="filter-help"><%= t("help", scope: "decidim.shared.filter_form_help") %></p>
19
+ <p class="filter-help" role="menuitem" aria-disabled="true"><%= t("help", scope: "decidim.shared.filter_form_help") %></p>
20
20
 
21
21
  <% if local_assigns.has_key?(:search_variable) %>
22
- <div class="filter-search filter-container">
22
+ <div class="filter-search filter-container" role="menuitem">
23
23
  <%= form.search_field search_variable,
24
24
  label: false,
25
25
  placeholder: search_label,
@@ -1,13 +1,14 @@
1
- <button class="order-by__button" id="dropdown-trigger-order" data-component="dropdown" data-target="dropdown-menu-order" data-open="false">
1
+ <button class="order-by__button" id="dropdown-trigger-order" data-component="dropdown" data-target="dropdown-menu-order" data-open-md="true">
2
2
  <%= icon "arrow-down-s-line" %>
3
3
  <%= icon "arrow-up-s-line" %>
4
4
  <span><%= t("#{i18n_scope}.label") %></span>
5
5
  </button>
6
- <div id="dropdown-menu-order" class="order-by" aria-hidden="true">
6
+ <div id="dropdown-menu-order" class="order-by">
7
7
  <% orders.each do |order_name| %>
8
8
  <%= order_link order_name,
9
9
  i18n_scope:,
10
10
  title: t("#{i18n_scope}.label"),
11
+ role: :menuitem,
11
12
  class: "button button__sm button__text-secondary #{order_name == order ? "underline font-bold" : "font-normal"}" %>
12
13
  <% end %>
13
14
  </div>
@@ -1,4 +1,4 @@
1
- <div class="filter-container">
1
+ <div class="filter-container" role="menuitem">
2
2
  <button id="trigger-menu-<%= id %>" data-controls="panel-dropdown-menu-<%= id %>" data-open="false" data-open-md="true">
3
3
  <%= icon "arrow-down-s-line" %>
4
4
  <%= icon "arrow-up-s-line" %>
@@ -1,4 +1,4 @@
1
- <div class="filter-container">
1
+ <div class="filter-container" role="menuitem">
2
2
  <button id="trigger-menu-<%= id %>" data-controls="panel-dropdown-menu-<%= id %>" data-open="false" data-open-md="true">
3
3
  <%= icon "arrow-down-s-line" %>
4
4
  <%= icon "arrow-up-s-line" %>
@@ -2,7 +2,7 @@
2
2
 
3
3
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
4
4
  <% available_locales.each do |locale| %>
5
- <link rel="alternate" href="<%= url_for(request.parameters.merge(locale:)) %>" hreflang="<%= locale %>">
5
+ <link rel="alternate" href="<%= current_url(request.parameters.merge(locale:)) %>" hreflang="<%= locale %>">
6
6
  <% end %>
7
7
 
8
8
  <meta name="twitter:card" content="summary_large_image">
@@ -2,7 +2,7 @@
2
2
  <div class="menu-bar__breadcrumb-mobile__dropdown-trigger">
3
3
  <span>
4
4
  <% breadcrumb_items.last(2).each_with_index do |item, i| %>
5
- <% item_label = decidim_html_escape(translated_attribute(item[:label])) %>
5
+ <% item_label = decidim_escape_translated(item[:label]).html_safe %>
6
6
  <% if i.positive? %>
7
7
  <span>/</span>
8
8
  <% end %>
@@ -10,12 +10,12 @@
10
10
 
11
11
  <div class="vertical-tabs">
12
12
  <nav aria-label="menu-vertical">
13
- <button id="dropdown-trigger-profile" data-component="dropdown" data-target="dropdown-menu-profile">
13
+ <button id="dropdown-trigger-profile" data-open-md="true" data-component="dropdown" data-target="dropdown-menu-profile">
14
14
  <span><%= user_menu.active_item&.label || t("decidim.searches.filters.jump_to") %></span>
15
15
  <%= icon "arrow-down-s-line" %>
16
16
  <%= icon "arrow-up-s-line" %>
17
17
  </button>
18
- <ul id="dropdown-menu-profile" class="vertical-tabs__list" aria-hidden="true">
18
+ <ul id="dropdown-menu-profile" class="vertical-tabs__list">
19
19
  <%= user_menu.render %>
20
20
  </ul>
21
21
  </nav>