decidim-core 0.27.0.rc1 → 0.27.0
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.
- checksums.yaml +4 -4
- data/app/cells/decidim/announcement_cell.rb +1 -1
- data/app/cells/decidim/content_blocks/cta/show.erb +1 -1
- data/app/cells/decidim/content_blocks/cta_cell.rb +1 -1
- data/app/cells/decidim/content_blocks/hero/show.erb +2 -2
- data/app/cells/decidim/content_blocks/hero_cell.rb +1 -0
- data/app/cells/decidim/content_blocks/highlighted_content_banner/show.erb +2 -2
- data/app/cells/decidim/content_blocks/stats_cell.rb +1 -0
- data/app/cells/decidim/content_blocks/sub_hero_cell.rb +1 -1
- data/app/cells/decidim/data_consent/category.erb +19 -19
- data/app/cells/decidim/data_consent/dialog.erb +12 -12
- data/app/cells/decidim/data_consent/modal.erb +13 -13
- data/app/cells/decidim/data_consent_cell.rb +3 -3
- data/app/commands/decidim/update_account.rb +3 -1
- data/app/controllers/concerns/decidim/resource_versions_concern.rb +4 -0
- data/app/controllers/decidim/devise/registrations_controller.rb +5 -1
- data/app/controllers/decidim/last_activities_controller.rb +1 -0
- data/app/events/decidim/resource_endorsed_event.rb +2 -1
- data/app/forms/decidim/account_form.rb +1 -1
- data/app/forms/decidim/user_group_form.rb +1 -1
- data/app/helpers/decidim/filters_helper.rb +5 -1
- data/app/helpers/decidim/sanitize_helper.rb +12 -3
- data/app/models/decidim/action_log.rb +9 -9
- data/app/models/decidim/notification.rb +11 -2
- data/app/models/decidim/user_base_entity.rb +1 -0
- data/app/packs/entrypoints/decidim_core.js +1 -1
- data/app/packs/src/decidim/account_form.js +68 -18
- data/app/packs/src/decidim/{cookie_consent → data_consent}/consent_manager.js +27 -24
- data/app/packs/src/decidim/data_consent/consent_manager.test.js +280 -0
- data/app/packs/src/decidim/{cookie_consent/cookie_consent.js → data_consent/index.js} +12 -12
- data/app/packs/src/decidim/input_character_counter.js +57 -34
- data/app/packs/src/decidim/sw/a2hs.js +10 -1
- data/app/packs/stylesheets/decidim/modules/_cards.scss +2 -0
- data/app/packs/stylesheets/decidim/modules/_comments.scss +2 -0
- data/app/packs/stylesheets/decidim/modules/{_cookie-consent.scss → _data-consent.scss} +10 -10
- data/app/packs/stylesheets/decidim/modules/_forms.scss +5 -0
- data/app/packs/stylesheets/decidim/modules/_modules.scss +1 -1
- data/app/permissions/decidim/permissions.rb +4 -2
- data/app/presenters/decidim/push_notification_presenter.rb +2 -1
- data/app/presenters/decidim/user_presenter.rb +4 -1
- data/app/scrubbers/decidim/admin_input_scrubber.rb +25 -0
- data/app/scrubbers/decidim/user_input_scrubber.rb +3 -5
- data/app/services/decidim/iframe_disabler.rb +2 -3
- data/app/services/decidim/notifications_digest_sending_decider.rb +6 -2
- data/app/services/decidim/send_push_notification.rb +14 -12
- data/app/views/decidim/account/_password_fields.html.erb +2 -2
- data/app/views/decidim/download_your_data/show.html.erb +1 -1
- data/app/views/decidim/notifications_settings/show.html.erb +65 -67
- data/app/views/decidim/pages/_standalone.html.erb +1 -1
- data/app/views/decidim/pages/_tabbed.html.erb +1 -1
- data/app/views/decidim/user_interests/show.html.erb +11 -13
- data/app/views/layouts/decidim/_application.html.erb +1 -1
- data/app/views/layouts/decidim/_data_consent_warning.html.erb +8 -0
- data/app/views/layouts/decidim/_main_footer.html.erb +1 -1
- data/config/locales/am-ET.yml +0 -1
- data/config/locales/ar.yml +0 -11
- data/config/locales/bg.yml +1 -10
- data/config/locales/ca.yml +20 -25
- data/config/locales/cs.yml +26 -31
- data/config/locales/da.yml +0 -1
- data/config/locales/de.yml +205 -11
- data/config/locales/el.yml +0 -9
- data/config/locales/en.yml +17 -22
- data/config/locales/es-MX.yml +21 -26
- data/config/locales/es-PY.yml +20 -25
- data/config/locales/es.yml +22 -27
- data/config/locales/et.yml +0 -1
- data/config/locales/eu.yml +4 -13
- data/config/locales/fi-plain.yml +17 -22
- data/config/locales/fi.yml +17 -22
- data/config/locales/fr-CA.yml +30 -21
- data/config/locales/fr.yml +43 -34
- data/config/locales/ga-IE.yml +1 -3
- data/config/locales/gl.yml +0 -11
- data/config/locales/hr.yml +0 -1
- data/config/locales/hu.yml +271 -10
- data/config/locales/id-ID.yml +0 -11
- data/config/locales/is-IS.yml +2 -1
- data/config/locales/it.yml +3 -12
- data/config/locales/ja.yml +31 -25
- data/config/locales/ko.yml +0 -1
- data/config/locales/lb.yml +0 -9
- data/config/locales/lt.yml +1949 -0
- data/config/locales/lv.yml +0 -9
- data/config/locales/mt.yml +0 -1
- data/config/locales/nl.yml +18 -12
- data/config/locales/no.yml +1 -10
- data/config/locales/oc-FR.yml +1 -0
- data/config/locales/om-ET.yml +0 -1
- data/config/locales/pl.yml +1 -10
- data/config/locales/pt-BR.yml +4 -13
- data/config/locales/pt.yml +1 -10
- data/config/locales/ro-RO.yml +0 -9
- data/config/locales/ru.yml +0 -2
- data/config/locales/si-LK.yml +0 -1
- data/config/locales/sk.yml +0 -12
- data/config/locales/so-SO.yml +0 -1
- data/config/locales/sv.yml +9 -14
- data/config/locales/sw-KE.yml +0 -1
- data/config/locales/ti-ER.yml +0 -1
- data/config/locales/tr-TR.yml +2 -11
- data/config/locales/uk.yml +1 -2
- data/config/locales/val-ES.yml +0 -1
- data/config/locales/vi.yml +0 -1
- data/config/locales/zh-CN.yml +2 -11
- data/config/locales/zh-TW.yml +0 -1
- data/config/routes.rb +20 -2
- data/lib/decidim/attributes/model.rb +9 -1
- data/lib/decidim/content_parsers/hashtag_parser.rb +1 -1
- data/lib/decidim/core/engine.rb +1 -1
- data/lib/decidim/core/test/shared_examples/resource_endorsed_event_examples.rb +60 -0
- data/lib/decidim/core/test/shared_examples/versions_controller_examples.rb +40 -0
- data/lib/decidim/core/test/shared_examples/with_endorsable_permissions_examples.rb +1 -1
- data/lib/decidim/core/test.rb +2 -0
- data/lib/decidim/core/version.rb +1 -1
- data/lib/decidim/core.rb +52 -13
- data/lib/decidim/dependency_resolver.rb +272 -0
- data/lib/decidim/events/simple_event.rb +1 -0
- data/lib/decidim/has_resource_permission.rb +0 -2
- data/lib/decidim/map/provider/dynamic_map/here.rb +46 -1
- data/lib/decidim/nicknamizable.rb +1 -1
- data/lib/decidim/translatable_attributes.rb +8 -1
- data/lib/decidim/url_option_resolver.rb +1 -1
- metadata +18 -13
- data/app/views/decidim/devise/registrations/edit.html.erb +0 -41
- data/app/views/layouts/decidim/_cookie_warning.html.erb +0 -8
@@ -2,10 +2,10 @@ import Cookies from "js-cookie";
|
|
2
2
|
|
3
3
|
class ConsentManager {
|
4
4
|
// Options should contain the following keys:
|
5
|
-
// - modal - HTML element of the
|
6
|
-
// - categories - Available
|
5
|
+
// - modal - HTML element of the data consent modal (e.g. "<div id="dc-modal">Foo bar</div>")
|
6
|
+
// - categories - Available data consent categories (e.g. ["essential", "preferences", "analytics", "marketing"])
|
7
7
|
// - cookieName - Name of the cookie saved in the browser (e.g. "decidim-consent")
|
8
|
-
// - warningElement - HTML element to be shown when user
|
8
|
+
// - warningElement - HTML element to be shown when user has not given the necessary data consent to display the content.
|
9
9
|
constructor(options) {
|
10
10
|
this.modal = options.modal;
|
11
11
|
this.categories = options.categories;
|
@@ -21,7 +21,12 @@ class ConsentManager {
|
|
21
21
|
|
22
22
|
updateState(newState) {
|
23
23
|
this.state = newState;
|
24
|
-
Cookies.set(this.cookieName, JSON.stringify(this.state)
|
24
|
+
Cookies.set(this.cookieName, JSON.stringify(this.state), {
|
25
|
+
domain: document.location.host.split(":")[0],
|
26
|
+
sameSite: "Lax",
|
27
|
+
expires: 365,
|
28
|
+
secure: window.location.protocol === "https:"
|
29
|
+
});
|
25
30
|
this.updateModalSelections();
|
26
31
|
this.triggerState();
|
27
32
|
}
|
@@ -46,33 +51,31 @@ class ConsentManager {
|
|
46
51
|
triggerIframes() {
|
47
52
|
if (this.allAccepted()) {
|
48
53
|
document.querySelectorAll(".disabled-iframe").forEach((original) => {
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
+
if (original.childNodes && original.childNodes.length) {
|
55
|
+
const content = Array.from(original.childNodes).find((childNode) => {
|
56
|
+
return childNode.nodeType === Node.COMMENT_NODE;
|
57
|
+
});
|
58
|
+
if (!content) {
|
59
|
+
return;
|
60
|
+
}
|
61
|
+
const newElement = document.createElement("div");
|
62
|
+
newElement.innerHTML = content.nodeValue;
|
63
|
+
original.parentNode.replaceChild(newElement.firstElementChild, original);
|
64
|
+
}
|
65
|
+
});
|
54
66
|
} else {
|
55
67
|
document.querySelectorAll("iframe").forEach((original) => {
|
56
|
-
const newElement =
|
57
|
-
newElement.className =
|
58
|
-
|
59
|
-
original.
|
60
|
-
})
|
68
|
+
const newElement = document.createElement("div");
|
69
|
+
newElement.className = "disabled-iframe";
|
70
|
+
newElement.appendChild(document.createComment(`${original.outerHTML}`));
|
71
|
+
original.parentNode.replaceChild(newElement, original);
|
72
|
+
});
|
61
73
|
}
|
62
74
|
}
|
63
75
|
|
64
|
-
transformElement(original, targetType) {
|
65
|
-
const newElement = document.createElement(targetType);
|
66
|
-
["src", "allow", "frameborder", "style", "loading"].forEach((attribute) => {
|
67
|
-
newElement.setAttribute(attribute, original.getAttribute(attribute));
|
68
|
-
})
|
69
|
-
|
70
|
-
return newElement;
|
71
|
-
}
|
72
|
-
|
73
76
|
triggerWarnings() {
|
74
77
|
document.querySelectorAll(".disabled-iframe").forEach((original) => {
|
75
|
-
if (original.querySelector(".
|
78
|
+
if (original.querySelector(".dataconsent-warning")) {
|
76
79
|
return;
|
77
80
|
}
|
78
81
|
|
@@ -0,0 +1,280 @@
|
|
1
|
+
/* global global, jest */
|
2
|
+
|
3
|
+
import Cookies from "js-cookie";
|
4
|
+
import ConsentManager from "./consent_manager";
|
5
|
+
|
6
|
+
// Mock js-cookie so that we can store the return values from the "set" method
|
7
|
+
// in order to inspect their flags.
|
8
|
+
jest.mock("js-cookie", () => {
|
9
|
+
const originalModule = jest.requireActual("js-cookie");
|
10
|
+
|
11
|
+
return {
|
12
|
+
// ...originalModule,
|
13
|
+
cookieStorage: {},
|
14
|
+
get: originalModule.get,
|
15
|
+
set: function (name, value, options) {
|
16
|
+
this.cookieStorage[name] = originalModule.set(name, value, options);
|
17
|
+
}
|
18
|
+
};
|
19
|
+
});
|
20
|
+
|
21
|
+
Reflect.deleteProperty(global.window, "location");
|
22
|
+
global.window = Object.create(window);
|
23
|
+
global.window.location = {
|
24
|
+
protocol: "https:",
|
25
|
+
hostname: "decidim.dev"
|
26
|
+
};
|
27
|
+
|
28
|
+
describe("ConsentManager", () => {
|
29
|
+
const dialogContent = `
|
30
|
+
<div id="dc-dialog-wrapper" class="flex-center" role="region">
|
31
|
+
<div class="dc-dialog padding-vertical-1">
|
32
|
+
<div class="row expanded">
|
33
|
+
<div class="columns medium-12 large-8">
|
34
|
+
<div class="dc-content">
|
35
|
+
<div class="h5">Information about the cookies used on the website</div>
|
36
|
+
<div>
|
37
|
+
We use cookies on our website to improve the performance and content of the site. The cookies enable us to provide a more individual user experience and social media channels.
|
38
|
+
</div>
|
39
|
+
</div>
|
40
|
+
</div>
|
41
|
+
<div class="columns medium-12 large-4">
|
42
|
+
<div class="dc-button-wrapper flex-center">
|
43
|
+
<button id="dc-dialog-accept" class="button">
|
44
|
+
Accept all
|
45
|
+
</button>
|
46
|
+
<button id="dc-dialog-reject" class="button hollow">
|
47
|
+
Accept only essential
|
48
|
+
</button>
|
49
|
+
<button id="dc-dialog-settings" class="button clear" data-open="dc-modal" aria-controls="dc-modal" aria-haspopup="dialog" tabindex="0">
|
50
|
+
Settings
|
51
|
+
</button>
|
52
|
+
</div>
|
53
|
+
</div>
|
54
|
+
</div>
|
55
|
+
</div>
|
56
|
+
</div>
|
57
|
+
`;
|
58
|
+
const modalContent = `
|
59
|
+
<div class="reveal dc-modal" id="dc-modal" role="dialog" data-close-on-click="false" data-close-on-esc="false" aria-modal="true" aria-hidden="true">
|
60
|
+
<div class="reveal__header">
|
61
|
+
<h3 class="reveal__title">Cookie settings</h3>
|
62
|
+
<p>
|
63
|
+
We use cookies to ensure the basic functionalities of the website and to enhance your online experience. You can choose for each category to opt-in/out whenever you want.
|
64
|
+
</p>
|
65
|
+
</div>
|
66
|
+
|
67
|
+
<div class="dc-categories">
|
68
|
+
<div class="category-wrapper margin-vertical-1" data-id="essential">
|
69
|
+
<div class="category-row flex-center">
|
70
|
+
<button class="dc-title padding-left-3">
|
71
|
+
<span class="h5 dc-category-title">
|
72
|
+
<strong>Essential</strong>
|
73
|
+
</span>
|
74
|
+
</button>
|
75
|
+
<div class="dc-switch">
|
76
|
+
<input class="switch-input" checked="checked" id="dc-essential" type="checkbox" name="essential" disabled="">
|
77
|
+
<label class="switch-paddle" for="dc-essential">
|
78
|
+
<span class="show-for-sr">Toggle Essential</span>
|
79
|
+
</label>
|
80
|
+
</div>
|
81
|
+
</div>
|
82
|
+
<div class="dc-description hide">
|
83
|
+
<div class="description-text">
|
84
|
+
<p>These cookies are essential for the proper functioning of my website. Without these cookies, the website would not work properly.</p>
|
85
|
+
</div>
|
86
|
+
<div class="dataconsent-details-wrapper">
|
87
|
+
<div class="row detail-titles">
|
88
|
+
<div class="columns small-2">Type</div>
|
89
|
+
<div class="columns small-2">Name</div>
|
90
|
+
<div class="columns small-2">Service</div>
|
91
|
+
<div class="columns small-6">Description</div>
|
92
|
+
</div>
|
93
|
+
<div class="row dataconsent-detail-row">
|
94
|
+
<div class="columns small-2">Cookie</div>
|
95
|
+
<div class="columns small-2">_session_id</div>
|
96
|
+
<div class="columns small-2">This website</div>
|
97
|
+
<div class="columns small-6">
|
98
|
+
Allows websites to remember user within a website when they move between web pages.
|
99
|
+
</div>
|
100
|
+
</div>
|
101
|
+
<div class="row dataconsent-detail-row">
|
102
|
+
<div class="columns small-2">Cookie</div>
|
103
|
+
<div class="columns small-2">decidim-consent</div>
|
104
|
+
<div class="columns small-2">This website</div>
|
105
|
+
<div class="columns small-6">
|
106
|
+
Stores information about the cookies allowed by the user on this website.
|
107
|
+
</div>
|
108
|
+
</div>
|
109
|
+
</div>
|
110
|
+
</div>
|
111
|
+
</div>
|
112
|
+
<div class="category-wrapper margin-vertical-1" data-id="preferences">
|
113
|
+
<div class="category-row flex-center">
|
114
|
+
<button class="dc-title padding-left-3">
|
115
|
+
<span class="h5 dc-category-title">
|
116
|
+
<strong>Preferences</strong>
|
117
|
+
</span>
|
118
|
+
</button>
|
119
|
+
<div class="dc-switch">
|
120
|
+
<input class="switch-input" id="dc-preferences" type="checkbox" name="preferences">
|
121
|
+
<label class="switch-paddle" for="dc-preferences">
|
122
|
+
<span class="show-for-sr">Toggle Preferences</span>
|
123
|
+
</label>
|
124
|
+
</div>
|
125
|
+
</div>
|
126
|
+
<div class="dc-description hide">
|
127
|
+
<div class="description-text">
|
128
|
+
<p>These cookies allow the website to remember the choices you have made in the past</p>
|
129
|
+
</div>
|
130
|
+
</div>
|
131
|
+
</div>
|
132
|
+
<div class="category-wrapper margin-vertical-1" data-id="analytics">
|
133
|
+
<div class="category-row flex-center">
|
134
|
+
<button class="dc-title padding-left-3">
|
135
|
+
<span class="h5 dc-category-title">
|
136
|
+
<strong>Analytics and statistics</strong>
|
137
|
+
</span>
|
138
|
+
</button>
|
139
|
+
<div class="dc-switch">
|
140
|
+
<input class="switch-input" id="dc-analytics" type="checkbox" name="analytics">
|
141
|
+
<label class="switch-paddle" for="dc-analytics">
|
142
|
+
<span class="show-for-sr">Toggle Analytics and statistics</span>
|
143
|
+
</label>
|
144
|
+
</div>
|
145
|
+
</div>
|
146
|
+
<div class="dc-description hide">
|
147
|
+
<div class="description-text">
|
148
|
+
<p>Analytics cookies are cookies that track how users navigate and interact with a website. The information collected is used to help the website owner improve the website.</p>
|
149
|
+
</div>
|
150
|
+
</div>
|
151
|
+
</div>
|
152
|
+
<div class="category-wrapper margin-vertical-1" data-id="marketing">
|
153
|
+
<div class="category-row flex-center">
|
154
|
+
<button class="dc-title padding-left-3">
|
155
|
+
<span class="h5 dc-category-title">
|
156
|
+
<strong>Marketing</strong>
|
157
|
+
</span>
|
158
|
+
</button>
|
159
|
+
<div class="dc-switch">
|
160
|
+
<input class="switch-input" id="dc-marketing" type="checkbox" name="marketing">
|
161
|
+
<label class="switch-paddle" for="dc-marketing">
|
162
|
+
<span class="show-for-sr">Toggle Marketing</span>
|
163
|
+
</label>
|
164
|
+
</div>
|
165
|
+
</div>
|
166
|
+
<div class="dc-description hide">
|
167
|
+
<div class="description-text">
|
168
|
+
<p>These cookies collect information about how you use the website, which pages you visited and which links you clicked on.</p>
|
169
|
+
</div>
|
170
|
+
</div>
|
171
|
+
</div>
|
172
|
+
</div>
|
173
|
+
<div class="dc-buttons-wrapper flex-center">
|
174
|
+
<div class="dc-buttons-left">
|
175
|
+
<button id="dc-modal-accept" class="button" data-close="">Accept all</button>
|
176
|
+
<button id="dc-modal-reject" class="button hollow" data-close="">Accept only essential</button>
|
177
|
+
</div>
|
178
|
+
<div class="dc-buttons-right">
|
179
|
+
<button id="dc-modal-save" class="button clear" data-close="">Save settings</button>
|
180
|
+
</div>
|
181
|
+
</div>
|
182
|
+
</div>
|
183
|
+
`;
|
184
|
+
const cookieWarningContent = `
|
185
|
+
<div class="dataconsent-warning flex-center padding-1 hide">
|
186
|
+
<p>You need to enable all cookies in order to see this content.</p>
|
187
|
+
<a href="#" class="button margin-vertical-2" data-open="dc-modal">
|
188
|
+
Change cookie settings
|
189
|
+
</a>
|
190
|
+
</div>
|
191
|
+
`;
|
192
|
+
|
193
|
+
const getCookie = (name) => {
|
194
|
+
for (const [key, value] of Object.entries(Cookies.cookieStorage)) {
|
195
|
+
if (key === name) {
|
196
|
+
const cookie = {};
|
197
|
+
value.split(";").forEach((cookieString) => {
|
198
|
+
const kv = cookieString.trim().split("=");
|
199
|
+
const val = kv[1] || true;
|
200
|
+
|
201
|
+
if (kv[0] === key) {
|
202
|
+
cookie.value = val;
|
203
|
+
} else {
|
204
|
+
cookie[kv[0]] = val;
|
205
|
+
}
|
206
|
+
});
|
207
|
+
|
208
|
+
return cookie;
|
209
|
+
}
|
210
|
+
}
|
211
|
+
|
212
|
+
return null;
|
213
|
+
};
|
214
|
+
|
215
|
+
let manager = null;
|
216
|
+
const originalLocation = window.location;
|
217
|
+
|
218
|
+
beforeEach(() => {
|
219
|
+
Cookies.cookieStorage = {};
|
220
|
+
|
221
|
+
document.body.innerHTML = dialogContent + modalContent + cookieWarningContent;
|
222
|
+
|
223
|
+
const modal = document.querySelector("#dc-modal");
|
224
|
+
const categories = [...modal.querySelectorAll(".category-wrapper")].map((el) => el.dataset.id);
|
225
|
+
manager = new ConsentManager({
|
226
|
+
modal: modal,
|
227
|
+
categories: categories,
|
228
|
+
cookieName: "decidim-consent",
|
229
|
+
warningElement: document.querySelector(".dataconsent-warning")
|
230
|
+
});
|
231
|
+
});
|
232
|
+
|
233
|
+
afterEach(() => {
|
234
|
+
window.location = originalLocation;
|
235
|
+
});
|
236
|
+
|
237
|
+
describe("updateState", () => {
|
238
|
+
let cookie = null;
|
239
|
+
|
240
|
+
describe("when the current URL is secure", () => {
|
241
|
+
beforeEach(() => {
|
242
|
+
manager.updateState({ foo: "bar" });
|
243
|
+
cookie = getCookie("decidim-consent");
|
244
|
+
});
|
245
|
+
|
246
|
+
it("sets the cookie with the correct value", () => {
|
247
|
+
expect(cookie.value).toEqual("{%22foo%22:%22bar%22}");
|
248
|
+
});
|
249
|
+
|
250
|
+
it("sets the cookie with the correct expiry", () => {
|
251
|
+
const expiry = Date.parse(cookie.expires);
|
252
|
+
const oneYearFromNow = Date.now() + 3600 * 24 * 1000 * 365;
|
253
|
+
|
254
|
+
// Expect it to be within a second of the expected expiry time because
|
255
|
+
// it would be pretty hard to try to guess the exact same millisecond.
|
256
|
+
expect(oneYearFromNow - 1000).toBeLessThan(expiry);
|
257
|
+
expect(oneYearFromNow + 1000).toBeGreaterThan(expiry);
|
258
|
+
});
|
259
|
+
|
260
|
+
it("sets the cookie with the correct flags", () => {
|
261
|
+
expect(cookie.domain).toEqual("decidim.dev");
|
262
|
+
expect(cookie.sameSite).toEqual("Lax");
|
263
|
+
expect(cookie.secure).toBe(true);
|
264
|
+
});
|
265
|
+
});
|
266
|
+
|
267
|
+
describe("when the current URL is insecure", () => {
|
268
|
+
beforeEach(() => {
|
269
|
+
Reflect.defineProperty(window.location, "protocol", { value: "http:" })
|
270
|
+
|
271
|
+
manager.updateState({ foo: "bar" });
|
272
|
+
cookie = getCookie("decidim-consent");
|
273
|
+
});
|
274
|
+
|
275
|
+
it("does not set the secure flag", () => {
|
276
|
+
expect(cookie.secure).toBeUndefined();
|
277
|
+
});
|
278
|
+
});
|
279
|
+
});
|
280
|
+
});
|
@@ -1,15 +1,15 @@
|
|
1
|
-
import ConsentManager from "src/decidim/
|
1
|
+
import ConsentManager from "src/decidim/data_consent/consent_manager";
|
2
2
|
|
3
3
|
const initDialog = (manager) => {
|
4
4
|
if (Object.keys(manager.state).length > 0) {
|
5
5
|
return;
|
6
6
|
}
|
7
|
-
const dialogWrapper = document.querySelector("#
|
7
|
+
const dialogWrapper = document.querySelector("#dc-dialog-wrapper");
|
8
8
|
dialogWrapper.classList.remove("hide");
|
9
9
|
|
10
|
-
const acceptAllButton = dialogWrapper.querySelector("#
|
11
|
-
const rejectAllButton = dialogWrapper.querySelector("#
|
12
|
-
const settingsButton = dialogWrapper.querySelector("#
|
10
|
+
const acceptAllButton = dialogWrapper.querySelector("#dc-dialog-accept");
|
11
|
+
const rejectAllButton = dialogWrapper.querySelector("#dc-dialog-reject");
|
12
|
+
const settingsButton = dialogWrapper.querySelector("#dc-dialog-settings");
|
13
13
|
|
14
14
|
acceptAllButton.addEventListener("click", () => {
|
15
15
|
manager.acceptAll();
|
@@ -30,8 +30,8 @@ const initModal = (manager) => {
|
|
30
30
|
const categoryElements = manager.modal.querySelectorAll(".category-wrapper");
|
31
31
|
|
32
32
|
categoryElements.forEach((categoryEl) => {
|
33
|
-
const categoryButton = categoryEl.querySelector(".
|
34
|
-
const categoryDescription = categoryEl.querySelector(".
|
33
|
+
const categoryButton = categoryEl.querySelector(".dc-title");
|
34
|
+
const categoryDescription = categoryEl.querySelector(".dc-description");
|
35
35
|
categoryButton.addEventListener("click", () => {
|
36
36
|
const hidden = categoryDescription.classList.contains("hide");
|
37
37
|
if (hidden) {
|
@@ -44,9 +44,9 @@ const initModal = (manager) => {
|
|
44
44
|
})
|
45
45
|
})
|
46
46
|
|
47
|
-
const acceptAllButton = manager.modal.querySelector("#
|
48
|
-
const rejectAllButton = manager.modal.querySelector("#
|
49
|
-
const saveSettingsButton = manager.modal.querySelector("#
|
47
|
+
const acceptAllButton = manager.modal.querySelector("#dc-modal-accept");
|
48
|
+
const rejectAllButton = manager.modal.querySelector("#dc-modal-reject");
|
49
|
+
const saveSettingsButton = manager.modal.querySelector("#dc-modal-save");
|
50
50
|
|
51
51
|
acceptAllButton.addEventListener("click", () => {
|
52
52
|
manager.acceptAll();
|
@@ -84,7 +84,7 @@ const initDisabledIframes = (manager) => {
|
|
84
84
|
}
|
85
85
|
|
86
86
|
document.addEventListener("DOMContentLoaded", () => {
|
87
|
-
const modal = document.querySelector("#
|
87
|
+
const modal = document.querySelector("#dc-modal");
|
88
88
|
if (!modal) {
|
89
89
|
return;
|
90
90
|
}
|
@@ -94,7 +94,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
94
94
|
modal: modal,
|
95
95
|
categories: categories,
|
96
96
|
cookieName: window.Decidim.config.get("consent_cookie_name"),
|
97
|
-
warningElement: document.querySelector(".
|
97
|
+
warningElement: document.querySelector(".dataconsent-warning")
|
98
98
|
});
|
99
99
|
|
100
100
|
initDisabledIframes(manager);
|
@@ -1,3 +1,5 @@
|
|
1
|
+
/* eslint max-lines: ["error", {"max": 350, "skipBlankLines": true}] */
|
2
|
+
|
1
3
|
const COUNT_KEY = "%count%";
|
2
4
|
// How often SR announces the message in relation to maximum characters. E.g.
|
3
5
|
// if max characters is 1000, screen reader announces the remaining characters
|
@@ -36,7 +38,7 @@ export default class InputCharacterCounter {
|
|
36
38
|
// Define the closest length for the input "gaps" defined by the threshold.
|
37
39
|
if (this.maxCharacters > 10) {
|
38
40
|
if (this.maxCharacters > 100) {
|
39
|
-
this.announceThreshold = Math.floor(this.maxCharacters * SR_ANNOUNCE_THRESHOLD_RATIO
|
41
|
+
this.announceThreshold = Math.floor(this.maxCharacters * SR_ANNOUNCE_THRESHOLD_RATIO);
|
40
42
|
} else {
|
41
43
|
this.announceThreshold = 10;
|
42
44
|
}
|
@@ -78,9 +80,6 @@ export default class InputCharacterCounter {
|
|
78
80
|
}
|
79
81
|
}
|
80
82
|
|
81
|
-
this.updateInputLength();
|
82
|
-
this.previousInputLength = this.inputLength;
|
83
|
-
|
84
83
|
if (this.$target.length > 0 && (this.maxCharacters > 0 || this.minCharacters > 0)) {
|
85
84
|
// Create the screen reader target element. We don't want to constantly
|
86
85
|
// announce every change to screen reader, only occasionally.
|
@@ -89,58 +88,68 @@ export default class InputCharacterCounter {
|
|
89
88
|
);
|
90
89
|
this.$target.before(this.$srTarget);
|
91
90
|
this.$target.attr("aria-hidden", "true");
|
92
|
-
this.setDescribedBy(true);
|
93
91
|
|
94
|
-
this
|
92
|
+
this.$userInput = this.$input;
|
93
|
+
|
94
|
+
// In WYSIWYG editors (Quill) we need to find the active editor from the
|
95
|
+
// DOM node. Quill has the experimental "find" method that should work
|
96
|
+
// fine in this case
|
97
|
+
if (Quill && this.$input.parent().is(".editor")) {
|
98
|
+
// Wait until the next javascript loop so Quill editors are created
|
99
|
+
setTimeout(() => {
|
100
|
+
this.editor = Quill.find(this.$input.siblings(".editor-container")[0]);
|
101
|
+
this.$userInput = $(this.editor.root);
|
102
|
+
this.initialize();
|
103
|
+
});
|
104
|
+
} else {
|
105
|
+
this.initialize();
|
106
|
+
}
|
95
107
|
}
|
96
108
|
}
|
97
109
|
|
110
|
+
initialize() {
|
111
|
+
this.updateInputLength();
|
112
|
+
this.previousInputLength = this.inputLength;
|
113
|
+
|
114
|
+
this.bindEvents();
|
115
|
+
this.setDescribedBy(true);
|
116
|
+
}
|
117
|
+
|
98
118
|
setDescribedBy(active) {
|
99
119
|
if (!this.describeByCounter) {
|
100
120
|
return;
|
101
121
|
}
|
102
122
|
|
103
123
|
if (active) {
|
104
|
-
this.$
|
124
|
+
this.$userInput.attr("aria-describedby", this.$srTarget.attr("id"));
|
105
125
|
} else {
|
106
|
-
this.$
|
126
|
+
this.$userInput.removeAttr("aria-describedby");
|
107
127
|
}
|
108
128
|
}
|
109
129
|
|
110
130
|
bindEvents() {
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
this.updateStatus();
|
120
|
-
});
|
121
|
-
})
|
131
|
+
if (this.editor) {
|
132
|
+
this.editor.on("text-change", () => {
|
133
|
+
this.handleInput();
|
134
|
+
});
|
135
|
+
} else {
|
136
|
+
this.$userInput.on("input", () => {
|
137
|
+
this.handleInput();
|
138
|
+
});
|
122
139
|
}
|
123
|
-
|
140
|
+
|
141
|
+
this.$userInput.on("keyup", () => {
|
124
142
|
this.updateStatus();
|
125
143
|
});
|
126
|
-
this.$
|
127
|
-
this.updateInputLength();
|
128
|
-
this.checkScreenReaderUpdate();
|
129
|
-
// If the input is "described by" the character counter, some screen
|
130
|
-
// readers (NVDA) announce the status twice when it is updated. By
|
131
|
-
// removing the aria-describedby attribute while the user is typing makes
|
132
|
-
// the screen reader announce the status only once.
|
133
|
-
this.setDescribedBy(false);
|
134
|
-
});
|
135
|
-
this.$input.on("focus", () => {
|
144
|
+
this.$userInput.on("focus", () => {
|
136
145
|
this.updateScreenReaderStatus();
|
137
146
|
});
|
138
|
-
this.$
|
147
|
+
this.$userInput.on("blur", () => {
|
139
148
|
this.updateScreenReaderStatus();
|
140
149
|
this.setDescribedBy(true);
|
141
150
|
});
|
142
|
-
if (this.$
|
143
|
-
this.$
|
151
|
+
if (this.$userInput.get(0) !== null) {
|
152
|
+
this.$userInput.get(0).addEventListener("emoji.added", () => {
|
144
153
|
this.updateStatus();
|
145
154
|
});
|
146
155
|
}
|
@@ -154,7 +163,21 @@ export default class InputCharacterCounter {
|
|
154
163
|
|
155
164
|
updateInputLength() {
|
156
165
|
this.previousInputLength = this.inputLength;
|
157
|
-
|
166
|
+
if (this.editor) {
|
167
|
+
this.inputLength = this.editor.getLength();
|
168
|
+
} else {
|
169
|
+
this.inputLength = this.$input.val().length;
|
170
|
+
}
|
171
|
+
}
|
172
|
+
|
173
|
+
handleInput() {
|
174
|
+
this.updateInputLength();
|
175
|
+
this.checkScreenReaderUpdate();
|
176
|
+
// If the input is "described by" the character counter, some screen
|
177
|
+
// readers (NVDA) announce the status twice when it is updated. By
|
178
|
+
// removing the aria-describedby attribute while the user is typing makes
|
179
|
+
// the screen reader announce the status only once.
|
180
|
+
this.setDescribedBy(false);
|
158
181
|
}
|
159
182
|
|
160
183
|
/**
|
@@ -3,9 +3,17 @@ const DELAYED_VISITS = 2
|
|
3
3
|
let deferredPrompt = null
|
4
4
|
|
5
5
|
const shouldCountVisitedPages = () => sessionStorage.getItem("userChoice") !== "dismissed" && visitedPages.length < DELAYED_VISITS && !visitedPages.includes(location.pathname)
|
6
|
-
const shouldPrompt = () =>
|
6
|
+
const shouldPrompt = () => {
|
7
|
+
// Disable the application install prompt showing constantly.
|
8
|
+
if (localStorage.getItem("pwaInstallPromptSeen")) {
|
9
|
+
return false
|
10
|
+
}
|
11
|
+
|
12
|
+
return deferredPrompt && sessionStorage.getItem("userChoice") !== "dismissed" && visitedPages.length >= DELAYED_VISITS
|
13
|
+
}
|
7
14
|
|
8
15
|
window.addEventListener("beforeinstallprompt", (event) => {
|
16
|
+
event.preventDefault()
|
9
17
|
deferredPrompt = event
|
10
18
|
|
11
19
|
// allow the user browse through different locations before prompt them anything
|
@@ -24,5 +32,6 @@ window.addEventListener("click", async (event) => {
|
|
24
32
|
// store the user choice to avoid asking again in the current session
|
25
33
|
sessionStorage.setItem("userChoice", outcome)
|
26
34
|
sessionStorage.removeItem("visitedPages")
|
35
|
+
localStorage.setItem("pwaInstallPromptSeen", true)
|
27
36
|
}
|
28
37
|
});
|