decidim-core 0.29.3 → 0.29.5
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/cells/decidim/activity/show.erb +6 -6
- data/app/cells/decidim/address/show.erb +3 -3
- data/app/cells/decidim/author/show.erb +2 -4
- data/app/cells/decidim/content_blocks/participatory_space_extra_data/extra_data.erb +2 -2
- data/app/cells/decidim/content_blocks/participatory_space_main_data/title.erb +11 -2
- data/app/cells/decidim/footer_topics/show.erb +2 -2
- data/app/cells/decidim/group_admins/show.erb +3 -1
- data/app/cells/decidim/group_members/show.erb +6 -2
- data/app/cells/decidim/images_panel/show.erb +5 -2
- data/app/cells/decidim/participatory_space_dropdown_metadata/metadata.erb +4 -4
- data/app/cells/decidim/report_button/flag_modal.erb +11 -9
- data/app/cells/decidim/report_user_button/flag_modal.erb +11 -10
- data/app/cells/decidim/upload_modal/files.erb +4 -4
- data/app/cells/decidim/upload_modal_cell.rb +5 -3
- data/app/commands/decidim/amendable/accept.rb +2 -1
- data/app/commands/decidim/create_report.rb +5 -1
- data/app/commands/decidim/invite_user.rb +1 -1
- data/app/controllers/concerns/decidim/headers/browser_feature_permissions.rb +50 -0
- data/app/controllers/concerns/decidim/participatory_space_context.rb +4 -1
- data/app/controllers/decidim/amendments_controller.rb +3 -3
- data/app/controllers/decidim/application_controller.rb +1 -0
- data/app/controllers/decidim/reports_controller.rb +6 -1
- data/app/forms/decidim/omniauth_registration_form.rb +1 -1
- data/app/forms/decidim/registration_form.rb +1 -1
- data/app/helpers/decidim/amendments_helper.rb +2 -1
- data/app/helpers/decidim/filters_helper.rb +25 -0
- data/app/helpers/decidim/layout_helper.rb +6 -0
- data/app/helpers/decidim/menu_helper.rb +2 -2
- data/app/helpers/decidim/paginate_helper.rb +1 -1
- data/app/helpers/decidim/tooltip_helper.rb +4 -1
- data/app/mailers/decidim/notifications_digest_mailer.rb +7 -1
- data/app/mailers/decidim/reported_mailer.rb +17 -2
- data/app/packs/images/decidim/default-avatar.svg +1 -1
- data/app/packs/src/decidim/callout.js +13 -8
- data/app/packs/src/decidim/confirm.js +79 -59
- data/app/packs/src/decidim/datepicker/generate_datepicker.js +2 -0
- data/app/packs/src/decidim/datepicker/generate_timepicker.js +2 -0
- data/app/packs/src/decidim/direct_uploads/upload_field.js +3 -4
- data/app/packs/src/decidim/direct_uploads/upload_modal.js +8 -9
- data/app/packs/src/decidim/dropdown_menu.js +18 -0
- data/app/packs/src/decidim/editor/common/suggestion.js +11 -1
- data/app/packs/src/decidim/form_remote.js +1 -1
- data/app/packs/src/decidim/impersonation.js +1 -1
- data/app/packs/src/decidim/index.js +5 -1
- data/app/packs/src/decidim/input_character_counter.js +1 -1
- data/app/packs/src/decidim/session_timeouter.js +1 -1
- data/app/packs/src/decidim/utilities/dom.js +148 -0
- data/app/packs/stylesheets/decidim/_activity.scss +4 -4
- data/app/packs/stylesheets/decidim/_cards.scss +4 -0
- data/app/packs/stylesheets/decidim/_filters.scss +1 -1
- data/app/packs/stylesheets/decidim/_header.scss +64 -37
- data/app/packs/stylesheets/decidim/_layout.scss +2 -2
- data/app/packs/stylesheets/decidim/_modal.scss +1 -5
- data/app/packs/stylesheets/decidim/_modal_update.scss +5 -1
- data/app/permissions/decidim/default_permissions.rb +2 -0
- data/app/permissions/decidim/permissions.rb +13 -1
- data/app/presenters/decidim/notification_to_mailer_presenter.rb +7 -3
- data/app/queries/decidim/last_activity.rb +25 -0
- data/app/views/decidim/errors/internal_server_error.html.erb +1 -1
- data/app/views/decidim/errors/not_found.html.erb +1 -1
- data/app/views/decidim/messaging/conversations/_reply_form.html.erb +1 -2
- data/app/views/decidim/messaging/conversations/_start.html.erb +1 -1
- data/app/views/decidim/newsletters/unsubscribe.html.erb +16 -4
- data/app/views/decidim/reported_mailer/hidden_manually.html.erb +25 -0
- data/app/views/decidim/searches/_filters.html.erb +48 -13
- data/app/views/decidim/shared/_component_announcement.html.erb +1 -1
- data/app/views/decidim/shared/_confirm_modal.html.erb +3 -5
- data/app/views/decidim/shared/_filters.html.erb +6 -4
- data/app/views/decidim/shared/_results_per_page.html.erb +1 -1
- data/app/views/kaminari/decidim/_page.html.erb +1 -1
- data/app/views/kaminari/decidim/_paginator.html.erb +1 -1
- data/app/views/layouts/decidim/_js_configuration.html.erb +1 -0
- data/app/views/layouts/decidim/_logo.html.erb +2 -2
- data/app/views/layouts/decidim/footer/_main.html.erb +1 -1
- data/app/views/layouts/decidim/footer/_main_intro.html.erb +1 -1
- data/app/views/layouts/decidim/footer/_mini.html.erb +2 -2
- data/app/views/layouts/decidim/header/_main.html.erb +2 -2
- data/app/views/layouts/decidim/header/_main_links_desktop.html.erb +6 -0
- data/app/views/layouts/decidim/header/_main_links_dropdown.html.erb +2 -0
- data/app/views/layouts/decidim/header/_main_links_mobile_account.html.erb +1 -1
- data/app/views/layouts/decidim/header/_menu_breadcrumb_main_dropdown_desktop.html.erb +5 -11
- data/app/views/layouts/decidim/header/_menu_breadcrumb_mobile_tablet.html.erb +5 -5
- data/app/views/layouts/decidim/header/_mobile_language_choose.html.erb +1 -1
- data/config/locales/ar.yml +0 -9
- data/config/locales/bg-BG.yml +2 -2
- data/config/locales/bg.yml +0 -11
- data/config/locales/ca-IT.yml +41 -6
- data/config/locales/ca.yml +41 -6
- data/config/locales/cs.yml +33 -10
- data/config/locales/de.yml +63 -25
- data/config/locales/el.yml +0 -10
- data/config/locales/en.yml +41 -6
- data/config/locales/es-MX.yml +42 -7
- data/config/locales/es-PY.yml +42 -7
- data/config/locales/es.yml +41 -6
- data/config/locales/eu.yml +79 -43
- data/config/locales/fi-plain.yml +55 -8
- data/config/locales/fi.yml +56 -9
- data/config/locales/fr-CA.yml +43 -9
- data/config/locales/fr-LU.yml +3 -3
- data/config/locales/fr.yml +43 -9
- data/config/locales/gl.yml +0 -8
- data/config/locales/hu.yml +0 -11
- data/config/locales/id-ID.yml +0 -8
- data/config/locales/is-IS.yml +0 -6
- data/config/locales/it.yml +124 -8
- data/config/locales/ja.yml +45 -11
- data/config/locales/lb-LU.yml +2 -2
- data/config/locales/lb.yml +0 -8
- data/config/locales/lt.yml +0 -11
- data/config/locales/lv.yml +0 -8
- data/config/locales/nl.yml +0 -8
- data/config/locales/no.yml +0 -8
- data/config/locales/pl.yml +0 -11
- data/config/locales/pt-BR.yml +1 -10
- data/config/locales/pt.yml +0 -8
- data/config/locales/ro-RO.yml +0 -14
- data/config/locales/ru.yml +0 -8
- data/config/locales/sk-SK.yml +3 -3
- data/config/locales/sk.yml +2 -8
- data/config/locales/sv.yml +56 -16
- data/config/locales/tr-TR.yml +1 -8
- data/config/locales/uk.yml +0 -7
- data/config/locales/zh-CN.yml +0 -8
- data/config/locales/zh-TW.yml +0 -11
- data/db/migrate/20171212103803_create_unique_nicknames.rb +1 -1
- data/db/migrate/20180221101934_fix_nickname_index.rb +1 -1
- data/db/migrate/20180706104107_add_nickname_to_managed_users.rb +1 -1
- data/db/migrate/20181001124950_move_users_groups_to_users_table.rb +1 -1
- data/db/migrate/20190412131728_fix_user_names.rb +1 -1
- data/lib/decidim/assets/tailwind/tailwind.config.js.erb +2 -1
- data/lib/decidim/core/test/factories.rb +2 -2
- data/lib/decidim/core/test/shared_examples/announcements_examples.rb +4 -0
- data/lib/decidim/core/version.rb +1 -1
- data/lib/decidim/form_builder.rb +14 -0
- data/lib/decidim/nicknamizable.rb +6 -9
- data/lib/tasks/upgrade/decidim_fix_nickname_uniqueness.rake +1 -1
- metadata +11 -7
- /data/app/views/decidim/reported_mailer/{hide.html.erb → hidden_automatically.html.erb} +0 -0
@@ -4,13 +4,18 @@
|
|
4
4
|
* reload without this.
|
5
5
|
*/
|
6
6
|
|
7
|
-
|
8
|
-
const
|
9
|
-
if (
|
10
|
-
|
11
|
-
// The content insertion is to try to hint some of the screen readers
|
12
|
-
// that the alert content has changed and needs to be announced.
|
13
|
-
$callout.attr("tabindex", "0").focus().html(`${$callout.html()} `);
|
14
|
-
}, 500);
|
7
|
+
document.addEventListener("turbo:load", () => {
|
8
|
+
const callout = document.querySelector(".flash[role='alert']");
|
9
|
+
if (!callout) {
|
10
|
+
return;
|
15
11
|
}
|
12
|
+
|
13
|
+
setTimeout(() => {
|
14
|
+
callout.setAttribute("tabindex", "0");
|
15
|
+
callout.focus();
|
16
|
+
|
17
|
+
// The content insertion is to try to hint some of the screen readers
|
18
|
+
// that the alert content has changed and needs to be announced.
|
19
|
+
callout.innerHTML += " ";
|
20
|
+
}, 500);
|
16
21
|
});
|
@@ -5,12 +5,14 @@
|
|
5
5
|
* it to gain control over the confirm events BEFORE rails-ujs is loaded.
|
6
6
|
*/
|
7
7
|
|
8
|
-
|
8
|
+
const { Rails } = window;
|
9
9
|
|
10
10
|
class ConfirmDialog {
|
11
11
|
constructor(sourceElement) {
|
12
12
|
this.$modal = $("#confirm-modal");
|
13
|
-
|
13
|
+
if (sourceElement) {
|
14
|
+
this.$source = $(sourceElement);
|
15
|
+
}
|
14
16
|
this.$content = $("[data-confirm-modal-content]", this.$modal);
|
15
17
|
this.$buttonConfirm = $("[data-confirm-ok]", this.$modal);
|
16
18
|
this.$buttonCancel = $("[data-confirm-cancel]", this.$modal);
|
@@ -29,22 +31,37 @@ class ConfirmDialog {
|
|
29
31
|
this.$buttonConfirm.on("click", (ev) => {
|
30
32
|
ev.preventDefault();
|
31
33
|
|
32
|
-
|
33
|
-
resolve(true);
|
34
|
-
this.$source.focus();
|
34
|
+
this.close(() => resolve(true));
|
35
35
|
});
|
36
36
|
|
37
37
|
this.$buttonCancel.on("click", (ev) => {
|
38
38
|
ev.preventDefault();
|
39
39
|
|
40
|
-
|
41
|
-
resolve(false);
|
42
|
-
this.$source.focus();
|
40
|
+
this.close(() => resolve(false));
|
43
41
|
});
|
44
42
|
});
|
45
43
|
}
|
44
|
+
|
45
|
+
close(afterClose) {
|
46
|
+
window.Decidim.currentDialogs["confirm-modal"].close()
|
47
|
+
afterClose();
|
48
|
+
if (this.$source) {
|
49
|
+
this.$source.focus();
|
50
|
+
}
|
51
|
+
}
|
46
52
|
}
|
47
53
|
|
54
|
+
const runConfirm = (message, sourceElement = null) => new Promise((resolve) => {
|
55
|
+
const dialog = new ConfirmDialog(sourceElement);
|
56
|
+
dialog.confirm(message).then((answer) => {
|
57
|
+
let completed = true;
|
58
|
+
if (sourceElement) {
|
59
|
+
completed = Rails.fire(sourceElement, "confirm:complete", [answer]);
|
60
|
+
}
|
61
|
+
resolve(answer && completed);
|
62
|
+
});
|
63
|
+
});
|
64
|
+
|
48
65
|
// Override the default confirm dialog by Rails
|
49
66
|
// See:
|
50
67
|
// https://github.com/rails/rails/blob/fba1064153d8e2f4654df7762a7d3664b93e9fc8/actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee
|
@@ -64,37 +81,35 @@ const allowAction = (ev, element) => {
|
|
64
81
|
return false;
|
65
82
|
}
|
66
83
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
(
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
)
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
newEv = new origEv.constructor(origEv.type, origEv);
|
95
|
-
}
|
96
|
-
ev.target.dispatchEvent(newEv);
|
84
|
+
runConfirm(message, element).then((answer) => {
|
85
|
+
if (!answer) {
|
86
|
+
return;
|
87
|
+
}
|
88
|
+
|
89
|
+
// Allow the event to propagate normally and re-dispatch it without
|
90
|
+
// the confirm data attribute which the Rails internal method is
|
91
|
+
// checking.
|
92
|
+
$(element).data("confirm", null);
|
93
|
+
$(element).removeAttr("data-confirm");
|
94
|
+
|
95
|
+
// The submit button click events will not do anything if they are
|
96
|
+
// dispatched as is. In these cases, just submit the underlying form.
|
97
|
+
if (ev.type === "click" &&
|
98
|
+
(
|
99
|
+
$(element).is('button[type="submit"]') ||
|
100
|
+
$(element).is('input[type="submit"]')
|
101
|
+
)
|
102
|
+
) {
|
103
|
+
$(element).parents("form").submit();
|
104
|
+
} else {
|
105
|
+
let origEv = ev.originalEvent || ev;
|
106
|
+
let newEv = origEv;
|
107
|
+
if (typeof Event === "function") {
|
108
|
+
// Clone the event because otherwise some click events may not
|
109
|
+
// work properly when re-dispatched.
|
110
|
+
newEv = new origEv.constructor(origEv.type, origEv);
|
97
111
|
}
|
112
|
+
ev.target.dispatchEvent(newEv);
|
98
113
|
}
|
99
114
|
});
|
100
115
|
|
@@ -129,26 +144,31 @@ const handleDocumentEvent = (ev, matchSelectors) => {
|
|
129
144
|
});
|
130
145
|
};
|
131
146
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
147
|
+
// Note that this needs to be run **before** Rails.start()
|
148
|
+
export const initializeConfirm = () => {
|
149
|
+
document.addEventListener("click", (ev) => {
|
150
|
+
return handleDocumentEvent(ev, [
|
151
|
+
Rails.linkClickSelector,
|
152
|
+
Rails.buttonClickSelector,
|
153
|
+
Rails.formInputClickSelector
|
154
|
+
]);
|
155
|
+
});
|
156
|
+
document.addEventListener("change", (ev) => {
|
157
|
+
return handleDocumentEvent(ev, [Rails.inputChangeSelector]);
|
158
|
+
});
|
159
|
+
document.addEventListener("submit", (ev) => {
|
160
|
+
return handleDocumentEvent(ev, [Rails.formSubmitSelector]);
|
161
|
+
});
|
145
162
|
|
146
|
-
// This is needed for the confirm dialog to work with Foundation Abide.
|
147
|
-
// Abide registers its own submit click listeners since Foundation 5.6.x
|
148
|
-
// which will be handled before the document listeners above. This would
|
149
|
-
// break the custom confirm functionality when used with Foundation Abide.
|
150
|
-
document.addEventListener("DOMContentLoaded", function() {
|
151
|
-
|
152
|
-
|
163
|
+
// This is needed for the confirm dialog to work with Foundation Abide.
|
164
|
+
// Abide registers its own submit click listeners since Foundation 5.6.x
|
165
|
+
// which will be handled before the document listeners above. This would
|
166
|
+
// break the custom confirm functionality when used with Foundation Abide.
|
167
|
+
document.addEventListener("DOMContentLoaded", function() {
|
168
|
+
$(Rails.formInputClickSelector).on("click.confirm", (ev) => {
|
169
|
+
handleConfirm(ev, getMatchingEventTarget(ev, Rails.formInputClickSelector));
|
170
|
+
});
|
153
171
|
});
|
154
|
-
}
|
172
|
+
};
|
173
|
+
|
174
|
+
export default runConfirm;
|
@@ -13,11 +13,13 @@ export default function generateDatePicker(input, row, formats) {
|
|
13
13
|
const date = document.createElement("input");
|
14
14
|
date.setAttribute("id", `${input.id}_date`);
|
15
15
|
date.setAttribute("type", "text");
|
16
|
+
date.setAttribute("aria-label", input.dataset.dateLabel);
|
16
17
|
|
17
18
|
const calendar = document.createElement("button");
|
18
19
|
calendar.innerHTML = icon("calendar-line");
|
19
20
|
calendar.setAttribute("class", "datepicker__calendar-button");
|
20
21
|
calendar.setAttribute("type", "button");
|
22
|
+
calendar.setAttribute("aria-label", input.dataset.buttonDateLabel);
|
21
23
|
|
22
24
|
dateColumn.appendChild(date);
|
23
25
|
dateColumn.appendChild(calendar);
|
@@ -13,11 +13,13 @@ export default function generateTimePicker(input, row, formats) {
|
|
13
13
|
const time = document.createElement("input");
|
14
14
|
time.setAttribute("id", `${input.id}_time`);
|
15
15
|
time.setAttribute("type", "text");
|
16
|
+
time.setAttribute("aria-label", input.dataset.timeLabel);
|
16
17
|
|
17
18
|
const clock = document.createElement("button");
|
18
19
|
clock.innerHTML = icon("time-line")
|
19
20
|
clock.setAttribute("class", "datepicker__clock-button");
|
20
21
|
clock.setAttribute("type", "button");
|
22
|
+
clock.setAttribute("aria-label", input.dataset.buttonTimeLabel);
|
21
23
|
|
22
24
|
timeColumn.appendChild(time);
|
23
25
|
timeColumn.appendChild(clock);
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import UploadModal from "src/decidim/direct_uploads/upload_modal";
|
2
|
-
import { truncateFilename } from "src/decidim/direct_uploads/upload_utility";
|
3
2
|
import { escapeHtml, escapeQuotes } from "src/decidim/utilities/text";
|
4
3
|
|
5
4
|
const updateModalTitle = (modal) => {
|
@@ -30,7 +29,7 @@ const updateActiveUploads = (modal) => {
|
|
30
29
|
const [removeFiles, addFiles] = [modal.items.filter(({ removable }) => removable), modal.items.filter(({ removable }) => !removable)]
|
31
30
|
|
32
31
|
addFiles.forEach((file, ix) => {
|
33
|
-
let title =
|
32
|
+
let title = file.name
|
34
33
|
|
35
34
|
let hidden = ""
|
36
35
|
if (file.hiddenField) {
|
@@ -79,8 +78,8 @@ const updateActiveUploads = (modal) => {
|
|
79
78
|
|
80
79
|
const template = `
|
81
80
|
<div ${attachmentIdOrHiddenField} data-filename="${escapeQuotes(file.name)}" data-title="${escapeQuotes(title)}">
|
82
|
-
${(/image/).test(file.type) &&
|
83
|
-
<span>${escapeHtml(title)}
|
81
|
+
${(/image/).test(file.type) && "<div><img src=\"data:,\" role=\"presentation\" /></div>" || ""}
|
82
|
+
<span>${escapeHtml(title)}</span>
|
84
83
|
${hidden}
|
85
84
|
</div>
|
86
85
|
`
|
@@ -1,6 +1,5 @@
|
|
1
1
|
import { Uploader } from "src/decidim/direct_uploads/uploader";
|
2
2
|
import icon from "src/decidim/icon";
|
3
|
-
import { truncateFilename } from "src/decidim/direct_uploads/upload_utility";
|
4
3
|
import { escapeHtml, escapeQuotes } from "src/decidim/utilities/text";
|
5
4
|
|
6
5
|
const STATUS = {
|
@@ -42,7 +41,7 @@ export default class UploadModal {
|
|
42
41
|
|
43
42
|
this.emptyItems = this.modal.querySelector("[data-dropzone-no-items]");
|
44
43
|
this.uploadItems = this.modal.querySelector("[data-dropzone-items]");
|
45
|
-
this.input = this.dropZone.querySelector("input");
|
44
|
+
this.input = this.dropZone.querySelector("input[type=file]");
|
46
45
|
this.items = []
|
47
46
|
|
48
47
|
this.attachmentCounter = 0;
|
@@ -171,29 +170,29 @@ export default class UploadModal {
|
|
171
170
|
|
172
171
|
createUploadItem(file, errors, opts = {}) {
|
173
172
|
const okTemplate = `
|
174
|
-
<img src="data:,"
|
175
|
-
<span>${escapeHtml(
|
173
|
+
<img src="data:,", role="presentation" />
|
174
|
+
<span class="upload-modal__span">${escapeHtml(file.name)}</span>
|
176
175
|
`
|
177
176
|
|
178
177
|
const errorTemplate = `
|
179
178
|
<div>${icon("error-warning-line")}</div>
|
180
179
|
<div>
|
181
|
-
<span>${escapeHtml(
|
180
|
+
<span class="upload-modal__span">${escapeHtml(file.name)}</span>
|
182
181
|
<span>${this.locales.validation_error}</span>
|
183
182
|
<ul>${errors.map((error) => `<li>${error}</li>`).join("\n")}</ul>
|
184
183
|
</div>
|
185
184
|
`
|
186
185
|
|
187
186
|
const titleTemplate = `
|
188
|
-
<img src="data:,"
|
187
|
+
<img src="data:," role="presentation" />
|
189
188
|
<div>
|
190
189
|
<div>
|
191
190
|
<label>${this.locales.filename}</label>
|
192
|
-
<span>${escapeHtml(
|
191
|
+
<span class="upload-modal__span">${escapeHtml(file.name)}</span>
|
193
192
|
</div>
|
194
193
|
<div>
|
195
|
-
<label>${this.locales.title}</label>
|
196
|
-
<input class="sm" type="text" value="${escapeQuotes(opts.title ||
|
194
|
+
<label for="${file.name}">${this.locales.title}</label>
|
195
|
+
<input class="sm" type="text" value="${escapeQuotes(opts.title || file.name)}" id="${file.name}" />
|
197
196
|
</div>
|
198
197
|
</div>
|
199
198
|
`
|
@@ -0,0 +1,18 @@
|
|
1
|
+
// changes the value "menu" of role attribute set by a11y on div dropdown-menu-account and
|
2
|
+
// dropdown-menu-account-mobile which are inappropriate for accessibility
|
3
|
+
document.addEventListener("DOMContentLoaded", () => {
|
4
|
+
const dropdownDiv = document.querySelector("#dropdown-menu-account");
|
5
|
+
const dropdownMobileDiv = document.querySelector("#dropdown-menu-account-mobile");
|
6
|
+
if (dropdownDiv) {
|
7
|
+
setTimeout(() => {
|
8
|
+
dropdownDiv.setAttribute("role", "dialog")
|
9
|
+
dropdownMobileDiv.setAttribute("role", "dialog")
|
10
|
+
}, 300)
|
11
|
+
}
|
12
|
+
const triggerButtonMobile = document.querySelector("#dropdown-trigger-links-mobile");
|
13
|
+
if (triggerButtonMobile) {
|
14
|
+
triggerButtonMobile.addEventListener("click", () => {
|
15
|
+
dropdownMobileDiv.setAttribute("aria-modal", "true")
|
16
|
+
})
|
17
|
+
}
|
18
|
+
});
|
@@ -98,7 +98,17 @@ export const createSuggestionRenderer = (node, { itemConverter } = {}) => () =>
|
|
98
98
|
suggestionItem.textContent = label;
|
99
99
|
suggestion.append(suggestionItem);
|
100
100
|
|
101
|
-
suggestionItem.addEventListener("click", () =>
|
101
|
+
suggestionItem.addEventListener("click", (ev) => {
|
102
|
+
ev.preventDefault();
|
103
|
+
|
104
|
+
if (currentRange) {
|
105
|
+
// Increase the current range by 1 since the ENTER key would do the
|
106
|
+
// same when doing the selection through keyboard.
|
107
|
+
currentRange.to += 1;
|
108
|
+
}
|
109
|
+
|
110
|
+
selectItem(idx);
|
111
|
+
});
|
102
112
|
});
|
103
113
|
}
|
104
114
|
|
@@ -15,7 +15,7 @@ $(() => {
|
|
15
15
|
}, 1000);
|
16
16
|
|
17
17
|
// Prevent reload when page is already unloading, otherwise it may cause infinite reloads.
|
18
|
-
window.addEventListener("
|
18
|
+
window.addEventListener("pagehide", () => {
|
19
19
|
clearInterval(exitInterval);
|
20
20
|
return;
|
21
21
|
});
|
@@ -41,7 +41,6 @@ import "src/decidim/vizzs"
|
|
41
41
|
import "src/decidim/responsive_horizontal_tabs"
|
42
42
|
import "src/decidim/security/selfxss_warning"
|
43
43
|
import "src/decidim/session_timeouter"
|
44
|
-
import "src/decidim/confirm"
|
45
44
|
import "src/decidim/results_listing"
|
46
45
|
import "src/decidim/impersonation"
|
47
46
|
import "src/decidim/gallery"
|
@@ -51,8 +50,10 @@ import "src/decidim/abide_form_validator_fixer"
|
|
51
50
|
import "src/decidim/sw"
|
52
51
|
import "src/decidim/sticky_header"
|
53
52
|
import "src/decidim/attachments"
|
53
|
+
import "src/decidim/dropdown_menu"
|
54
54
|
|
55
55
|
// local deps that require initialization
|
56
|
+
import ConfirmDialog, { initializeConfirm } from "src/decidim/confirm"
|
56
57
|
import formDatePicker from "src/decidim/datepicker/form_datepicker"
|
57
58
|
import Configuration from "src/decidim/configuration"
|
58
59
|
import ExternalLink from "src/decidim/external_link"
|
@@ -91,6 +92,7 @@ window.Decidim = window.Decidim || {
|
|
91
92
|
addInputEmoji,
|
92
93
|
EmojiButton,
|
93
94
|
Dialogs,
|
95
|
+
ConfirmDialog,
|
94
96
|
announceForScreenReader
|
95
97
|
};
|
96
98
|
|
@@ -123,6 +125,8 @@ window.initFoundation = (element) => {
|
|
123
125
|
});
|
124
126
|
};
|
125
127
|
|
128
|
+
// Confirm initialization needs to happen before Rails.start()
|
129
|
+
initializeConfirm();
|
126
130
|
Rails.start()
|
127
131
|
|
128
132
|
/**
|
@@ -93,7 +93,7 @@ export default class InputCharacterCounter {
|
|
93
93
|
this.$srTarget = $(`#${screenReaderId}`);
|
94
94
|
if (!this.$srTarget.length) {
|
95
95
|
this.$srTarget = $(
|
96
|
-
`<span role="status" id="${screenReaderId}" class="sr-only remaining-character-count-sr" />`
|
96
|
+
`<span role="status" id="${screenReaderId}" class="sr-only remaining-character-count-sr" aria-hidden="true"/>`
|
97
97
|
);
|
98
98
|
this.$target.before(this.$srTarget);
|
99
99
|
}
|
@@ -0,0 +1,148 @@
|
|
1
|
+
import confirmAction from "src/decidim/confirm"
|
2
|
+
import { getMessages } from "src/decidim/i18n"
|
3
|
+
|
4
|
+
const { Rails } = window;
|
5
|
+
|
6
|
+
const createUnloadPreventer = () => {
|
7
|
+
const preventUnloadConditions = [];
|
8
|
+
|
9
|
+
const confirmMessage = getMessages("confirmUnload") || "Are you sure you want to leave this page?";
|
10
|
+
|
11
|
+
const canUnload = (event) => !preventUnloadConditions.some((condition) => condition(event));
|
12
|
+
|
13
|
+
// TLDR:
|
14
|
+
// The beforeunload event does not work during tests due to the deprecation of
|
15
|
+
// the unload event and ChromeDriver automatically accepting these dialogs.
|
16
|
+
// ---
|
17
|
+
//
|
18
|
+
// Even when there are custom listeners on links and forms, the beforeunload
|
19
|
+
// event is to ensure that the user does not accidentally reload the page or
|
20
|
+
// close the browser or the tab. Note that this does not work during the tests
|
21
|
+
// with ChromeDriver due to the deprecation of the unload event and
|
22
|
+
// ChromeDriver automatically accepting these dialogs. For the time being,
|
23
|
+
// this should work when a real user interacts with the browser along with the
|
24
|
+
// "Permissions-Policy" header set by the backend. For more information about
|
25
|
+
// the header, see Decidim::Headers::BrowserFeaturePermissions).
|
26
|
+
const unloadListener = (event) => {
|
27
|
+
if (canUnload(event)) {
|
28
|
+
return;
|
29
|
+
}
|
30
|
+
|
31
|
+
// According to:
|
32
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
|
33
|
+
//
|
34
|
+
// > [...] best practice is to trigger the dialog by invoking
|
35
|
+
// > preventDefault() on the event object, while also setting returnValue to
|
36
|
+
// > support legacy cases.
|
37
|
+
event.preventDefault();
|
38
|
+
event.returnValue = true;
|
39
|
+
};
|
40
|
+
|
41
|
+
// The beforeunload event listener has to be registered AFTER a user
|
42
|
+
// interaction which is why it is wrapped around the next click event that
|
43
|
+
// happens after the first unload listener was registered. Otherwise it might
|
44
|
+
// not work due to the deprecation of the unload APIs in Chromium based
|
45
|
+
// browsers and possibly in the web standards in the future.
|
46
|
+
//
|
47
|
+
// According to:
|
48
|
+
// https://developer.chrome.com/docs/web-platform/page-lifecycle-api#the_beforeunload_event
|
49
|
+
//
|
50
|
+
// > Never add a beforeunload listener unconditionally or use it as an
|
51
|
+
// > end-of-session signal. Only add it when a user has unsaved work, and
|
52
|
+
// > remove it as soon as that work has been saved.
|
53
|
+
const registerBeforeUnload = () => {
|
54
|
+
window.removeEventListener("click", registerBeforeUnload);
|
55
|
+
window.addEventListener("beforeunload", unloadListener);
|
56
|
+
};
|
57
|
+
|
58
|
+
const disableBeforeUnload = () => {
|
59
|
+
window.removeEventListener("click", registerBeforeUnload);
|
60
|
+
window.removeEventListener("beforeunload", unloadListener);
|
61
|
+
};
|
62
|
+
|
63
|
+
const linkClickListener = (ev) => {
|
64
|
+
const link = ev.target?.closest("a");
|
65
|
+
if (!link) {
|
66
|
+
return;
|
67
|
+
}
|
68
|
+
|
69
|
+
if (canUnload(ev)) {
|
70
|
+
disableBeforeUnload();
|
71
|
+
document.removeEventListener("click", linkClickListener);
|
72
|
+
return;
|
73
|
+
}
|
74
|
+
|
75
|
+
window.exitUrl = link.href;
|
76
|
+
|
77
|
+
ev.preventDefault();
|
78
|
+
ev.stopPropagation();
|
79
|
+
|
80
|
+
confirmAction(confirmMessage, link).then((answer) => {
|
81
|
+
if (!answer) {
|
82
|
+
return;
|
83
|
+
}
|
84
|
+
|
85
|
+
disableBeforeUnload();
|
86
|
+
document.removeEventListener("click", linkClickListener);
|
87
|
+
link.click();
|
88
|
+
});
|
89
|
+
};
|
90
|
+
|
91
|
+
const formSubmitListener = (ev) => {
|
92
|
+
const source = ev.target?.closest("form");
|
93
|
+
if (!source) {
|
94
|
+
return;
|
95
|
+
}
|
96
|
+
|
97
|
+
if (canUnload(ev)) {
|
98
|
+
disableBeforeUnload();
|
99
|
+
document.removeEventListener("submit", formSubmitListener);
|
100
|
+
return;
|
101
|
+
}
|
102
|
+
|
103
|
+
const button = source.closest(Rails.formSubmitSelector);
|
104
|
+
if (!button) {
|
105
|
+
return;
|
106
|
+
}
|
107
|
+
|
108
|
+
ev.preventDefault();
|
109
|
+
ev.stopImmediatePropagation();
|
110
|
+
ev.stopPropagation();
|
111
|
+
|
112
|
+
confirmAction(confirmMessage, button).then((answer) => {
|
113
|
+
if (!answer) {
|
114
|
+
return;
|
115
|
+
}
|
116
|
+
|
117
|
+
disableBeforeUnload();
|
118
|
+
document.removeEventListener("submit", formSubmitListener);
|
119
|
+
source.submit();
|
120
|
+
});
|
121
|
+
};
|
122
|
+
|
123
|
+
const registerPreventUnloadListeners = () => {
|
124
|
+
window.addEventListener("click", registerBeforeUnload);
|
125
|
+
document.addEventListener("click", linkClickListener);
|
126
|
+
document.addEventListener("submit", formSubmitListener);
|
127
|
+
};
|
128
|
+
|
129
|
+
return {
|
130
|
+
addPreventCondition: (condition) => {
|
131
|
+
if (typeof condition !== "function") {
|
132
|
+
return;
|
133
|
+
}
|
134
|
+
|
135
|
+
if (preventUnloadConditions.length < 1) {
|
136
|
+
// The unload listeners are global, so only the first call to this
|
137
|
+
// function should result to registering these listeners.
|
138
|
+
registerPreventUnloadListeners();
|
139
|
+
}
|
140
|
+
|
141
|
+
preventUnloadConditions.push(condition);
|
142
|
+
}
|
143
|
+
};
|
144
|
+
};
|
145
|
+
|
146
|
+
const unloadPreventer = createUnloadPreventer();
|
147
|
+
|
148
|
+
export const preventUnload = (condition) => unloadPreventer.addPreventCondition(condition);
|
@@ -12,12 +12,12 @@
|
|
12
12
|
&__content {
|
13
13
|
@apply col-span-2 md:col-span-1 order-first md:order-none space-y-2;
|
14
14
|
|
15
|
-
>
|
16
|
-
|
17
|
-
@apply text-gray-2 text-sm;
|
15
|
+
> div:first-child {
|
16
|
+
p {
|
17
|
+
@apply text-gray-2 text-sm inline-block;
|
18
18
|
|
19
19
|
svg {
|
20
|
-
@apply text-gray fill-current;
|
20
|
+
@apply text-gray fill-current inline;
|
21
21
|
}
|
22
22
|
}
|
23
23
|
|