decidim-core 0.30.7 → 0.30.8
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.
- checksums.yaml +4 -4
- data/app/helpers/decidim/mailer_helper.rb +36 -0
- data/app/helpers/decidim/menu_helper.rb +2 -1
- data/app/helpers/decidim/newsletters_helper.rb +4 -22
- data/app/mailers/decidim/application_mailer.rb +4 -0
- data/app/packs/src/decidim/datepicker/datepicker_functions.js +26 -0
- data/app/packs/src/decidim/datepicker/generate_datepicker.js +2 -1
- data/app/packs/src/decidim/datepicker/generate_timepicker.js +9 -1
- data/app/packs/src/decidim/datepicker/test/datepicker_functions_adjust_picker_position.test.js +234 -0
- data/app/presenters/decidim/menu_item_presenter.rb +7 -1
- data/app/views/decidim/devise/registrations/new.html.erb +1 -0
- data/app/views/decidim/devise/shared/_tos_fields.html.erb +3 -3
- data/app/views/decidim/notification_mailer/event_received.html.erb +3 -3
- data/config/locales/cs.yml +3 -0
- data/config/locales/de.yml +13 -0
- data/config/locales/eu.yml +3 -0
- data/config/locales/fi-plain.yml +5 -0
- data/config/locales/fi.yml +5 -0
- data/lib/decidim/content_parsers/blob_parser.rb +2 -2
- data/lib/decidim/content_renderers/blob_renderer.rb +2 -2
- data/lib/decidim/core/version.rb +1 -1
- metadata +8 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b31c19b0f1263634559541d974f68dba477328f79a493ab4188eb3d8cb42a0eb
|
|
4
|
+
data.tar.gz: a9d06e9ea81dfe61b62c9f99961a4284af380a5343a12ce5c7ca7b055f4041b8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4c821103cefa1cfc8c3688fe69dce51bd2e6c94a448bee4fdc05ef5abd08e522cc4785e0827217b464b9b4a601cb52daa8539b21b0bf98f39e69fdff1b5824a5
|
|
7
|
+
data.tar.gz: 9c27f4b8ce372ac3295e719a3985b7276ec91d90303b4e627545d4571012936d3d4f1694c6e81e4df95fac154bbb2aaa3889130b9ffcfc609a1b2a40a699a99f
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Decidim
|
|
4
|
+
# Helper that provides methods to render order selector and links
|
|
5
|
+
module MailerHelper
|
|
6
|
+
# Transforms relative image URLs in HTML content to absolute URLs using the provided host.
|
|
7
|
+
# This is used in emails (newsletters and notifications) to ensure images display correctly
|
|
8
|
+
# in email clients.
|
|
9
|
+
#
|
|
10
|
+
# @param content [String] - HTML content with img tags
|
|
11
|
+
# @param host [String] - the Decidim::Organization host to use for the root URL
|
|
12
|
+
#
|
|
13
|
+
# @return [String] - the content with transformed image URLs
|
|
14
|
+
def decidim_transform_image_urls(content, host)
|
|
15
|
+
return content if host.blank? || content.blank?
|
|
16
|
+
|
|
17
|
+
root_url = if Rails.application.secrets.dig(:storage, :cdn_host).present?
|
|
18
|
+
Rails.application.secrets.dig(:storage, :cdn_host).chomp("/")
|
|
19
|
+
else
|
|
20
|
+
Decidim::EngineRouter.new("decidim", {}).root_url(host:).chomp("/")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
content.gsub(/src\s*=\s*(['"])([^'"]*)\1/) do
|
|
24
|
+
quote = Regexp.last_match(1)
|
|
25
|
+
src_value = Regexp.last_match(2)
|
|
26
|
+
|
|
27
|
+
if src_value.blank? || src_value.start_with?("http://", "https://", "data:", "//", "cid:")
|
|
28
|
+
%(src=#{quote}#{src_value}#{quote})
|
|
29
|
+
else
|
|
30
|
+
normalized_src = src_value.start_with?("/") ? src_value : "/#{src_value}"
|
|
31
|
+
%(src=#{quote}#{root_url}#{normalized_src}#{quote})
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -57,7 +57,8 @@ module Decidim
|
|
|
57
57
|
self,
|
|
58
58
|
element_class: "font-semibold underline",
|
|
59
59
|
active_class: "is-active",
|
|
60
|
-
|
|
60
|
+
role: false,
|
|
61
|
+
container_options: { class: "space-y-4 break-inside-avoid" },
|
|
61
62
|
label: t("layouts.decidim.footer.decidim_title")
|
|
62
63
|
)
|
|
63
64
|
end
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
module Decidim
|
|
4
4
|
# Helper that provides methods to render links with utm codes, and replaced name
|
|
5
5
|
module NewslettersHelper
|
|
6
|
+
include Decidim::SanitizeHelper
|
|
7
|
+
include Decidim::MailerHelper
|
|
8
|
+
|
|
6
9
|
# If the newsletter body there are some links and the Decidim.track_newsletter_links = true
|
|
7
10
|
# it will be replaced with the utm_codes method described below.
|
|
8
11
|
# for example transform "https://es.lipsum.com/" to "https://es.lipsum.com/?utm_source=localhost&utm_campaign=newsletter_11"
|
|
@@ -19,7 +22,7 @@ module Decidim
|
|
|
19
22
|
|
|
20
23
|
content = interpret_name(content, user)
|
|
21
24
|
content = track_newsletter_links(content, id, host)
|
|
22
|
-
|
|
25
|
+
decidim_transform_image_urls(content, host)
|
|
23
26
|
end
|
|
24
27
|
|
|
25
28
|
# this method is used to generate the root link on mail with the utm_codes
|
|
@@ -67,27 +70,6 @@ module Decidim
|
|
|
67
70
|
content.gsub("%{name}", user.name)
|
|
68
71
|
end
|
|
69
72
|
|
|
70
|
-
# Find each img HTML tag with relative path in src attribute
|
|
71
|
-
# For each URL, prepends the decidim.root_url
|
|
72
|
-
# If host is not defined it returns full content
|
|
73
|
-
#
|
|
74
|
-
# @param content [String] - the string to convert
|
|
75
|
-
# @param host [String] - the Decidim::Organization host to replace
|
|
76
|
-
#
|
|
77
|
-
# @return [String] - the content converted
|
|
78
|
-
#
|
|
79
|
-
def transform_image_urls(content, host)
|
|
80
|
-
return content if host.blank?
|
|
81
|
-
|
|
82
|
-
content.scan(/src\s*=\s*"([^"]*)"/).each do |src|
|
|
83
|
-
root_url = decidim.root_url(host:)[0..-2]
|
|
84
|
-
src_replaced = "#{root_url}#{src.first}"
|
|
85
|
-
content = content.gsub(/src\s*=\s*"([^"]*#{src.first})"/, %(src="#{src_replaced}"))
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
content
|
|
89
|
-
end
|
|
90
|
-
|
|
91
73
|
# Add tracking query params to each links
|
|
92
74
|
#
|
|
93
75
|
# @param content [String] - the string to convert
|
|
@@ -7,9 +7,13 @@ module Decidim
|
|
|
7
7
|
include LocalisedMailer
|
|
8
8
|
include MultitenantAssetHost
|
|
9
9
|
include Decidim::SanitizeHelper
|
|
10
|
+
include Decidim::MailerHelper
|
|
10
11
|
include Decidim::OrganizationHelper
|
|
11
12
|
helper_method :organization_name, :decidim_escape_translated, :decidim_sanitize_translated, :translated_attribute, :decidim_sanitize, :decidim_sanitize_newsletter
|
|
12
13
|
|
|
14
|
+
helper Decidim::SanitizeHelper
|
|
15
|
+
helper Decidim::MailerHelper
|
|
16
|
+
|
|
13
17
|
after_action :set_smtp
|
|
14
18
|
after_action :set_from
|
|
15
19
|
|
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
// Utility helper functions for the date and time picker functionality
|
|
2
2
|
|
|
3
|
+
export const adjustPickerPosition = (input, datePickerContainer, selector) => {
|
|
4
|
+
const parent = input.closest(selector);
|
|
5
|
+
|
|
6
|
+
if (getComputedStyle(parent).position === "static") {
|
|
7
|
+
parent.style.position = "relative";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const rect = input.getBoundingClientRect();
|
|
11
|
+
const calendarHeight = datePickerContainer.offsetHeight;
|
|
12
|
+
const spaceAbove = rect.top;
|
|
13
|
+
const spaceBelow = window.innerHeight - rect.bottom;
|
|
14
|
+
const openBelow = spaceBelow >= calendarHeight || spaceBelow >= spaceAbove;
|
|
15
|
+
|
|
16
|
+
if (openBelow) {
|
|
17
|
+
// Open below
|
|
18
|
+
datePickerContainer.style.top = `${input.offsetHeight}px`;
|
|
19
|
+
datePickerContainer.style.bottom = "";
|
|
20
|
+
} else {
|
|
21
|
+
// Open above
|
|
22
|
+
datePickerContainer.style.top = "";
|
|
23
|
+
datePickerContainer.style.bottom = `${input.offsetHeight}px`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
datePickerContainer.style.right = "0px";
|
|
27
|
+
};
|
|
28
|
+
|
|
3
29
|
export const setHour = (value, format) => {
|
|
4
30
|
const hour = value.split(":")[0];
|
|
5
31
|
if (format === 12) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable require-jsdoc */
|
|
2
2
|
import icon from "src/decidim/icon"
|
|
3
|
-
import { dateToPicker, formatDate, displayDate, formatTime, calculateDatepickerPos } from "src/decidim/datepicker/datepicker_functions"
|
|
3
|
+
import { dateToPicker, formatDate, displayDate, formatTime, calculateDatepickerPos, adjustPickerPosition } from "src/decidim/datepicker/datepicker_functions"
|
|
4
4
|
import { dateKeyDownListener, dateBeforeInputListener } from "src/decidim/datepicker/datepicker_listeners"
|
|
5
5
|
import { getDictionary } from "src/decidim/i18n"
|
|
6
6
|
|
|
@@ -130,6 +130,7 @@ export default function generateDatePicker(input, row, formats) {
|
|
|
130
130
|
};
|
|
131
131
|
pickedDate = null;
|
|
132
132
|
datePickerContainer.style.display = "block";
|
|
133
|
+
adjustPickerPosition(date, datePickerContainer, ".datepicker__date-column");
|
|
133
134
|
|
|
134
135
|
document.addEventListener("click", datePickerDisplay);
|
|
135
136
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/* eslint-disable require-jsdoc */
|
|
2
|
+
/* eslint max-lines: ["error", 310] */
|
|
3
|
+
|
|
2
4
|
import icon from "src/decidim/icon"
|
|
3
|
-
import { changeHourDisplay, changeMinuteDisplay, formatDate, hourDisplay, minuteDisplay, formatTime, setHour, setMinute, updateTimeValue, updateInputValue } from "src/decidim/datepicker/datepicker_functions"
|
|
5
|
+
import { changeHourDisplay, changeMinuteDisplay, formatDate, hourDisplay, minuteDisplay, formatTime, setHour, setMinute, updateTimeValue, updateInputValue, adjustPickerPosition } from "src/decidim/datepicker/datepicker_functions"
|
|
4
6
|
import { timeKeyDownListener, timeBeforeInputListener } from "src/decidim/datepicker/datepicker_listeners";
|
|
5
7
|
import { getDictionary } from "src/decidim/i18n";
|
|
6
8
|
|
|
@@ -21,6 +23,10 @@ export default function generateTimePicker(input, row, formats) {
|
|
|
21
23
|
clock.setAttribute("type", "button");
|
|
22
24
|
clock.setAttribute("aria-label", input.dataset.buttonTimeLabel);
|
|
23
25
|
|
|
26
|
+
if (input.attributes.disabled) {
|
|
27
|
+
clock.setAttribute("disabled", input.attributes.disabled);
|
|
28
|
+
};
|
|
29
|
+
|
|
24
30
|
timeColumn.appendChild(time);
|
|
25
31
|
timeColumn.appendChild(clock);
|
|
26
32
|
|
|
@@ -270,6 +276,8 @@ export default function generateTimePicker(input, row, formats) {
|
|
|
270
276
|
event.preventDefault();
|
|
271
277
|
timePicker.style.display = "block";
|
|
272
278
|
document.addEventListener("click", timePickerDisplay);
|
|
279
|
+
adjustPickerPosition(time, timePicker, ".datepicker__time-column")
|
|
280
|
+
|
|
273
281
|
hours.value = hourDisplay(hour);
|
|
274
282
|
minutes.value = minuteDisplay(minute);
|
|
275
283
|
});
|
data/app/packs/src/decidim/datepicker/test/datepicker_functions_adjust_picker_position.test.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/* global jest */
|
|
2
|
+
|
|
3
|
+
import { adjustPickerPosition } from "src/decidim/datepicker/datepicker_functions";
|
|
4
|
+
|
|
5
|
+
describe("adjustDatePickerPosition", () => {
|
|
6
|
+
let input = null;
|
|
7
|
+
let parent = null;
|
|
8
|
+
let datePickerContainer = null;
|
|
9
|
+
|
|
10
|
+
let originalInnerHeight = window.innerHeight;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
// Setup DOM structure
|
|
14
|
+
parent = document.createElement("div");
|
|
15
|
+
parent.className = "datepicker__date-column";
|
|
16
|
+
document.body.appendChild(parent);
|
|
17
|
+
|
|
18
|
+
input = document.createElement("input");
|
|
19
|
+
Reflect.defineProperty(input, "offsetHeight", {
|
|
20
|
+
configurable: true,
|
|
21
|
+
value: 40
|
|
22
|
+
});
|
|
23
|
+
parent.appendChild(input);
|
|
24
|
+
|
|
25
|
+
datePickerContainer = document.createElement("div");
|
|
26
|
+
datePickerContainer.className = "datepicker__container";
|
|
27
|
+
parent.appendChild(datePickerContainer);
|
|
28
|
+
|
|
29
|
+
// Mock offsetHeight for calendar
|
|
30
|
+
Reflect.defineProperty(datePickerContainer, "offsetHeight", {
|
|
31
|
+
configurable: true,
|
|
32
|
+
value: 300
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// store original viewport height
|
|
36
|
+
originalInnerHeight = window.innerHeight;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
document.body.removeChild(parent);
|
|
41
|
+
|
|
42
|
+
Reflect.defineProperty(window, "innerHeight", {
|
|
43
|
+
writable: true,
|
|
44
|
+
configurable: true,
|
|
45
|
+
value: originalInnerHeight
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
jest.restoreAllMocks();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("sets parent position to relative when static", () => {
|
|
52
|
+
parent.style.position = "static";
|
|
53
|
+
|
|
54
|
+
adjustPickerPosition(input, datePickerContainer, ".datepicker__date-column");
|
|
55
|
+
|
|
56
|
+
expect(parent.style.position).toBe("relative");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("does not change parent position when already positioned", () => {
|
|
60
|
+
parent.style.position = "absolute";
|
|
61
|
+
|
|
62
|
+
adjustPickerPosition(input, datePickerContainer, ".datepicker__date-column");
|
|
63
|
+
|
|
64
|
+
expect(parent.style.position).toBe("absolute");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("opens below when sufficient space below", () => {
|
|
68
|
+
jest.spyOn(input, "getBoundingClientRect").mockReturnValue({
|
|
69
|
+
top: 100,
|
|
70
|
+
bottom: 140
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
Reflect.defineProperty(window, "innerHeight", {
|
|
74
|
+
writable: true,
|
|
75
|
+
configurable: true,
|
|
76
|
+
value: 800
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
adjustPickerPosition(input, datePickerContainer, ".datepicker__date-column");
|
|
80
|
+
|
|
81
|
+
expect(datePickerContainer.style.top).toBe("40px");
|
|
82
|
+
expect(datePickerContainer.style.bottom).toBe("");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("opens above when insufficient space below", () => {
|
|
86
|
+
jest.spyOn(input, "getBoundingClientRect").mockReturnValue({
|
|
87
|
+
top: 400,
|
|
88
|
+
bottom: 440
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
Reflect.defineProperty(window, "innerHeight", {
|
|
92
|
+
writable: true,
|
|
93
|
+
configurable: true,
|
|
94
|
+
value: 500
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
adjustPickerPosition(input, datePickerContainer, ".datepicker__date-column");
|
|
98
|
+
|
|
99
|
+
expect(datePickerContainer.style.top).toBe("");
|
|
100
|
+
expect(datePickerContainer.style.bottom).toBe("40px");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("prefers opening below when space is equal above and below", () => {
|
|
104
|
+
jest.spyOn(input, "getBoundingClientRect").mockReturnValue({
|
|
105
|
+
top: 250,
|
|
106
|
+
bottom: 290
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
Reflect.defineProperty(window, "innerHeight", {
|
|
110
|
+
writable: true,
|
|
111
|
+
configurable: true,
|
|
112
|
+
value: 540
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
adjustPickerPosition(input, datePickerContainer, ".datepicker__date-column");
|
|
116
|
+
|
|
117
|
+
expect(datePickerContainer.style.top).toBe("40px");
|
|
118
|
+
expect(datePickerContainer.style.bottom).toBe("");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("always sets right position to 0px", () => {
|
|
122
|
+
jest.spyOn(input, "getBoundingClientRect").mockReturnValue({
|
|
123
|
+
top: 100,
|
|
124
|
+
bottom: 140
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
adjustPickerPosition(input, datePickerContainer, ".datepicker__date-column");
|
|
128
|
+
|
|
129
|
+
expect(datePickerContainer.style.right).toBe("0px");
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
describe("adjustTimePickerPosition", () => {
|
|
135
|
+
let input = null;
|
|
136
|
+
let parent = null;
|
|
137
|
+
let timePicker = null;
|
|
138
|
+
|
|
139
|
+
let originalInnerHeight = window.innerHeight;
|
|
140
|
+
|
|
141
|
+
beforeEach(() => {
|
|
142
|
+
parent = document.createElement("div");
|
|
143
|
+
parent.className = "datepicker__time-column";
|
|
144
|
+
document.body.appendChild(parent);
|
|
145
|
+
|
|
146
|
+
input = document.createElement("input");
|
|
147
|
+
Reflect.defineProperty(input, "offsetHeight", {
|
|
148
|
+
configurable: true,
|
|
149
|
+
value: 30
|
|
150
|
+
});
|
|
151
|
+
parent.appendChild(input);
|
|
152
|
+
|
|
153
|
+
timePicker = document.createElement("div");
|
|
154
|
+
timePicker.className = "timepicker__container";
|
|
155
|
+
parent.appendChild(timePicker);
|
|
156
|
+
|
|
157
|
+
Reflect.defineProperty(timePicker, "offsetHeight", {
|
|
158
|
+
configurable: true,
|
|
159
|
+
value: 200
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// store original value before any test mutates it
|
|
163
|
+
originalInnerHeight = window.innerHeight;
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
afterEach(() => {
|
|
167
|
+
// restore DOM
|
|
168
|
+
document.body.removeChild(parent);
|
|
169
|
+
|
|
170
|
+
// restore window.innerHeight (fix for CodeRabbit warning)
|
|
171
|
+
Reflect.defineProperty(window, "innerHeight", {
|
|
172
|
+
writable: true,
|
|
173
|
+
configurable: true,
|
|
174
|
+
value: originalInnerHeight
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
jest.restoreAllMocks();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("sets parent position to relative when static", () => {
|
|
181
|
+
parent.style.position = "static";
|
|
182
|
+
|
|
183
|
+
adjustPickerPosition(input, timePicker, ".datepicker__time-column");
|
|
184
|
+
|
|
185
|
+
expect(parent.style.position).toBe("relative");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("opens below when there is enough space", () => {
|
|
189
|
+
jest.spyOn(input, "getBoundingClientRect").mockReturnValue({
|
|
190
|
+
top: 100,
|
|
191
|
+
bottom: 130
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
Reflect.defineProperty(window, "innerHeight", {
|
|
195
|
+
writable: true,
|
|
196
|
+
configurable: true,
|
|
197
|
+
value: 700
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
adjustPickerPosition(input, timePicker, ".datepicker__time-column");
|
|
201
|
+
|
|
202
|
+
expect(timePicker.style.top).toBe("30px");
|
|
203
|
+
expect(timePicker.style.bottom).toBe("");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("opens above when there is not enough space below", () => {
|
|
207
|
+
jest.spyOn(input, "getBoundingClientRect").mockReturnValue({
|
|
208
|
+
top: 400,
|
|
209
|
+
bottom: 430
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
Reflect.defineProperty(window, "innerHeight", {
|
|
213
|
+
writable: true,
|
|
214
|
+
configurable: true,
|
|
215
|
+
value: 500
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
adjustPickerPosition(input, timePicker, ".datepicker__time-column");
|
|
219
|
+
|
|
220
|
+
expect(timePicker.style.top).toBe("");
|
|
221
|
+
expect(timePicker.style.bottom).toBe("30px");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("always aligns to the right", () => {
|
|
225
|
+
jest.spyOn(input, "getBoundingClientRect").mockReturnValue({
|
|
226
|
+
top: 100,
|
|
227
|
+
bottom: 130
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
adjustPickerPosition(input, timePicker, ".datepicker__time-column");
|
|
231
|
+
|
|
232
|
+
expect(timePicker.style.right).toBe("0px");
|
|
233
|
+
});
|
|
234
|
+
});
|
|
@@ -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, role:
|
|
29
|
+
content_tag :li, role: menuitem_role, 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
|
|
@@ -62,6 +62,12 @@ module Decidim
|
|
|
62
62
|
[@options.element_class, active_class].compact.join(" ")
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
def menuitem_role
|
|
66
|
+
return if @options.role == false
|
|
67
|
+
|
|
68
|
+
@options.role || :menuitem
|
|
69
|
+
end
|
|
70
|
+
|
|
65
71
|
def active_class
|
|
66
72
|
active_link_to_class(
|
|
67
73
|
url,
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
<%= f.text_field :name, help_text: t("decidim.devise.registrations.new.username_help"), autocomplete: "name", placeholder: "John Doe" %>
|
|
35
35
|
|
|
36
36
|
<%= f.email_field :email, autocomplete: "email", placeholder: t("placeholder_email", scope: "decidim.devise.shared") %>
|
|
37
|
+
<span class="sr-only"><%= t("placeholder_email", scope: "decidim.devise.shared") %></span>
|
|
37
38
|
|
|
38
39
|
<%= render partial: "decidim/account/password_fields", locals: { form: f, user: :user } %>
|
|
39
40
|
</div>
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<div id="card__tos" class="form__wrapper-block border-y-2">
|
|
2
2
|
<h2 class="h4"><%= t("decidim.devise.registrations.new.tos_title") %></h2>
|
|
3
|
+
<span class="sr-only"><%= t("forms.required") %></span>
|
|
3
4
|
|
|
4
|
-
<div>
|
|
5
|
+
<div id="terms_of_service_summary">
|
|
5
6
|
<% terms_of_service_summary_content_blocks.each do |content_block| %>
|
|
6
7
|
<%= cell content_block.manifest.cell, content_block %>
|
|
7
8
|
<% end %>
|
|
8
9
|
</div>
|
|
9
|
-
|
|
10
|
-
<%= form.check_box :tos_agreement, label: t("decidim.devise.registrations.new.tos_agreement", link: link_to(t("decidim.devise.registrations.new.terms"), decidim.page_path("terms-of-service"))), label_options: { class: "form__wrapper-checkbox-label" } %>
|
|
10
|
+
<%= form.check_box :tos_agreement, label: t("decidim.devise.registrations.new.tos_agreement", link: link_to(t("decidim.devise.registrations.new.terms"), decidim.page_path("terms-of-service"))), label_options: { class: "form__wrapper-checkbox-label" }, "aria-describedby": "terms_of_service_summary" %>
|
|
11
11
|
</div>
|
|
12
12
|
|
|
13
13
|
<div id="card__newsletter" class="form__wrapper-block">
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
<blockquote>
|
|
17
17
|
<p>
|
|
18
|
-
<%= @event_instance.safe_resource_text %>
|
|
18
|
+
<%= decidim_transform_image_urls(@event_instance.safe_resource_text, @organization.host).html_safe %>
|
|
19
19
|
</p>
|
|
20
20
|
</blockquote>
|
|
21
21
|
<% end %>
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
<p style="font-weight: bold"><%= t(".translated_text") %></p>
|
|
29
29
|
<blockquote>
|
|
30
30
|
<p>
|
|
31
|
-
<%= @event_instance.safe_resource_translated_text %>
|
|
31
|
+
<%= decidim_transform_image_urls(@event_instance.safe_resource_translated_text, @organization.host).html_safe %>
|
|
32
32
|
</p>
|
|
33
33
|
</blockquote>
|
|
34
34
|
<% end %>
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
<table>
|
|
41
41
|
<tr>
|
|
42
42
|
<td>
|
|
43
|
-
<%= link_to @event_instance.button_text, @event_instance.button_url, target: :blank %>
|
|
43
|
+
<%= link_to decidim_sanitize(@event_instance.button_text, strip_tags: true), @event_instance.button_url, target: :blank %>
|
|
44
44
|
</td>
|
|
45
45
|
</tr>
|
|
46
46
|
</table>
|
data/config/locales/cs.yml
CHANGED
|
@@ -1214,6 +1214,9 @@ cs:
|
|
|
1214
1214
|
actions:
|
|
1215
1215
|
confirm_modal:
|
|
1216
1216
|
ok_add: Přidat administrátora
|
|
1217
|
+
ok_remove: Odstranit správce
|
|
1218
|
+
title_add: Potvrdit nového správce
|
|
1219
|
+
title_remove: Odstranit správce
|
|
1217
1220
|
demote_admin: Odstranit admin
|
|
1218
1221
|
demote:
|
|
1219
1222
|
error: Při odebrání tohoto účastníka ze seznamu administrátorů došlo k chybě.
|
data/config/locales/de.yml
CHANGED
|
@@ -1118,6 +1118,8 @@ de:
|
|
|
1118
1118
|
explanation: 'Anleitung für Bild:'
|
|
1119
1119
|
message_1: Vorzugsweise ein Bild im Querformat, das keinen Text enthält.
|
|
1120
1120
|
message_2: Der Dienst schneidet die Datei zu.
|
|
1121
|
+
import_file:
|
|
1122
|
+
message_1: Muss ein JSON-Dokument sein, das über die Exportfunktion heruntergeladen wurde.
|
|
1121
1123
|
file_validation:
|
|
1122
1124
|
allowed_file_extensions: 'Erlaubte Dateiformate: %{extensions}'
|
|
1123
1125
|
max_file_dimension: 'Maximale Dateigröße: %{resolution} Pixel'
|
|
@@ -1478,9 +1480,13 @@ de:
|
|
|
1478
1480
|
same_language: Der Inhalt wurde in Ihrer bevorzugten Sprache (%{language}) veröffentlicht, daher wird in dieser E-Mail keine automatisierte Übersetzung angezeigt.
|
|
1479
1481
|
translated_text: 'Automatisch übersetzter Text:'
|
|
1480
1482
|
notifications:
|
|
1483
|
+
action_error: Beim Aktualisieren der Benachrichtigungseinstellungen ist ein Fehler aufgetreten.
|
|
1481
1484
|
no_notifications: Noch keine Benachrichtigungen
|
|
1482
1485
|
show:
|
|
1486
|
+
deleted: Inhalt wurde vom Autor gelöscht.
|
|
1483
1487
|
missing_event: Hoppla, diese Benachrichtigung gehört zu einem Artikel, der nicht mehr verfügbar ist. Du kannst sie verwerfen.
|
|
1488
|
+
moderated: Inhalt wurde durch Moderation versteckt.
|
|
1489
|
+
not_available: Hoppla, diese Benachrichtigung gehört zu einem Artikel, der nicht mehr verfügbar ist. Es ist keine weitere Aktion erforderlich.
|
|
1484
1490
|
notifications_digest_mailer:
|
|
1485
1491
|
header:
|
|
1486
1492
|
daily: Tägliche Zusammenfassung
|
|
@@ -1489,6 +1495,7 @@ de:
|
|
|
1489
1495
|
hello: Hallo %{name}
|
|
1490
1496
|
intro:
|
|
1491
1497
|
daily: 'Dies sind die Benachrichtigungen vom letzten Tag basierend auf den Aktivitäten, denen Sie folgen:'
|
|
1498
|
+
real_time: 'Es gibt eine Benachrichtigung über die Aktivität, die Sie folgen:'
|
|
1492
1499
|
weekly: 'Dies sind die Benachrichtigungen der letzten Woche, basierend auf den Aktivitäten, die Sie folgen:'
|
|
1493
1500
|
outro: Sie haben diese Benachrichtigung erhalten, weil Sie diesen Inhalt oder seine Verfassenden folgen. Sie können dem Inhalt direkt auf seiner Seite entfolgen.
|
|
1494
1501
|
see_more: Weitere Benachrichtigungen ansehen
|
|
@@ -1642,7 +1649,9 @@ de:
|
|
|
1642
1649
|
title: Wie man diese Dateien öffnet und mit ihnen arbeitet
|
|
1643
1650
|
license:
|
|
1644
1651
|
body_1_html: Diese Datenbank von %{organization_name} wird unter %{link_database} zur Verfügung gestellt. Alle Rechte an einzelnen Inhalten der Datenbank sind unter %{link_contents} lizenziert.
|
|
1652
|
+
license_contents_link: https://opendatacommons.org/licenses/dbcl/1.0/
|
|
1645
1653
|
license_contents_name: Lizenz für Datenbankinhalte
|
|
1654
|
+
license_database_link: https://opendatacommons.org/licenses/odbl/1.0/
|
|
1646
1655
|
license_database_name: Offene Datenbanklizenz
|
|
1647
1656
|
title: Lizenz
|
|
1648
1657
|
title: Offene Daten
|
|
@@ -1962,6 +1971,7 @@ de:
|
|
|
1962
1971
|
send_paranoid_instructions: Wenn Ihre E-Mail-Adresse in unserer Datenbank vorhanden ist, erhalten Sie innerhalb weniger Minuten eine E-Mail mit Anweisungen zur Bestätigung Ihrer E-Mail-Adresse.
|
|
1963
1972
|
failure:
|
|
1964
1973
|
already_authenticated: Sie sind bereits angemeldet.
|
|
1974
|
+
csrf_token: Ihre Anfrage konnte nicht verifiziert werden. Bitte versuchen Sie es erneut.
|
|
1965
1975
|
inactive: Dein Benutzerkonto ist noch nicht aktiviert.
|
|
1966
1976
|
invalid: Ungültige %{authentication_keys} oder Passwort
|
|
1967
1977
|
invited: Sie haben eine ausstehende Einladung, akzeptieren Sie sie, um die Erstellung Ihres Kontos abzuschließen.
|
|
@@ -1976,6 +1986,7 @@ de:
|
|
|
1976
1986
|
nickname_help: Ihr Pseudonym auf %{organization}. Kann nur Buchstaben, Zahlen, '-' und '_' enthalten.
|
|
1977
1987
|
submit_button: Speichern
|
|
1978
1988
|
subtitle: Wenn Sie die Einladung annehmen, geben Sie bitte Ihren Kontonamen und Ihr Passwort ein.
|
|
1989
|
+
subtitle_no_password: Wenn Sie die Einladung annehmen, geben Sie bitte Ihren Kontonamen ein.
|
|
1979
1990
|
invitation_removed: Ihre Einladung wurde entfernt.
|
|
1980
1991
|
invitation_token_invalid: Das angegebene Einladungstoken ist nicht gültig!
|
|
1981
1992
|
new:
|
|
@@ -1983,6 +1994,8 @@ de:
|
|
|
1983
1994
|
submit_button: Eine Einladung schicken
|
|
1984
1995
|
no_invitations_remaining: Keine Einladungen übrig
|
|
1985
1996
|
send_instructions: Eine Einladungs-E-Mail wurde an %{email}gesendet.
|
|
1997
|
+
updated: Einladung erfolgreich angenommen. Sie sind jetzt angemeldet.
|
|
1998
|
+
updated_not_active: Einladung erfolgreich angenommen.
|
|
1986
1999
|
mailer:
|
|
1987
2000
|
confirmation_instructions:
|
|
1988
2001
|
action: Konto bestätigen
|
data/config/locales/eu.yml
CHANGED
|
@@ -1989,6 +1989,7 @@ eu:
|
|
|
1989
1989
|
nickname_help: Zure ezizena %{organization}-an. Letrak, zenbakiak, '-' eta '_' soilik eduki ditzake.
|
|
1990
1990
|
submit_button: Gorde
|
|
1991
1991
|
subtitle: Gonbidapena onartzen baduzu, mesedez, ezarri zure ezizena eta pasahitza.
|
|
1992
|
+
subtitle_no_password: Gonbidapena onartzen baduzu, jarri zure ezizena.
|
|
1992
1993
|
invitation_removed: Zure gonbidapena kendu egin da.
|
|
1993
1994
|
invitation_token_invalid: Gonbidapen token hori ez da baliozkoa!
|
|
1994
1995
|
new:
|
|
@@ -1996,6 +1997,8 @@ eu:
|
|
|
1996
1997
|
submit_button: Bidali gonbidapena
|
|
1997
1998
|
no_invitations_remaining: Ez da gonbidapenik geratzen
|
|
1998
1999
|
send_instructions: Gonbidapen-mezu elektroniko bat %{email} helbidera bidali da.
|
|
2000
|
+
updated: Gonbidapena behar bezala onartu da. Orain erregistratuta zaude.
|
|
2001
|
+
updated_not_active: Gonbidapena behar bezala onartu da.
|
|
1999
2002
|
mailer:
|
|
2000
2003
|
confirmation_instructions:
|
|
2001
2004
|
action: Berretsi nire kontua
|
data/config/locales/fi-plain.yml
CHANGED
|
@@ -1991,6 +1991,7 @@ fi-pl:
|
|
|
1991
1991
|
nickname_help: Nimimerkkisi palvelussa %{organization}. Voi sisältää ainoastaan kirjaimia, numeroita sekä yhdysmerkkejä "-" ja alaviivoja "_".
|
|
1992
1992
|
submit_button: Tallenna
|
|
1993
1993
|
subtitle: Jos hyväksyt kutsun, aseta käyttäjänimesi ja salasana.
|
|
1994
|
+
subtitle_no_password: Jos hyväksyt kutsun, aseta tilillesi nimi.
|
|
1994
1995
|
invitation_removed: Kutsusi peruutettiin.
|
|
1995
1996
|
invitation_token_invalid: Käyttämäsi kutsuavain ei ole voimassa!
|
|
1996
1997
|
new:
|
|
@@ -1998,6 +1999,8 @@ fi-pl:
|
|
|
1998
1999
|
submit_button: Lähetä kutsu
|
|
1999
2000
|
no_invitations_remaining: Ei kutsuja jäljellä
|
|
2000
2001
|
send_instructions: Kutsuviesti on lähetetty %{email}.
|
|
2002
|
+
updated: Kutsun hyväksyminen onnistui. Olet nyt kirjautunut sisään.
|
|
2003
|
+
updated_not_active: Kutsun hyväksyminen onnistui.
|
|
2001
2004
|
mailer:
|
|
2002
2005
|
confirmation_instructions:
|
|
2003
2006
|
action: Vahvista käyttäjätilini
|
|
@@ -2057,6 +2060,8 @@ fi-pl:
|
|
|
2057
2060
|
confirm_new_password: Vahvista uusi salasana
|
|
2058
2061
|
new_password: Uusi salasana
|
|
2059
2062
|
old_password_help: Vahvistaaksesi muutokset käyttäjätiliisi, anna nykyinen salasanasi.
|
|
2063
|
+
password_help: "Vähintään %{minimum_characters} merkkiä, vähintään 5 eri merkkiä, ei voi olla yleisesti käytetty salasana (esim. 123456), eikä voi vastata nimeäsi, nimimerkkiäsi, sähköpostiosoitettasi tai tämän palvelun verkko-osoitetta."
|
|
2064
|
+
password_help_admin: "Vähintään %{minimum_characters} merkkiä, vähintään 5 eri merkkiä, ei voi olla yleisesti käytetty salasana (esim. 123456), eikä voi vastata nimeäsi, nimimerkkiäsi, sähköpostiosoitettasi tai tämän palvelun verkko-osoitetta. Et myöskään voi käyttää aikaisempia salasanojasi."
|
|
2060
2065
|
title: Salasanan vaihto
|
|
2061
2066
|
new:
|
|
2062
2067
|
forgot_your_password: Unohditko salasanasi?
|
data/config/locales/fi.yml
CHANGED
|
@@ -1991,6 +1991,7 @@ fi:
|
|
|
1991
1991
|
nickname_help: Nimimerkkisi palvelussa %{organization}. Voi sisältää ainoastaan kirjaimia, numeroita sekä yhdysmerkkejä "-" ja alaviivoja "_".
|
|
1992
1992
|
submit_button: Tallenna
|
|
1993
1993
|
subtitle: Jos hyväksyt kutsun, aseta käyttäjänimesi ja salasana.
|
|
1994
|
+
subtitle_no_password: Jos hyväksyt kutsun, aseta tilillesi nimi.
|
|
1994
1995
|
invitation_removed: Kutsusi on peruttu.
|
|
1995
1996
|
invitation_token_invalid: Käyttämäsi kutsuavain ei ole voimassa!
|
|
1996
1997
|
new:
|
|
@@ -1998,6 +1999,8 @@ fi:
|
|
|
1998
1999
|
submit_button: Lähetä kutsu
|
|
1999
2000
|
no_invitations_remaining: Ei kutsuja jäljellä
|
|
2000
2001
|
send_instructions: Kutsuviesti on lähetetty %{email}.
|
|
2002
|
+
updated: Kutsun hyväksyminen onnistui. Olet nyt kirjautunut sisään.
|
|
2003
|
+
updated_not_active: Kutsun hyväksyminen onnistui.
|
|
2001
2004
|
mailer:
|
|
2002
2005
|
confirmation_instructions:
|
|
2003
2006
|
action: Vahvista käyttäjätilini
|
|
@@ -2057,6 +2060,8 @@ fi:
|
|
|
2057
2060
|
confirm_new_password: Vahvista uusi salasana
|
|
2058
2061
|
new_password: Uusi salasana
|
|
2059
2062
|
old_password_help: Vahvistaaksesi muutokset käyttäjätiliisi, anna nykyinen salasanasi.
|
|
2063
|
+
password_help: "Vähintään %{minimum_characters} merkkiä, vähintään 5 eri merkkiä, ei voi olla yleisesti käytetty salasana (esim. 123456), eikä voi vastata nimeäsi, nimimerkkiäsi, sähköpostiosoitettasi tai tämän palvelun verkko-osoitetta."
|
|
2064
|
+
password_help_admin: "Vähintään %{minimum_characters} merkkiä, vähintään 5 eri merkkiä, ei voi olla yleisesti käytetty salasana (esim. 123456), eikä voi vastata nimeäsi, nimimerkkiäsi, sähköpostiosoitettasi tai tämän palvelun verkko-osoitetta. Et myöskään voi käyttää aikaisempia salasanojasi."
|
|
2060
2065
|
title: Salasanan vaihto
|
|
2061
2066
|
new:
|
|
2062
2067
|
forgot_your_password: Unohditko salasanasi?
|
|
@@ -34,8 +34,8 @@ module Decidim
|
|
|
34
34
|
# Group 6: Variation key for representations
|
|
35
35
|
/(?<variation_part>[\w.=-]+)
|
|
36
36
|
)?
|
|
37
|
-
# Group 7: Filename
|
|
38
|
-
/([
|
|
37
|
+
# Group 7: Filename (supports apostrophes inside names but not as HTML quote delimiters)
|
|
38
|
+
/((?:[^\s/"<>']|'(?=[^\s/"<>']))+)
|
|
39
39
|
}x
|
|
40
40
|
|
|
41
41
|
def rewrite
|
|
@@ -18,8 +18,8 @@ module Decidim
|
|
|
18
18
|
#
|
|
19
19
|
# @see BaseRenderer Examples of how to use a content renderer
|
|
20
20
|
class BlobRenderer < BaseRenderer
|
|
21
|
-
# Matches a global id representing a
|
|
22
|
-
GLOBAL_ID_REGEX = %r{(gid://[\w-]+/ActiveStorage::Blob/\d+)(/([\w=-]+))?}
|
|
21
|
+
# Matches a global id representing an ActiveStorage::Blob (optionally with a variant key)
|
|
22
|
+
GLOBAL_ID_REGEX = %r{(gid://[\w-]+/ActiveStorage::Blob/\d+)(/([\w=-]+))?(?:[^\s"'<>]*)}
|
|
23
23
|
|
|
24
24
|
# Replaces found Global IDs matching an existing blob with a URL to
|
|
25
25
|
# that blob. The Global IDs representing an invalid ActiveStorage::Blob
|
data/lib/decidim/core/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: decidim-core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.30.
|
|
4
|
+
version: 0.30.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Josep Jaume Rey Peroy
|
|
@@ -10,7 +10,7 @@ authors:
|
|
|
10
10
|
autorequire:
|
|
11
11
|
bindir: bin
|
|
12
12
|
cert_chain: []
|
|
13
|
-
date: 2026-
|
|
13
|
+
date: 2026-04-24 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: concurrent-ruby
|
|
@@ -794,28 +794,28 @@ dependencies:
|
|
|
794
794
|
requirements:
|
|
795
795
|
- - '='
|
|
796
796
|
- !ruby/object:Gem::Version
|
|
797
|
-
version: 0.30.
|
|
797
|
+
version: 0.30.8
|
|
798
798
|
type: :development
|
|
799
799
|
prerelease: false
|
|
800
800
|
version_requirements: !ruby/object:Gem::Requirement
|
|
801
801
|
requirements:
|
|
802
802
|
- - '='
|
|
803
803
|
- !ruby/object:Gem::Version
|
|
804
|
-
version: 0.30.
|
|
804
|
+
version: 0.30.8
|
|
805
805
|
- !ruby/object:Gem::Dependency
|
|
806
806
|
name: decidim-dev
|
|
807
807
|
requirement: !ruby/object:Gem::Requirement
|
|
808
808
|
requirements:
|
|
809
809
|
- - '='
|
|
810
810
|
- !ruby/object:Gem::Version
|
|
811
|
-
version: 0.30.
|
|
811
|
+
version: 0.30.8
|
|
812
812
|
type: :development
|
|
813
813
|
prerelease: false
|
|
814
814
|
version_requirements: !ruby/object:Gem::Requirement
|
|
815
815
|
requirements:
|
|
816
816
|
- - '='
|
|
817
817
|
- !ruby/object:Gem::Version
|
|
818
|
-
version: 0.30.
|
|
818
|
+
version: 0.30.8
|
|
819
819
|
description: Adds core features so other engines can hook into the framework.
|
|
820
820
|
email:
|
|
821
821
|
- josepjaume@gmail.com
|
|
@@ -1388,6 +1388,7 @@ files:
|
|
|
1388
1388
|
- app/helpers/decidim/language_chooser_helper.rb
|
|
1389
1389
|
- app/helpers/decidim/layout_helper.rb
|
|
1390
1390
|
- app/helpers/decidim/localized_locales_helper.rb
|
|
1391
|
+
- app/helpers/decidim/mailer_helper.rb
|
|
1391
1392
|
- app/helpers/decidim/map_helper.rb
|
|
1392
1393
|
- app/helpers/decidim/markup_helper.rb
|
|
1393
1394
|
- app/helpers/decidim/menu_helper.rb
|
|
@@ -1619,6 +1620,7 @@ files:
|
|
|
1619
1620
|
- app/packs/src/decidim/datepicker/generate_datepicker.js
|
|
1620
1621
|
- app/packs/src/decidim/datepicker/generate_timepicker.js
|
|
1621
1622
|
- app/packs/src/decidim/datepicker/test/date.test.js
|
|
1623
|
+
- app/packs/src/decidim/datepicker/test/datepicker_functions_adjust_picker_position.test.js
|
|
1622
1624
|
- app/packs/src/decidim/datepicker/test/time.test.js
|
|
1623
1625
|
- app/packs/src/decidim/decidim_application.js
|
|
1624
1626
|
- app/packs/src/decidim/delayed.js
|