decidim-core 0.27.1 → 0.27.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of decidim-core might be problematic. Click here for more details.

Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/newsletter_templates/base_cell.rb +8 -0
  3. data/app/cells/decidim/newsletter_templates/basic_only_text/show.erb +4 -4
  4. data/app/cells/decidim/newsletter_templates/image_text_cta/show.erb +4 -4
  5. data/app/cells/decidim/upload_modal/files.erb +1 -0
  6. data/app/cells/decidim/upload_modal_cell.rb +26 -11
  7. data/app/commands/decidim/attachment_methods.rb +20 -2
  8. data/app/commands/decidim/create_registration.rb +1 -0
  9. data/app/commands/decidim/gallery_methods.rb +1 -1
  10. data/app/commands/decidim/unendorse_resource.rb +1 -1
  11. data/app/commands/decidim/update_account.rb +1 -0
  12. data/app/commands/decidim/update_password.rb +2 -0
  13. data/app/controllers/decidim/devise/sessions_controller.rb +18 -2
  14. data/app/controllers/decidim/groups_controller.rb +5 -0
  15. data/app/controllers/decidim/links_controller.rb +10 -11
  16. data/app/controllers/decidim/profiles_controller.rb +1 -1
  17. data/app/helpers/decidim/cells_helper.rb +1 -0
  18. data/app/helpers/decidim/external_domain_helper.rb +14 -3
  19. data/app/helpers/decidim/icon_helper.rb +3 -3
  20. data/app/helpers/decidim/newsletters_helper.rb +1 -0
  21. data/app/helpers/decidim/sanitize_helper.rb +3 -2
  22. data/app/mailers/decidim/newsletter_mailer.rb +10 -3
  23. data/app/models/decidim/newsletter.rb +28 -0
  24. data/app/models/decidim/scope_type.rb +24 -0
  25. data/app/models/decidim/user.rb +0 -2
  26. data/app/models/decidim/user_base_entity.rb +2 -0
  27. data/app/models/decidim/user_block.rb +2 -2
  28. data/app/models/decidim/user_group.rb +1 -1
  29. data/app/packs/src/decidim/direct_uploads/upload_modal.js +0 -1
  30. data/app/packs/src/decidim/editor/clipboard_override.js +6 -2
  31. data/app/packs/src/decidim/editor.js +63 -33
  32. data/app/packs/src/decidim/form_filter.component.test.js +148 -5
  33. data/app/packs/src/decidim/form_filter.js +26 -4
  34. data/app/packs/stylesheets/decidim/email.scss +7 -0
  35. data/app/packs/stylesheets/decidim/modules/_buttons.scss +10 -6
  36. data/app/packs/stylesheets/decidim/modules/_cards.scss +1 -1
  37. data/app/packs/stylesheets/decidim/modules/_comments.scss +24 -0
  38. data/app/packs/stylesheets/decidim/modules/_input-gallery.scss +2 -1
  39. data/app/packs/stylesheets/decidim/modules/_upload_modal.scss +0 -4
  40. data/app/packs/stylesheets/decidim/vizzs/_linechart.scss +2 -2
  41. data/app/packs/stylesheets/decidim/vizzs/_rowchart.scss +2 -2
  42. data/app/presenters/decidim/admin_log/user_group_presenter.rb +1 -1
  43. data/app/presenters/decidim/admin_log/user_moderation_presenter.rb +1 -1
  44. data/app/presenters/decidim/notification_presenter.rb +1 -1
  45. data/app/presenters/decidim/notification_to_mailer_presenter.rb +1 -0
  46. data/app/presenters/decidim/push_notification_presenter.rb +1 -1
  47. data/app/presenters/decidim/user_group_presenter.rb +1 -1
  48. data/app/presenters/decidim/user_presenter.rb +1 -1
  49. data/app/scrubbers/decidim/admin_input_scrubber.rb +3 -1
  50. data/app/scrubbers/decidim/user_input_scrubber.rb +30 -1
  51. data/app/services/decidim/traceability.rb +1 -0
  52. data/app/uploaders/decidim/application_uploader.rb +1 -1
  53. data/app/uploaders/decidim/avatar_uploader.rb +2 -2
  54. data/app/validators/uploader_image_dimensions_validator.rb +22 -2
  55. data/app/views/decidim/links/_invalid_url_modal.html.erb +17 -0
  56. data/app/views/decidim/links/_modal.html.erb +1 -1
  57. data/app/views/decidim/links/invalid_url.js.erb +24 -0
  58. data/app/views/decidim/links/new.html.erb +1 -1
  59. data/app/views/decidim/messaging/conversations/_conversation.html.erb +2 -6
  60. data/app/views/decidim/newsletter_mailer/newsletter.html.erb +3 -3
  61. data/app/views/decidim/newsletters/show.html.erb +1 -1
  62. data/app/views/layouts/decidim/_mailer_logo.html.erb +2 -2
  63. data/app/views/layouts/decidim/newsletter_base.html.erb +2 -2
  64. data/config/locales/ar.yml +571 -7
  65. data/config/locales/bg.yml +6 -8
  66. data/config/locales/ca.yml +38 -30
  67. data/config/locales/cs.yml +28 -33
  68. data/config/locales/da.yml +4 -0
  69. data/config/locales/de.yml +4 -25
  70. data/config/locales/el.yml +6 -9
  71. data/config/locales/en.yml +22 -15
  72. data/config/locales/eo.yml +2 -1
  73. data/config/locales/es-MX.yml +30 -22
  74. data/config/locales/es-PY.yml +30 -22
  75. data/config/locales/es.yml +36 -28
  76. data/config/locales/et.yml +4 -0
  77. data/config/locales/eu.yml +173 -80
  78. data/config/locales/fa-IR.yml +1 -0
  79. data/config/locales/fi-plain.yml +7 -20
  80. data/config/locales/fi.yml +26 -18
  81. data/config/locales/fr-CA.yml +27 -19
  82. data/config/locales/fr.yml +25 -17
  83. data/config/locales/ga-IE.yml +1 -0
  84. data/config/locales/gl.yml +2 -25
  85. data/config/locales/gn-PY.yml +4 -0
  86. data/config/locales/hr.yml +4 -0
  87. data/config/locales/hu.yml +68 -28
  88. data/config/locales/id-ID.yml +7 -8
  89. data/config/locales/is-IS.yml +2 -2
  90. data/config/locales/it.yml +2 -10
  91. data/config/locales/ja.yml +30 -37
  92. data/config/locales/ka-GE.yml +5 -0
  93. data/config/locales/kaa.yml +1 -0
  94. data/config/locales/lb.yml +0 -8
  95. data/config/locales/lt.yml +0 -38
  96. data/config/locales/lv.yml +5 -7
  97. data/config/locales/nl.yml +1 -27
  98. data/config/locales/no.yml +3 -29
  99. data/config/locales/oc-FR.yml +3 -0
  100. data/config/locales/pl.yml +4 -39
  101. data/config/locales/pt-BR.yml +2 -10
  102. data/config/locales/pt.yml +0 -8
  103. data/config/locales/ro-RO.yml +85 -7
  104. data/config/locales/ru.yml +6 -4
  105. data/config/locales/sk.yml +8 -9
  106. data/config/locales/sl.yml +1 -0
  107. data/config/locales/sr-CS.yml +2 -0
  108. data/config/locales/sv.yml +23 -28
  109. data/config/locales/tr-TR.yml +7 -12
  110. data/config/locales/uk.yml +6 -4
  111. data/config/locales/zh-CN.yml +3 -8
  112. data/config/locales/zh-TW.yml +1872 -0
  113. data/lib/decidim/api/types/localized_string_type.rb +9 -0
  114. data/lib/decidim/api/types/translated_field_type.rb +20 -5
  115. data/lib/decidim/asset_router/pipeline.rb +95 -0
  116. data/lib/decidim/asset_router/storage.rb +82 -0
  117. data/lib/decidim/asset_router.rb +3 -75
  118. data/lib/decidim/attribute_object/form.rb +9 -0
  119. data/lib/decidim/core/test/factories.rb +13 -6
  120. data/lib/decidim/core/test/shared_examples/comments_examples.rb +36 -0
  121. data/lib/decidim/core/test/shared_examples/digest_mail_examples.rb +33 -0
  122. data/lib/decidim/core/test/shared_examples/editor_shared_examples.rb +5 -4
  123. data/lib/decidim/core/test/shared_examples/rich_text_editor_examples.rb +7 -3
  124. data/lib/decidim/core/test.rb +1 -0
  125. data/lib/decidim/core/version.rb +1 -1
  126. data/lib/decidim/dependency_resolver.rb +14 -8
  127. data/lib/decidim/form_builder.rb +5 -4
  128. data/lib/decidim/participatory_space_resourceable.rb +7 -1
  129. data/lib/decidim/publicable.rb +4 -0
  130. metadata +14 -6
@@ -1,11 +1,25 @@
1
1
  /* eslint-disable require-jsdoc */
2
2
 
3
- import lineBreakButtonHandler from "src/decidim/editor/linebreak_module"
4
- import "src/decidim/editor/clipboard_override"
5
- import "src/decidim/vendor/image-resize.min"
6
- import "src/decidim/vendor/image-upload.min"
3
+ import lineBreakButtonHandler from "src/decidim/editor/linebreak_module";
4
+ import "src/decidim/editor/clipboard_override";
5
+ import "src/decidim/vendor/image-resize.min";
6
+ import "src/decidim/vendor/image-upload.min";
7
7
 
8
- const quillFormats = ["bold", "italic", "link", "underline", "header", "list", "video", "image", "alt", "break", "width", "style", "code", "blockquote", "indent"];
8
+ const quillFormats = [
9
+ "bold",
10
+ "italic",
11
+ "link",
12
+ "underline",
13
+ "header",
14
+ "list",
15
+ "alt",
16
+ "break",
17
+ "width",
18
+ "style",
19
+ "code",
20
+ "blockquote",
21
+ "indent"
22
+ ];
9
23
 
10
24
  export default function createQuillEditor(container) {
11
25
  const toolbar = $(container).data("toolbar");
@@ -17,26 +31,28 @@ export default function createQuillEditor(container) {
17
31
  [{ list: "ordered" }, { list: "bullet" }],
18
32
  ["link", "clean"],
19
33
  ["code", "blockquote"],
20
- [{ "indent": "-1"}, { "indent": "+1" }]
34
+ [{ indent: "-1" }, { indent: "+1" }]
21
35
  ];
22
36
 
23
- let addImage = $(container).data("editorImages");
37
+ let addImage = false;
38
+ let addVideo = false;
24
39
 
25
- if (toolbar === "full") {
40
+ /**
41
+ * - basic = only basic controls without titles
42
+ * - content = basic + headings
43
+ * - full = basic + headings + image + video
44
+ */
45
+ if (toolbar === "content") {
46
+ quillToolbar = [[{ header: [2, 3, 4, 5, 6, false] }], ...quillToolbar];
47
+ } else if (toolbar === "full") {
48
+ addImage = true;
49
+ addVideo = true;
26
50
  quillToolbar = [
27
51
  [{ header: [2, 3, 4, 5, 6, false] }],
28
52
  ...quillToolbar,
29
- ["video"]
53
+ ["video"],
54
+ ["image"]
30
55
  ];
31
- } else if (toolbar === "basic") {
32
- quillToolbar = [
33
- ...quillToolbar,
34
- ["video"]
35
- ];
36
- }
37
-
38
- if (addImage) {
39
- quillToolbar.push(["image"]);
40
56
  }
41
57
 
42
58
  let modules = {
@@ -44,17 +60,26 @@ export default function createQuillEditor(container) {
44
60
  toolbar: {
45
61
  container: quillToolbar,
46
62
  handlers: {
47
- "linebreak": lineBreakButtonHandler
63
+ linebreak: lineBreakButtonHandler
48
64
  }
49
65
  }
50
66
  };
51
67
  const $input = $(container).siblings('input[type="hidden"]');
52
68
  container.innerHTML = $input.val() || "";
53
69
  const token = $('meta[name="csrf-token"]').attr("content");
70
+
71
+ if (addVideo) {
72
+ quillFormats.push("video");
73
+ }
74
+
54
75
  if (addImage) {
76
+ // Attempt to allow images only if the image support is enabled at editor support.
77
+ // see: https://github.com/quilljs/quill/issues/1108
78
+ quillFormats.push("image");
79
+
55
80
  modules.imageResize = {
56
81
  modules: ["Resize", "DisplaySize"]
57
- }
82
+ };
58
83
  modules.imageUpload = {
59
84
  url: $(container).data("uploadImagesPath"),
60
85
  method: "POST",
@@ -62,18 +87,23 @@ export default function createQuillEditor(container) {
62
87
  withCredentials: false,
63
88
  headers: { "X-CSRF-Token": token },
64
89
  callbackOK: (serverResponse, next) => {
65
- $("div.ql-toolbar").last().removeClass("editor-loading")
90
+ $("div.ql-toolbar").last().removeClass("editor-loading");
66
91
  next(serverResponse.url);
67
92
  },
68
93
  callbackKO: (serverError) => {
69
- $("div.ql-toolbar").last().removeClass("editor-loading")
94
+ $("div.ql-toolbar").last().removeClass("editor-loading");
70
95
  console.log(`Image upload error: ${serverError.message}`);
71
96
  },
72
97
  checkBeforeSend: (file, next) => {
73
- $("div.ql-toolbar").last().addClass("editor-loading")
98
+ $("div.ql-toolbar").last().addClass("editor-loading");
74
99
  next(file);
75
100
  }
76
- }
101
+ };
102
+
103
+ const text = $(container).data("dragAndDropHelpText");
104
+ $(container).after(
105
+ `<p class="help-text" style="margin-top:-1.5rem;">${text}</p>`
106
+ );
77
107
  }
78
108
  const quill = new Quill(container, {
79
109
  modules: modules,
@@ -81,6 +111,11 @@ export default function createQuillEditor(container) {
81
111
  theme: "snow"
82
112
  });
83
113
 
114
+ if (addImage === false) {
115
+ // Firefox natively implements image drop in contenteditable which is why we need to disable that
116
+ quill.root.addEventListener("drop", (ev) => ev.preventDefault());
117
+ }
118
+
84
119
  if (disabled) {
85
120
  quill.disable();
86
121
  }
@@ -95,7 +130,10 @@ export default function createQuillEditor(container) {
95
130
  });
96
131
  container.dispatchEvent(event);
97
132
 
98
- if ((text === "\n" || text === "\n\n") && quill.root.querySelectorAll(allowedEmptyContentSelector).length === 0) {
133
+ if (
134
+ (text === "\n" || text === "\n\n") &&
135
+ quill.root.querySelectorAll(allowedEmptyContentSelector).length === 0
136
+ ) {
99
137
  $input.val("");
100
138
  } else {
101
139
  const emptyParagraph = "<p><br></p>";
@@ -109,13 +147,5 @@ export default function createQuillEditor(container) {
109
147
  // After editor is ready, linebreak_module deletes two extraneous new lines
110
148
  quill.emitter.emit("editor-ready");
111
149
 
112
- if (addImage) {
113
- const text = $(container).data("dragAndDropHelpText");
114
- $(container).after(`<p class="help-text" style="margin-top:-1.5rem;">${text}</p>`);
115
- }
116
-
117
- // After editor is ready, linebreak_module deletes two extraneous new lines
118
- quill.emitter.emit("editor-ready");
119
-
120
150
  return quill;
121
151
  }
@@ -1,4 +1,4 @@
1
- /* global spyOn */
1
+ /* global spyOn, jest */
2
2
  /* eslint-disable id-length */
3
3
  window.$ = $;
4
4
 
@@ -7,6 +7,20 @@ import DataPicker from "./data_picker"
7
7
 
8
8
  const FormFilterComponent = require("./form_filter.component_for_testing.js");
9
9
 
10
+ const expectedPushState = (state, filters) => {
11
+ const queryString = Object.keys(filters).map((key) => {
12
+ const name = `filter[${key}]`;
13
+ const val = filters[key];
14
+ if (Array.isArray(val)) {
15
+ return val.map((v) => `${encodeURIComponent(`${name}[]`)}=${encodeURIComponent(v)}`).join("&");
16
+ }
17
+
18
+ return `${encodeURIComponent(name)}=${encodeURIComponent(val)}`;
19
+ }).join("&");
20
+
21
+ return [state, null, `/filters?${queryString}`];
22
+ }
23
+
10
24
  describe("FormFilterComponent", () => {
11
25
  const selector = "form#new_filter";
12
26
  let subject = null;
@@ -15,6 +29,10 @@ describe("FormFilterComponent", () => {
15
29
  beforeEach(() => {
16
30
  let form = `
17
31
  <form id="new_filter" action="/filters" method="get">
32
+ <fieldset>
33
+ <input id="filter_search_text_cont" placeholder="Search" data-disable-dynamic-change="true" type="search" name="filter[search_text_cont]">
34
+ </fieldset>
35
+
18
36
  <fieldset>
19
37
  <div id="filter_somerandomid_scope_id" class="data-picker picker-multiple" data-picker-name="filter[scope_id]">
20
38
  <div class="picker-values">
@@ -67,11 +85,25 @@ describe("FormFilterComponent", () => {
67
85
  `;
68
86
  $("body").append(form);
69
87
 
88
+ const $form = $(document).find("form");
89
+
70
90
  window.Decidim = window.Decidim || {};
71
91
 
72
92
  window.theDataPicker = new DataPicker($(".data-picker"));
73
93
  window.theCheckBoxesTree = new CheckBoxesTree();
74
- subject = new FormFilterComponent($(document).find("form"));
94
+ window.Rails = {
95
+ fire: (htmlElement, event) => {
96
+ // Hack to call trigger on the correct instance of the form, as fetching
97
+ // with the selector does not work.
98
+ if (htmlElement === $form[0]) {
99
+ $form.trigger(event);
100
+ }
101
+ }
102
+ };
103
+
104
+ subject = new FormFilterComponent($form);
105
+
106
+ jest.useFakeTimers();
75
107
  });
76
108
 
77
109
  it("exists", () => {
@@ -88,7 +120,19 @@ describe("FormFilterComponent", () => {
88
120
 
89
121
  describe("when mounted", () => {
90
122
  beforeEach(() => {
91
- spyOn(subject.$form, "on");
123
+ // Jest doesn't implement listening on the form submit event so we need
124
+ // to hack it.
125
+ const originalOn = subject.$form.on.bind(subject.$form);
126
+ jest.spyOn(subject.$form, "on").mockImplementation((...args) => {
127
+ if (args[0] === "submit") {
128
+ subject.$form.submitHandler = args[1];
129
+ } else if (args[0] === "change" && typeof args[1] === "string") {
130
+ subject.$form.changeHandler = args[2];
131
+ } else {
132
+ originalOn(...args);
133
+ }
134
+ });
135
+
92
136
  subject.mountComponent();
93
137
  });
94
138
 
@@ -100,8 +144,98 @@ describe("FormFilterComponent", () => {
100
144
  expect(subject.mounted).toBeTruthy();
101
145
  });
102
146
 
103
- it("binds the form change event", () => {
147
+ it("binds the form change and submit events", () => {
104
148
  expect(subject.$form.on).toHaveBeenCalledWith("change", "input:not([data-disable-dynamic-change]), select:not([data-disable-dynamic-change])", subject._onFormChange);
149
+ expect(subject.$form.on).toHaveBeenCalledWith("submit", subject._onFormSubmit);
150
+ });
151
+
152
+ describe("form changes", () => {
153
+ beforeEach(() => {
154
+ spyOn(window.history, "pushState");
155
+
156
+ // This is a hack to be able to trigger the events even somewhat close
157
+ // to an actual situation. In real browser environment the change events
158
+ // would be triggered by the input/select elements but to simplify the
159
+ // test implementation, we trigger them directly on the form.
160
+ const originalTrigger = subject.$form.trigger.bind(subject.$form);
161
+ jest.spyOn(subject.$form, "trigger").mockImplementation((...args) => {
162
+ if (args[0] === "submit") {
163
+ subject.$form.submitHandler(
164
+ $.event.fix(new CustomEvent("submit", { bubbles: true, cancelable: true }))
165
+ );
166
+ } else if (args[0] === "change") {
167
+ subject.$form.changeHandler();
168
+ } else {
169
+ originalTrigger(...args);
170
+ }
171
+
172
+ jest.runAllTimers();
173
+ });
174
+ });
175
+
176
+ it("does not save the state in case there were no changes to previous state", () => {
177
+ subject.$form.trigger("change");
178
+
179
+ expect(window.history.pushState).not.toHaveBeenCalled();
180
+ });
181
+
182
+ it("saves the state after dynamic form changes", () => {
183
+ $("#filter_somerandomid_category_id").val(2);
184
+
185
+ subject.$form.trigger("change");
186
+
187
+ const state = {
188
+ "filter_somerandomid_scope_id": [
189
+ {
190
+ "text": "Scope 1",
191
+ "url": "picker_url_1",
192
+ "value": "3"
193
+ },
194
+ {
195
+ "text": "Scope 2",
196
+ "url": "picker_url_2",
197
+ "value": "4"
198
+ }
199
+ ]
200
+ };
201
+ const filters = {
202
+ "search_text_cont": "",
203
+ "scope_id": [3, 4],
204
+ "category_id": 2,
205
+ "state": [""]
206
+ };
207
+ expect(window.history.pushState).toHaveBeenCalledWith(...expectedPushState(state, filters));
208
+ });
209
+
210
+ it("saves the state after form submission through input element", () => {
211
+ const textInput = document.getElementById("filter_search_text_cont");
212
+ textInput.value = "search";
213
+
214
+ subject.$form.trigger("submit");
215
+
216
+ const state = {
217
+ "filter_somerandomid_scope_id": [
218
+ {
219
+ "text": "Scope 1",
220
+ "url": "picker_url_1",
221
+ "value": "3"
222
+ },
223
+ {
224
+ "text": "Scope 2",
225
+ "url": "picker_url_2",
226
+ "value": "4"
227
+ }
228
+ ]
229
+ }
230
+ const filters = {
231
+ "search_text_cont": "search",
232
+ "scope_id": [3, 4],
233
+ "category_id": 1,
234
+ "state": [""]
235
+ }
236
+
237
+ expect(window.history.pushState).toHaveBeenCalledWith(...expectedPushState(state, filters));
238
+ });
105
239
  });
106
240
 
107
241
  describe("onpopstate event", () => {
@@ -131,6 +265,14 @@ describe("FormFilterComponent", () => {
131
265
  expect(checked.map((input) => input.value)).toEqual(["", "accepted", "evaluating"]);
132
266
  expect(checked.filter((input) => input.indeterminate).map((input) => input.value)).toEqual([""]);
133
267
  });
268
+
269
+ it("does not save the state", () => {
270
+ spyOn(window.history, "pushState");
271
+
272
+ window.onpopstate({ isTrusted: true, state: scopesPickerState});
273
+
274
+ expect(window.history.pushState).not.toHaveBeenCalled();
275
+ });
134
276
  });
135
277
  });
136
278
 
@@ -145,8 +287,9 @@ describe("FormFilterComponent", () => {
145
287
  expect(subject.mounted).toBeFalsy();
146
288
  });
147
289
 
148
- it("unbinds the form change event", () => {
290
+ it("unbinds the form change and submit events", () => {
149
291
  expect(subject.$form.off).toHaveBeenCalledWith("change", "input, select", subject._onFormChange);
292
+ expect(subject.$form.off).toHaveBeenCalledWith("submit", subject._onFormSubmit);
150
293
  });
151
294
  });
152
295
 
@@ -23,6 +23,7 @@ export default class FormFilterComponent {
23
23
 
24
24
  this._updateInitialState();
25
25
  this._onFormChange = delayed(this, this._onFormChange.bind(this));
26
+ this._onFormSubmit = delayed(this, this._onFormSubmit.bind(this));
26
27
  this._onPopState = this._onPopState.bind(this);
27
28
 
28
29
  if (window.Decidim.PopStateHandler) {
@@ -42,6 +43,7 @@ export default class FormFilterComponent {
42
43
  if (this.mounted) {
43
44
  this.mounted = false;
44
45
  this.$form.off("change", "input, select", this._onFormChange);
46
+ this.$form.off("submit", this._onFormSubmit);
45
47
 
46
48
  unregisterCallback(`filters-${this.id}`)
47
49
  }
@@ -62,6 +64,7 @@ export default class FormFilterComponent {
62
64
  contentContainer = this.$form.data("remoteFill");
63
65
  }
64
66
  this.$form.on("change", "input:not([data-disable-dynamic-change]), select:not([data-disable-dynamic-change])", this._onFormChange);
67
+ this.$form.on("submit", this._onFormSubmit);
65
68
 
66
69
  this.currentFormRequest = null;
67
70
  this.$form.on("ajax:beforeSend", (e) => {
@@ -254,14 +257,16 @@ export default class FormFilterComponent {
254
257
 
255
258
  // Only one instance should submit the form on browser history navigation
256
259
  if (this.popStateSubmiter) {
257
- Rails.fire(this.$form[0], "submit");
260
+ Rails.fire(this.$form[0], "submit", { from: "pop" });
258
261
  }
259
262
 
260
263
  this.changeEvents = true;
261
264
  }
262
265
 
263
266
  /**
264
- * Handles the logic to update the current location after a form change event.
267
+ * Handles the logic to decide whether the form should be submitted or not
268
+ * after a form change event. The form is only submitted when changes have
269
+ * occurred.
265
270
  * @private
266
271
  * @returns {Void} - Returns nothing.
267
272
  */
@@ -270,7 +275,7 @@ export default class FormFilterComponent {
270
275
  return;
271
276
  }
272
277
 
273
- const [newPath, newState] = this._currentStateAndPath();
278
+ const [newPath] = this._currentStateAndPath();
274
279
  const path = this._getLocation(false);
275
280
 
276
281
  if (newPath === path) {
@@ -278,6 +283,23 @@ export default class FormFilterComponent {
278
283
  }
279
284
 
280
285
  Rails.fire(this.$form[0], "submit");
286
+ }
287
+
288
+ /**
289
+ * Saves the current state of the search on form submit to update the search
290
+ * parameters to the URL and store the picker states.
291
+ * @private
292
+ * @param {jQuery.Event} ev The event that caused the form to submit.
293
+ * @returns {Void} - Returns nothing.
294
+ */
295
+ _onFormSubmit(ev) {
296
+ const eventDetail = ev.originalEvent.detail;
297
+ if (eventDetail && eventDetail.from === "pop") {
298
+ return;
299
+ }
300
+
301
+ const [newPath, newState] = this._currentStateAndPath();
302
+
281
303
  pushState(newPath, newState);
282
304
  this._saveFilters(newPath);
283
305
  }
@@ -314,7 +336,7 @@ export default class FormFilterComponent {
314
336
  * @returns {String} - Returns a unique identifier
315
337
  */
316
338
  _getUID() {
317
- return `filter-form-${new Date().setUTCMilliseconds()}-${Math.floor(Math.random() * 10000000)}`;
339
+ return `filter-form-${new Date().getUTCMilliseconds()}-${Math.floor(Math.random() * 10000000)}`;
318
340
  }
319
341
 
320
342
  /**
@@ -25,6 +25,13 @@ body{
25
25
  -moz-box-sizing: border-box;
26
26
  -webkit-box-sizing: border-box;
27
27
  box-sizing: border-box;
28
+
29
+ &.preview{
30
+ .see-on-website,
31
+ .unsubscribe{
32
+ cursor: not-allowed;
33
+ }
34
+ }
28
35
  }
29
36
 
30
37
  .ExternalClass{
@@ -123,6 +123,10 @@
123
123
  }
124
124
  }
125
125
 
126
+ &.primary{
127
+ @include button-hollow-variant(var(--primary));
128
+ }
129
+
126
130
  &.secondary{
127
131
  @include button-hollow-variant(var(--secondary));
128
132
  }
@@ -257,12 +261,12 @@
257
261
 
258
262
  .button--shadow{
259
263
  $shadows: (
260
- primary: shade($primary, 50),
261
- secondary: shade($secondary, 50),
262
- success: shade($success, 50),
263
- warning: shade($warning, 50),
264
- alert: shade($alert, 50),
265
- muted: shade($muted, 50),
264
+ primary: shade($primary, 50%),
265
+ secondary: shade($secondary, 50%),
266
+ success: shade($success, 50%),
267
+ warning: shade($warning, 50%),
268
+ alert: shade($alert, 50%),
269
+ muted: shade($muted, 50%),
266
270
  );
267
271
 
268
272
  @include modifiers(background-color, $shadows){
@@ -934,7 +934,7 @@ a .card__title{
934
934
  @include modifiers(
935
935
  color,
936
936
  (
937
- muted: tint($muted, 50),
937
+ muted: tint($muted, 50%),
938
938
  )
939
939
  ){
940
940
  margin-top: -$global-margin * .95;
@@ -17,6 +17,30 @@ $comment-form-bg: $light-gray;
17
17
 
18
18
  .comment-thread{
19
19
  @extend .card;
20
+
21
+ .show-comment-replies{
22
+ display: none;
23
+ }
24
+
25
+ .hide-comment-replies{
26
+ display: inline;
27
+ }
28
+
29
+ .comment__hide{
30
+ float: left;
31
+ margin-bottom: 0;
32
+ }
33
+
34
+ .no-comments{
35
+ .show-comment-replies{
36
+ display: inline;
37
+ }
38
+
39
+ .hide-comment-replies,
40
+ .replies{
41
+ display: none;
42
+ }
43
+ }
20
44
  }
21
45
 
22
46
  .comment-thread__title{
@@ -26,7 +26,8 @@
26
26
  top: 2px;
27
27
  width: 2rem;
28
28
  height: 2rem;
29
- background: rgba(var(--primary-rgb), .8);
29
+ background: var(--primary);
30
+ opacity: .8;
30
31
  color: $white;
31
32
  border-radius: 50%;
32
33
  }
@@ -32,10 +32,6 @@
32
32
  border-width: 2px;
33
33
  border-radius: 4px;
34
34
 
35
- * >{
36
- display: none;
37
- }
38
-
39
35
  .form-error{
40
36
  margin: 0;
41
37
  }
@@ -36,8 +36,8 @@
36
36
  @mixin loop-colors-types($color, $max: 12){
37
37
  @for $i from 0 through ($max - 1){
38
38
  $interval: ($i % 4) * 24 + 1;
39
- $tints: tint($color, $interval);
40
- $shades: shade($color, $interval);
39
+ $tints: tint($color, $interval * 1%);
40
+ $shades: shade($color, $interval * 1%);
41
41
  $adjusts: adjust-color($color, $lightness: $interval * 1%, $hue: -$interval);
42
42
 
43
43
  .type-#{$i}:not(.legend){
@@ -27,8 +27,8 @@
27
27
  @mixin loop-colors-types($color, $max: 12){
28
28
  @for $i from 0 through ($max - 1){
29
29
  $interval: ($i % 4) * 24 + 1;
30
- $tints: tint($color, $interval);
31
- $shades: shade($color, $interval);
30
+ $tints: tint($color, $interval * 1%);
31
+ $shades: shade($color, $interval * 1%);
32
32
  $adjusts: adjust-color($color, $lightness: $interval * 1%, $hue: -$interval);
33
33
 
34
34
  .type-#{$i}{
@@ -20,7 +20,7 @@ module Decidim
20
20
 
21
21
  def action_string
22
22
  case action
23
- when "verify", "verify_via_csv", "reject"
23
+ when "verify", "verify_via_csv", "reject", "block", "unblock"
24
24
  "decidim.admin_log.user_group.#{action}"
25
25
  else
26
26
  super
@@ -52,7 +52,7 @@ module Decidim
52
52
  end
53
53
 
54
54
  def unreported_user
55
- @unreported_user ||= Decidim::User.find_by(id: action_log.extra.dig("extra", "user_id"))
55
+ @unreported_user ||= Decidim::UserBaseEntity.find_by(id: action_log.extra.dig("extra", "user_id"))
56
56
  end
57
57
 
58
58
  def has_diff?
@@ -11,7 +11,7 @@ module Decidim
11
11
 
12
12
  def created_at_in_words
13
13
  if created_at.between?(1.month.ago, Time.current)
14
- time_ago_in_words(created_at)
14
+ I18n.t("decidim.user_conversations.index.time_ago", time: time_ago_in_words(created_at))
15
15
  else
16
16
  format = created_at.year == Time.current.year ? :ddmm : :ddmmyyyy
17
17
  I18n.l(created_at, format: format)
@@ -27,6 +27,7 @@ module Decidim
27
27
  @event ||= event_class.constantize.new(
28
28
  resource: resource,
29
29
  user: user,
30
+ user_role: user_role,
30
31
  event_name: event_name,
31
32
  extra: extra
32
33
  )
@@ -19,7 +19,7 @@ module Decidim
19
19
  end
20
20
 
21
21
  def url
22
- resource.reported_content_url
22
+ event_class_instance.resource_url
23
23
  end
24
24
  end
25
25
  end
@@ -16,7 +16,7 @@ module Decidim
16
16
  end
17
17
 
18
18
  def can_be_contacted?
19
- true
19
+ true unless blocked?
20
20
  end
21
21
 
22
22
  def officialization_text
@@ -61,7 +61,7 @@ module Decidim
61
61
  end
62
62
 
63
63
  def can_be_contacted?
64
- true
64
+ true unless blocked?
65
65
  end
66
66
 
67
67
  def officialization_text
@@ -14,12 +14,14 @@ module Decidim
14
14
  class AdminInputScrubber < UserInputScrubber
15
15
  private
16
16
 
17
+ DECIDIM_ALLOWED_TAGS = %w(img video audio source comment iframe).freeze
18
+
17
19
  def custom_allowed_attributes
18
20
  super + %w(frameborder allowfullscreen) - %w(onerror)
19
21
  end
20
22
 
21
23
  def custom_allowed_tags
22
- super + %w(comment iframe)
24
+ super + DECIDIM_ALLOWED_TAGS
23
25
  end
24
26
  end
25
27
  end