decidim-core 0.30.0 → 0.30.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/activity/show.erb +6 -6
  3. data/app/cells/decidim/address/show.erb +3 -3
  4. data/app/cells/decidim/author/show.erb +1 -1
  5. data/app/cells/decidim/content_blocks/participatory_space_extra_data/extra_data.erb +2 -2
  6. data/app/cells/decidim/content_blocks/participatory_space_main_data/title.erb +11 -2
  7. data/app/cells/decidim/footer_topics/show.erb +2 -2
  8. data/app/cells/decidim/group_admins/show.erb +3 -1
  9. data/app/cells/decidim/group_members/show.erb +6 -2
  10. data/app/cells/decidim/images_panel/show.erb +5 -2
  11. data/app/cells/decidim/participatory_space_dropdown_metadata/metadata.erb +4 -4
  12. data/app/cells/decidim/report_button/flag_modal.erb +11 -9
  13. data/app/cells/decidim/report_user_button/flag_modal.erb +11 -10
  14. data/app/cells/decidim/upload_modal/files.erb +4 -4
  15. data/app/cells/decidim/upload_modal_cell.rb +5 -3
  16. data/app/commands/decidim/amendable/accept.rb +2 -1
  17. data/app/commands/decidim/create_omniauth_registration.rb +1 -1
  18. data/app/commands/decidim/create_report.rb +5 -1
  19. data/app/commands/decidim/invite_user.rb +1 -1
  20. data/app/controllers/concerns/decidim/participatory_space_context.rb +4 -1
  21. data/app/controllers/decidim/amendments_controller.rb +3 -3
  22. data/app/controllers/decidim/reports_controller.rb +6 -1
  23. data/app/forms/decidim/ephemeral_user_form.rb +1 -1
  24. data/app/forms/decidim/omniauth_registration_form.rb +1 -1
  25. data/app/forms/decidim/registration_form.rb +1 -1
  26. data/app/helpers/decidim/amendments_helper.rb +2 -1
  27. data/app/helpers/decidim/filters_helper.rb +25 -0
  28. data/app/helpers/decidim/layout_helper.rb +6 -0
  29. data/app/helpers/decidim/menu_helper.rb +2 -2
  30. data/app/helpers/decidim/paginate_helper.rb +1 -1
  31. data/app/helpers/decidim/tooltip_helper.rb +4 -1
  32. data/app/mailers/decidim/notifications_digest_mailer.rb +7 -1
  33. data/app/mailers/decidim/reported_mailer.rb +17 -2
  34. data/app/packs/images/decidim/default-avatar.svg +1 -1
  35. data/app/packs/src/decidim/callout.js +13 -8
  36. data/app/packs/src/decidim/confirm.js +15 -3
  37. data/app/packs/src/decidim/datepicker/generate_datepicker.js +2 -0
  38. data/app/packs/src/decidim/datepicker/generate_timepicker.js +2 -0
  39. data/app/packs/src/decidim/direct_uploads/upload_field.js +3 -4
  40. data/app/packs/src/decidim/direct_uploads/upload_modal.js +8 -9
  41. data/app/packs/src/decidim/dropdown_menu.js +18 -0
  42. data/app/packs/src/decidim/editor/common/suggestion.js +11 -1
  43. data/app/packs/src/decidim/index.js +1 -0
  44. data/app/packs/src/decidim/input_character_counter.js +1 -1
  45. data/app/packs/stylesheets/decidim/_activity.scss +4 -4
  46. data/app/packs/stylesheets/decidim/_cards.scss +4 -0
  47. data/app/packs/stylesheets/decidim/_filters.scss +1 -1
  48. data/app/packs/stylesheets/decidim/_header.scss +64 -37
  49. data/app/packs/stylesheets/decidim/_layout.scss +2 -2
  50. data/app/packs/stylesheets/decidim/_modal.scss +1 -5
  51. data/app/packs/stylesheets/decidim/_modal_update.scss +5 -1
  52. data/app/packs/stylesheets/decidim/resource_history.scss +14 -4
  53. data/app/permissions/decidim/default_permissions.rb +2 -0
  54. data/app/permissions/decidim/permissions.rb +13 -1
  55. data/app/presenters/decidim/notification_to_mailer_presenter.rb +7 -3
  56. data/app/queries/decidim/last_activity.rb +25 -0
  57. data/app/validators/translated_etiquette_validator.rb +2 -0
  58. data/app/views/decidim/errors/internal_server_error.html.erb +1 -1
  59. data/app/views/decidim/errors/not_found.html.erb +1 -1
  60. data/app/views/decidim/messaging/conversations/_reply_form.html.erb +1 -2
  61. data/app/views/decidim/messaging/conversations/_start.html.erb +1 -1
  62. data/app/views/decidim/newsletters/unsubscribe.html.erb +16 -4
  63. data/app/views/decidim/reported_mailer/hidden_manually.html.erb +25 -0
  64. data/app/views/decidim/searches/_filters.html.erb +48 -13
  65. data/app/views/decidim/shared/_component_announcement.html.erb +1 -1
  66. data/app/views/decidim/shared/_confirm_modal.html.erb +3 -5
  67. data/app/views/decidim/shared/_filters.html.erb +6 -4
  68. data/app/views/decidim/shared/_results_per_page.html.erb +1 -1
  69. data/app/views/kaminari/decidim/_page.html.erb +1 -1
  70. data/app/views/kaminari/decidim/_paginator.html.erb +1 -1
  71. data/app/views/layouts/decidim/_logo.html.erb +2 -2
  72. data/app/views/layouts/decidim/footer/_main.html.erb +1 -1
  73. data/app/views/layouts/decidim/footer/_main_intro.html.erb +1 -1
  74. data/app/views/layouts/decidim/footer/_mini.html.erb +2 -2
  75. data/app/views/layouts/decidim/header/_main.html.erb +2 -2
  76. data/app/views/layouts/decidim/header/_main_links_desktop.html.erb +6 -0
  77. data/app/views/layouts/decidim/header/_main_links_dropdown.html.erb +2 -0
  78. data/app/views/layouts/decidim/header/_main_links_mobile_account.html.erb +1 -1
  79. data/app/views/layouts/decidim/header/_menu_breadcrumb_main_dropdown_desktop.html.erb +5 -11
  80. data/app/views/layouts/decidim/header/_menu_breadcrumb_mobile_tablet.html.erb +5 -5
  81. data/app/views/layouts/decidim/header/_mobile_language_choose.html.erb +1 -1
  82. data/config/locales/ar.yml +0 -10
  83. data/config/locales/bg-BG.yml +2 -2
  84. data/config/locales/bg.yml +0 -12
  85. data/config/locales/ca-IT.yml +40 -6
  86. data/config/locales/ca.yml +40 -6
  87. data/config/locales/cs.yml +37 -17
  88. data/config/locales/de.yml +76 -29
  89. data/config/locales/el.yml +0 -11
  90. data/config/locales/en.yml +40 -6
  91. data/config/locales/es-MX.yml +41 -7
  92. data/config/locales/es-PY.yml +41 -7
  93. data/config/locales/es.yml +40 -6
  94. data/config/locales/eu.yml +82 -47
  95. data/config/locales/fi-plain.yml +65 -9
  96. data/config/locales/fi.yml +66 -10
  97. data/config/locales/fr-CA.yml +42 -13
  98. data/config/locales/fr-LU.yml +3 -3
  99. data/config/locales/fr.yml +42 -13
  100. data/config/locales/gl.yml +0 -8
  101. data/config/locales/hu.yml +0 -12
  102. data/config/locales/id-ID.yml +0 -8
  103. data/config/locales/is-IS.yml +0 -6
  104. data/config/locales/it.yml +165 -8
  105. data/config/locales/ja.yml +47 -14
  106. data/config/locales/lb-LU.yml +2 -2
  107. data/config/locales/lb.yml +0 -8
  108. data/config/locales/lt.yml +0 -12
  109. data/config/locales/lv.yml +0 -8
  110. data/config/locales/nl.yml +0 -8
  111. data/config/locales/no.yml +0 -8
  112. data/config/locales/pl.yml +0 -12
  113. data/config/locales/pt-BR.yml +0 -11
  114. data/config/locales/pt.yml +0 -8
  115. data/config/locales/ro-RO.yml +0 -18
  116. data/config/locales/ru.yml +0 -8
  117. data/config/locales/sk-SK.yml +3 -3
  118. data/config/locales/sk.yml +2 -8
  119. data/config/locales/sv.yml +63 -16
  120. data/config/locales/tr-TR.yml +0 -8
  121. data/config/locales/uk.yml +0 -7
  122. data/config/locales/zh-CN.yml +0 -8
  123. data/config/locales/zh-TW.yml +0 -12
  124. data/db/migrate/20171212103803_create_unique_nicknames.rb +1 -1
  125. data/db/migrate/20180221101934_fix_nickname_index.rb +1 -1
  126. data/db/migrate/20180706104107_add_nickname_to_managed_users.rb +1 -1
  127. data/db/migrate/20181001124950_move_users_groups_to_users_table.rb +1 -1
  128. data/db/migrate/20190412131728_fix_user_names.rb +1 -1
  129. data/lib/decidim/assets/tailwind/tailwind.config.js.erb +2 -1
  130. data/lib/decidim/core/test/factories.rb +14 -2
  131. data/lib/decidim/core/test/shared_examples/announcements_examples.rb +4 -0
  132. data/lib/decidim/core/test/shared_examples/comments_examples.rb +7 -5
  133. data/lib/decidim/core/test/shared_examples/map_examples.rb +2 -2
  134. data/lib/decidim/core/version.rb +1 -1
  135. data/lib/decidim/form_builder.rb +14 -0
  136. data/lib/decidim/nicknamizable.rb +6 -9
  137. data/lib/decidim/private_download_helper.rb +3 -3
  138. data/lib/tasks/upgrade/clean.rake +9 -1
  139. data/lib/tasks/upgrade/decidim_fix_nickname_uniqueness.rake +1 -1
  140. data/lib/tasks/upgrade/migrations.rake +2 -0
  141. metadata +9 -7
  142. /data/app/views/decidim/reported_mailer/{hide.html.erb → hidden_automatically.html.erb} +0 -0
@@ -10,13 +10,25 @@ const { Rails } = window;
10
10
  class ConfirmDialog {
11
11
  constructor(sourceElement) {
12
12
  this.$modal = $("#confirm-modal");
13
- if (sourceElement) {
14
- this.$source = $(sourceElement);
15
- }
16
13
  this.$content = $("[data-confirm-modal-content]", this.$modal);
14
+ this.$title = $("[data-dialog-title]", this.$modal);
17
15
  this.$buttonConfirm = $("[data-confirm-ok]", this.$modal);
18
16
  this.$buttonCancel = $("[data-confirm-cancel]", this.$modal);
19
17
 
18
+ if (sourceElement) {
19
+ this.$source = $(sourceElement);
20
+
21
+ const confirmTitle = this.$source.data("confirm-title");
22
+ const confirmButton = this.$source.data("confirm-button");
23
+
24
+ this.$title.text(confirmTitle)
25
+
26
+ const $buttonSpan = this.$buttonConfirm.find("span");
27
+ if ($buttonSpan.length > 0) {
28
+ $buttonSpan.text(confirmButton);
29
+ }
30
+ }
31
+
20
32
  window.Decidim.currentDialogs["confirm-modal"].open()
21
33
  }
22
34
 
@@ -13,11 +13,13 @@ export default function generateDatePicker(input, row, formats) {
13
13
  const date = document.createElement("input");
14
14
  date.setAttribute("id", `${input.id}_date`);
15
15
  date.setAttribute("type", "text");
16
+ date.setAttribute("aria-label", input.dataset.dateLabel);
16
17
 
17
18
  const calendar = document.createElement("button");
18
19
  calendar.innerHTML = icon("calendar-line");
19
20
  calendar.setAttribute("class", "datepicker__calendar-button");
20
21
  calendar.setAttribute("type", "button");
22
+ calendar.setAttribute("aria-label", input.dataset.buttonDateLabel);
21
23
 
22
24
  dateColumn.appendChild(date);
23
25
  dateColumn.appendChild(calendar);
@@ -13,11 +13,13 @@ export default function generateTimePicker(input, row, formats) {
13
13
  const time = document.createElement("input");
14
14
  time.setAttribute("id", `${input.id}_time`);
15
15
  time.setAttribute("type", "text");
16
+ time.setAttribute("aria-label", input.dataset.timeLabel);
16
17
 
17
18
  const clock = document.createElement("button");
18
19
  clock.innerHTML = icon("time-line")
19
20
  clock.setAttribute("class", "datepicker__clock-button");
20
21
  clock.setAttribute("type", "button");
22
+ clock.setAttribute("aria-label", input.dataset.buttonTimeLabel);
21
23
 
22
24
  timeColumn.appendChild(time);
23
25
  timeColumn.appendChild(clock);
@@ -1,5 +1,4 @@
1
1
  import UploadModal from "src/decidim/direct_uploads/upload_modal";
2
- import { truncateFilename } from "src/decidim/direct_uploads/upload_utility";
3
2
  import { escapeHtml, escapeQuotes } from "src/decidim/utilities/text";
4
3
 
5
4
  const updateModalTitle = (modal) => {
@@ -30,7 +29,7 @@ const updateActiveUploads = (modal) => {
30
29
  const [removeFiles, addFiles] = [modal.items.filter(({ removable }) => removable), modal.items.filter(({ removable }) => !removable)]
31
30
 
32
31
  addFiles.forEach((file, ix) => {
33
- let title = truncateFilename(file.name, 19)
32
+ let title = file.name
34
33
 
35
34
  let hidden = ""
36
35
  if (file.hiddenField) {
@@ -79,8 +78,8 @@ const updateActiveUploads = (modal) => {
79
78
 
80
79
  const template = `
81
80
  <div ${attachmentIdOrHiddenField} data-filename="${escapeQuotes(file.name)}" data-title="${escapeQuotes(title)}">
82
- ${(/image/).test(file.type) && `<div><img src="data:," alt="${escapeQuotes(file.name)}" /></div>` || ""}
83
- <span>${escapeHtml(title)} (${escapeHtml(truncateFilename(file.name))})</span>
81
+ ${(/image/).test(file.type) && "<div><img src=\"data:,\" role=\"presentation\" /></div>" || ""}
82
+ <span>${escapeHtml(title)}</span>
84
83
  ${hidden}
85
84
  </div>
86
85
  `
@@ -1,6 +1,5 @@
1
1
  import { Uploader } from "src/decidim/direct_uploads/uploader";
2
2
  import icon from "src/decidim/icon";
3
- import { truncateFilename } from "src/decidim/direct_uploads/upload_utility";
4
3
  import { escapeHtml, escapeQuotes } from "src/decidim/utilities/text";
5
4
 
6
5
  const STATUS = {
@@ -42,7 +41,7 @@ export default class UploadModal {
42
41
 
43
42
  this.emptyItems = this.modal.querySelector("[data-dropzone-no-items]");
44
43
  this.uploadItems = this.modal.querySelector("[data-dropzone-items]");
45
- this.input = this.dropZone.querySelector("input");
44
+ this.input = this.dropZone.querySelector("input[type=file]");
46
45
  this.items = []
47
46
 
48
47
  this.attachmentCounter = 0;
@@ -171,29 +170,29 @@ export default class UploadModal {
171
170
 
172
171
  createUploadItem(file, errors, opts = {}) {
173
172
  const okTemplate = `
174
- <img src="data:," alt="${escapeQuotes(file.name)}" />
175
- <span>${escapeHtml(truncateFilename(file.name))}</span>
173
+ <img src="data:,", role="presentation" />
174
+ <span class="upload-modal__span">${escapeHtml(file.name)}</span>
176
175
  `
177
176
 
178
177
  const errorTemplate = `
179
178
  <div>${icon("error-warning-line")}</div>
180
179
  <div>
181
- <span>${escapeHtml(truncateFilename(file.name))}</span>
180
+ <span class="upload-modal__span">${escapeHtml(file.name)}</span>
182
181
  <span>${this.locales.validation_error}</span>
183
182
  <ul>${errors.map((error) => `<li>${error}</li>`).join("\n")}</ul>
184
183
  </div>
185
184
  `
186
185
 
187
186
  const titleTemplate = `
188
- <img src="data:," alt="${escapeQuotes(file.name)}" />
187
+ <img src="data:," role="presentation" />
189
188
  <div>
190
189
  <div>
191
190
  <label>${this.locales.filename}</label>
192
- <span>${escapeHtml(truncateFilename(file.name))}</span>
191
+ <span class="upload-modal__span">${escapeHtml(file.name)}</span>
193
192
  </div>
194
193
  <div>
195
- <label>${this.locales.title}</label>
196
- <input class="sm" type="text" value="${escapeQuotes(opts.title || truncateFilename(file.name))}" />
194
+ <label for="${file.name}">${this.locales.title}</label>
195
+ <input class="sm" type="text" value="${escapeQuotes(opts.title || file.name)}" id="${file.name}" />
197
196
  </div>
198
197
  </div>
199
198
  `
@@ -0,0 +1,18 @@
1
+ // changes the value "menu" of role attribute set by a11y on div dropdown-menu-account and
2
+ // dropdown-menu-account-mobile which are inappropriate for accessibility
3
+ document.addEventListener("DOMContentLoaded", () => {
4
+ const dropdownDiv = document.querySelector("#dropdown-menu-account");
5
+ const dropdownMobileDiv = document.querySelector("#dropdown-menu-account-mobile");
6
+ if (dropdownDiv) {
7
+ setTimeout(() => {
8
+ dropdownDiv.setAttribute("role", "dialog")
9
+ dropdownMobileDiv.setAttribute("role", "dialog")
10
+ }, 300)
11
+ }
12
+ const triggerButtonMobile = document.querySelector("#dropdown-trigger-links-mobile");
13
+ if (triggerButtonMobile) {
14
+ triggerButtonMobile.addEventListener("click", () => {
15
+ dropdownMobileDiv.setAttribute("aria-modal", "true")
16
+ })
17
+ }
18
+ });
@@ -98,7 +98,17 @@ export const createSuggestionRenderer = (node, { itemConverter } = {}) => () =>
98
98
  suggestionItem.textContent = label;
99
99
  suggestion.append(suggestionItem);
100
100
 
101
- suggestionItem.addEventListener("click", () => selectItem(idx));
101
+ suggestionItem.addEventListener("click", (ev) => {
102
+ ev.preventDefault();
103
+
104
+ if (currentRange) {
105
+ // Increase the current range by 1 since the ENTER key would do the
106
+ // same when doing the selection through keyboard.
107
+ currentRange.to += 1;
108
+ }
109
+
110
+ selectItem(idx);
111
+ });
102
112
  });
103
113
  }
104
114
 
@@ -52,6 +52,7 @@ import "src/decidim/sw"
52
52
  import "src/decidim/sticky_header"
53
53
  import "src/decidim/sticky_footer"
54
54
  import "src/decidim/attachments"
55
+ import "src/decidim/dropdown_menu"
55
56
 
56
57
  // local deps that require initialization
57
58
  import ConfirmDialog, { initializeConfirm } from "src/decidim/confirm"
@@ -93,7 +93,7 @@ export default class InputCharacterCounter {
93
93
  this.$srTarget = $(`#${screenReaderId}`);
94
94
  if (!this.$srTarget.length) {
95
95
  this.$srTarget = $(
96
- `<span role="status" id="${screenReaderId}" class="sr-only remaining-character-count-sr" />`
96
+ `<span role="status" id="${screenReaderId}" class="sr-only remaining-character-count-sr" aria-hidden="true"/>`
97
97
  );
98
98
  this.$target.before(this.$srTarget);
99
99
  }
@@ -12,12 +12,12 @@
12
12
  &__content {
13
13
  @apply col-span-2 md:col-span-1 order-first md:order-none space-y-2;
14
14
 
15
- > span:first-child {
16
- span {
17
- @apply text-gray-2 text-sm;
15
+ > div:first-child {
16
+ p {
17
+ @apply text-gray-2 text-sm inline-block;
18
18
 
19
19
  svg {
20
- @apply text-gray fill-current;
20
+ @apply text-gray fill-current inline;
21
21
  }
22
22
  }
23
23
 
@@ -168,6 +168,10 @@
168
168
  svg {
169
169
  @apply flex-none text-gray fill-current;
170
170
  }
171
+
172
+ [data-author] {
173
+ @apply flex first:[&>*]:max-w-40 -mr-8;
174
+ }
171
175
  }
172
176
 
173
177
  /* shared styles */
@@ -89,7 +89,7 @@
89
89
  }
90
90
 
91
91
  span {
92
- @apply text-sm text-gray-2 truncate whitespace-nowrap first-letter:uppercase;
92
+ @apply text-sm text-gray-2 first-letter:uppercase;
93
93
  }
94
94
 
95
95
  [data-controls*="panel"] {
@@ -61,7 +61,7 @@ header {
61
61
  @apply hidden md:block col-span-2 col-start-5 xl:col-start-4;
62
62
 
63
63
  form {
64
- @apply block relative rounded text-md bg-background;
64
+ @apply block relative rounded text-md border border-neutral-200 outline outline-1 outline-transparent rounded bg-background-2 leading-relaxed;
65
65
  }
66
66
 
67
67
  input[type="text"] {
@@ -69,7 +69,7 @@ header {
69
69
  }
70
70
 
71
71
  button[type="submit"] {
72
- @apply absolute ltr:right-2 rtl:left-2 inset-y-2 text-secondary;
72
+ @apply absolute ltr:right-2 rtl:left-2 inset-y-0 text-secondary px-2;
73
73
  }
74
74
  }
75
75
 
@@ -334,10 +334,6 @@ header {
334
334
  &__dropdown-trigger {
335
335
  @apply flex items-center justify-between text-white;
336
336
 
337
- span {
338
- @apply flex flex-wrap md:flex-nowrap gap-x-2.5 overflow-hidden text-white;
339
- }
340
-
341
337
  svg {
342
338
  @apply w-6 h-6 fill-current;
343
339
  }
@@ -373,54 +369,85 @@ header {
373
369
  @apply absolute top-full left-0 bg-white rounded w-full bg-gray-5;
374
370
  }
375
371
 
376
- &__main-dropdown {
377
- @apply bg-white divide-y divide-gray-3 rounded-b shadow-lg text-black w-full lg:w-[1280px] h-screen md:h-auto;
372
+ &__dropdown-menu {
373
+ @apply w-full md:w-1/4 bg-primary px-4 md:px-8 pt-0 pb-3 md:py-3 divide-y divide-gray-3 text-white;
378
374
 
379
- &__bottom,
380
- &__top {
381
- @apply flex flex-col md:flex-row justify-between p-4 md:p-8 gap-x-8;
375
+ > * {
376
+ @apply py-3 md:py-3.5;
377
+ }
382
378
 
383
- &-right {
384
- @apply hidden md:block md:w-1/2;
379
+ a {
380
+ @apply flex items-center justify-start gap-1 font-semibold text-lg text-white;
381
+
382
+ span {
383
+ @apply min-w-0 truncate;
385
384
  }
386
- }
387
385
 
388
- &__bottom {
389
- @apply hidden md:flex;
386
+ svg {
387
+ @apply flex-none fill-current;
388
+ }
390
389
  }
390
+ }
391
391
 
392
- &__title {
393
- @apply hidden h4 md:flex md:h3;
394
- }
392
+ &__main-dropdown {
393
+ @apply bg-white flex flex-row rounded-b shadow-lg text-black w-full lg:w-[1280px] h-screen md:h-auto;
395
394
 
396
- &__subtitle {
397
- @apply hidden text-md md:flex md:text-lg text-gray-2 mt-5;
395
+ &__left {
396
+ @apply flex flex-col justify-between p-4 md:p-8 space-y-5 hidden md:block md:w-3/4;
397
+
398
+ &-top {
399
+ @apply border-b-4 border-gray-3 pb-3;
400
+ }
398
401
  }
399
402
 
400
- &__menu {
401
- @apply w-full md:w-1/2 mt-0 grid md:grid-cols-2 gap-x-6 text-secondary;
403
+ &__top {
404
+ @apply w-full px-4;
402
405
 
403
- > * {
404
- @apply py-3 md:py-3.5 border-b last:border-0 border-gray-3;
406
+ &-menu {
407
+ @apply w-full md:w-[100%] mt-0 grid md:grid-cols-2 gap-x-6 text-secondary;
408
+
409
+ > * {
410
+ @apply py-3 md:py-3.5 border-b last:border-0 border-gray-3;
405
411
 
406
- /* since the grid has 2 columns, remove the border for these last 2 columns */
407
- &:nth-last-child(-n + 2) {
408
- @apply md:border-0;
412
+ /* since the grid has 2 columns, remove the border for these last 2 columns */
413
+ &:nth-last-child(-n + 2) {
414
+ @apply md:border-0;
415
+ }
409
416
  }
410
- }
411
417
 
412
- a {
413
- @apply flex items-center justify-start gap-1 font-semibold text-lg text-secondary;
418
+ a {
419
+ @apply flex items-center justify-start gap-1 font-semibold text-lg text-secondary;
414
420
 
415
- span {
416
- @apply min-w-0 truncate;
417
- }
421
+ span {
422
+ @apply min-w-0 truncate;
423
+ }
418
424
 
419
- svg {
420
- @apply flex-none fill-current;
425
+ svg {
426
+ @apply flex-none fill-current;
427
+ }
421
428
  }
422
429
  }
423
430
  }
431
+
432
+ &__bottom {
433
+ @apply hidden md:flex;
434
+
435
+ &-right {
436
+ @apply mr-2 mb-2;
437
+ }
438
+
439
+ &-left {
440
+ @apply mr-2 mt-1;
441
+ }
442
+ }
443
+
444
+ &__title {
445
+ @apply hidden h4 md:flex md:h3;
446
+ }
447
+
448
+ &__subtitle {
449
+ @apply hidden text-md md:flex md:text-lg text-gray-2 mt-5;
450
+ }
424
451
  }
425
452
 
426
453
  &__secondary-dropdown {
@@ -459,7 +486,7 @@ header {
459
486
  }
460
487
 
461
488
  &__metadata {
462
- @apply flex items-center text-sm space-x-6 py-8;
489
+ @apply flex items-center text-sm space-x-6 pb-6;
463
490
 
464
491
  > span {
465
492
  @apply flex items-center space-x-2;
@@ -2,11 +2,11 @@
2
2
  @apply flex flex-col min-h-screen;
3
3
 
4
4
  &__skip {
5
- @apply absolute z-10 left-0 -translate-x-full py-1 px-4 bg-primary rounded-br-lg text-white cursor-pointer transition focus:translate-x-0;
5
+ @apply absolute z-[9999] left-0 -translate-x-full py-1 px-4 bg-primary rounded-br-lg text-white cursor-pointer transition focus:translate-x-0;
6
6
  }
7
7
 
8
8
  [data-content] {
9
- @apply relative flex flex-col;
9
+ @apply relative flex flex-col flex-1;
10
10
  }
11
11
  }
12
12
 
@@ -18,15 +18,11 @@
18
18
  }
19
19
 
20
20
  [data-dialog-container] {
21
- @apply grid grid-cols-[auto_1fr] items-start md:items-center gap-2 text-left;
21
+ @apply grid grid-rows-[auto_1fr] items-start md:items-center gap-2 text-left;
22
22
 
23
23
  > svg {
24
24
  @apply w-6 h-6 text-gray fill-current flex-none;
25
25
  }
26
-
27
- > :last-child {
28
- @apply col-span-2 md:col-span-1 md:col-start-2;
29
- }
30
26
  }
31
27
 
32
28
  [data-dialog-title] {
@@ -132,8 +132,12 @@
132
132
  }
133
133
 
134
134
  span {
135
- @apply text-sm text-gray-2 mx-auto;
135
+ @apply text-sm text-gray-2 mx-auto w-full break-all mb-2;
136
136
  }
137
137
  }
138
138
  }
139
+
140
+ &__span {
141
+ @apply w-full break-all mb-2;
142
+ }
139
143
  }
@@ -13,19 +13,29 @@
13
13
  }
14
14
 
15
15
  &_content {
16
- @apply flex flex-col lg:items-center lg:flex-row lg:gap-1;
16
+ @apply flex flex-col lg:items-start lg:flex-row lg:gap-2 lg:pt-[6px];
17
17
  }
18
18
 
19
19
  &_date {
20
- @apply text-sm text-gray-2;
20
+ @apply text-sm text-gray-2 lg:pt-px;
21
21
  }
22
22
  }
23
23
 
24
24
  &_text {
25
- @apply flex flex-col lg:flex-none lg:block relative;
25
+ @apply flex flex-col relative max-w-[250px] md:max-w-[600px] lg:max-w-[400px];
26
+ }
27
+
28
+ &_text:has(.resource-link) {
29
+ .resource-link {
30
+ @apply md:ml-[-1px] lg:ml-[-106px];
31
+ }
32
+ }
33
+
34
+ &_text:not(:has(.resource-link)) a {
35
+ @apply md:ml-[-1px] lg:ml-[-106px];
26
36
  }
27
37
 
28
38
  &__line {
29
- @apply absolute -left-[26px] lg:-left-[130px] top-3.5 lg:top-[26px] w-px h-full bg-secondary;
39
+ @apply absolute -left-[26px] lg:-left-[134px] top-3.5 lg:top-[26px] w-px h-full lg:h-[90%] bg-secondary;
30
40
  }
31
41
  }
@@ -5,6 +5,8 @@ module Decidim
5
5
  # actions by any kind of user. Also works as a default implementation so other
6
6
  # components can inherit from it and get some convenience methods.
7
7
  class DefaultPermissions
8
+ include Decidim::UserRoleChecker
9
+
8
10
  def initialize(user, permission_action, context = {})
9
11
  @user = user
10
12
  @permission_action = permission_action
@@ -106,7 +106,19 @@ module Decidim
106
106
  return allow! if component.current_settings.amendment_creation_enabled
107
107
  when :accept,
108
108
  :reject
109
- return allow! if component.current_settings.amendment_reaction_enabled
109
+ return disallow! unless component.current_settings.amendment_reaction_enabled
110
+
111
+ amendable = context.fetch(:amendable, nil)
112
+
113
+ if amendable.respond_to?(:official?) && amendable.official?
114
+ return allow! if user.admin?
115
+
116
+ return disallow!
117
+ end
118
+
119
+ return disallow! unless amendable.authored_by?(user)
120
+
121
+ return allow!
110
122
  when :promote
111
123
  return allow! if component.current_settings.amendment_promotion_enabled
112
124
  end
@@ -14,15 +14,19 @@ module Decidim
14
14
  delegate :url_helpers, to: "Decidim::Core::Engine.routes"
15
15
  delegate :resource_title, to: :event
16
16
  delegate :resource_url, to: :event
17
- delegate :email_intro, to: :event
18
17
  delegate :resource_path, to: :event
19
18
  delegate :safe_resource_text, to: :event
20
19
 
20
+ def email_intro
21
+ event.email_intro if event.respond_to?(:email_intro)
22
+ end
23
+
21
24
  def date_time
25
+ created_at_in_time_zone = created_at.in_time_zone(resource.organization.time_zone)
22
26
  if frequency == :daily
23
- created_at.strftime("%H:%M")
27
+ I18n.l(created_at_in_time_zone, format: :time_of_day)
24
28
  else
25
- I18n.l(created_at, format: :decidim_short)
29
+ I18n.l(created_at_in_time_zone, format: :decidim_short)
26
30
  end
27
31
  end
28
32
 
@@ -18,6 +18,7 @@ module Decidim
18
18
  query = filter_moderated(query)
19
19
  query = filter_spaces(query)
20
20
  query = filter_deleted(query)
21
+ query = filter_withdrawn(query)
21
22
  query.with_new_resource_type("all")
22
23
  end
23
24
  end
@@ -48,6 +49,30 @@ module Decidim
48
49
  ).where(decidim_moderations: { id: nil })
49
50
  end
50
51
 
52
+ def filter_withdrawn(query)
53
+ # Filter out the items that have been withdrawn.
54
+ conditions = []
55
+
56
+ ActionLog.public_resource_types.each do |resource_type|
57
+ klass = resource_type.constantize
58
+
59
+ condition = if klass.respond_to?(:not_withdrawn)
60
+ Arel.sql(
61
+ [
62
+ "decidim_action_logs.resource_type = '#{resource_type}'",
63
+ "decidim_action_logs.resource_id IN (#{Arel.sql(klass.not_withdrawn.select(:id).to_sql)})"
64
+ ].join(" AND ")
65
+ ).to_s
66
+ else
67
+ Arel.sql("decidim_action_logs.resource_type = '#{resource_type}'").to_s
68
+ end
69
+
70
+ conditions << "(#{condition})"
71
+ end
72
+
73
+ query.where(Arel.sql(conditions.join(" OR ")).to_s)
74
+ end
75
+
51
76
  def filter_spaces(query)
52
77
  conditions = []
53
78
 
@@ -6,6 +6,8 @@
6
6
  # validates :my_i18n_field, translated_etiquette: true
7
7
  class TranslatedEtiquetteValidator < EtiquetteValidator
8
8
  def validate_each(record, attribute, _value)
9
+ return unless Decidim.enable_etiquette_validator
10
+
9
11
  translated_attr = "#{attribute}_#{default_locale_for(record)}".gsub("-", "__")
10
12
  translated_value = record.send(translated_attr)
11
13
  return if translated_value.blank?
@@ -1,6 +1,6 @@
1
1
  <%= render layout: "layouts/decidim/shared/layout_center" do %>
2
2
 
3
- <div class="place-self-center">
3
+ <div class="flex flex-col items-center">
4
4
  <div class="flex justify-center">
5
5
  <h1 class="title-decorator my-12"><%= t("title", scope: "decidim.errors.internal_server_error") %></h1>
6
6
  </div>
@@ -1,6 +1,6 @@
1
1
  <%= render layout: "layouts/decidim/shared/layout_center" do %>
2
2
 
3
- <div class="place-self-center">
3
+ <div class="flex flex-col items-center">
4
4
  <div class="flex justify-center">
5
5
  <h1 class="title-decorator my-12"><%= t("title", scope: "decidim.errors.not_found") %></h1>
6
6
  </div>
@@ -1,8 +1,7 @@
1
1
  <div data-conversation-reply>
2
2
  <%= decidim_form_for form, url: decidim.conversation_path(conversation.id), method: :put, remote: true do |f| %>
3
3
  <div class="conversation__reply">
4
- <%= f.label :body, nil, class: "sr-only" %>
5
- <%= f.text_area :body, label: false, rows: 4, required: true, placeholder: t(".placeholder"), data: { "input-emoji": true } %>
4
+ <%= f.text_area :body, label: false, rows: 4, required: true, placeholder: t(".placeholder"), data: { "input-emoji": true }, aria: { label: t(".placeholder") } %>
6
5
  <%= f.submit "#{t(".send")} #{icon "arrow-right-line", class: "fill-current"}".html_safe, class: "button button__sm button__secondary self-end mt-4" %>
7
6
  </div>
8
7
  <% end %>
@@ -5,7 +5,7 @@
5
5
  <%= f.hidden_field :recipient_id, id: nil, name: "conversation[recipient_id][]", value: recipient.id %>
6
6
  <% end %>
7
7
  <%= f.label :body, nil, class: "sr-only" %>
8
- <%= f.text_area :body, label: false, rows: 4, required: true, placeholder: t(".placeholder"), data: { "input-emoji": true } %>
8
+ <%= f.text_area :body, label: false, rows: 4, required: true, placeholder: t(".placeholder"), data: { "input-emoji": true }, aria: { label: t(".placeholder") } %>
9
9
  <%= f.submit "#{t(".send")} #{icon "arrow-right-line", class: "fill-current"}".html_safe, class: "button button__sm button__secondary self-end mt-4" %>
10
10
  </div>
11
11
  <% end %>
@@ -1,4 +1,16 @@
1
- <div class="row">
2
- <h4><%== t ".unsubscribe" %></h4>
3
- <p><%== t ".check_subscription", link: decidim.notifications_settings_path %></p>
4
- </div>
1
+ <main class="layout-1col cols-6">
2
+ <div class="text-center py-12">
3
+ <h1 class="h1 decorator inline-block text-left"><%= t ".unsubscribe" %></h1>
4
+ </div>
5
+ <div class="page__container">
6
+ <div class="editor-content">
7
+ <div class="rich-text-display">
8
+ <%= t ".subscription_preferences", organization_name: translated_attribute(current_organization.name) %>
9
+ </div>
10
+ <br>
11
+ <div class="rich-text-display">
12
+ <%= t ".check_subscription_html", link: decidim.notifications_settings_path %>
13
+ </div>
14
+ </div>
15
+ </div>
16
+ </main>