decidim-core 0.32.1 → 0.32.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/cells/decidim/content_blocks/participatory_space_main_data/title.erb +1 -14
- data/app/cells/decidim/following_cell.rb +1 -1
- data/app/cells/decidim/member/show.erb +1 -1
- data/app/cells/decidim/nav_links/show.erb +3 -1
- data/app/cells/decidim/share_text_widget/modal.erb +1 -0
- data/app/cells/decidim/share_widget/modal.erb +1 -0
- data/app/cells/decidim/share_widget/qr_code_modal.erb +2 -4
- data/app/commands/decidim/create_omniauth_registration.rb +17 -2
- data/app/controllers/concerns/decidim/devise_authentication_methods.rb +4 -0
- data/app/controllers/concerns/decidim/devise_controllers.rb +2 -0
- data/app/controllers/concerns/decidim/disable_redirection_to_external_host.rb +2 -0
- data/app/controllers/concerns/decidim/filter_resource.rb +2 -0
- data/app/controllers/concerns/decidim/impersonate_users.rb +2 -2
- data/app/controllers/concerns/decidim/locale_switcher.rb +12 -10
- data/app/controllers/concerns/decidim/needs_permission.rb +22 -10
- data/app/controllers/concerns/decidim/onboarding_action_methods.rb +2 -0
- data/app/controllers/concerns/decidim/paginable.rb +7 -3
- data/app/controllers/concerns/decidim/payload_info.rb +2 -0
- data/app/controllers/concerns/decidim/resource_versions_concern.rb +14 -0
- data/app/controllers/decidim/application_controller.rb +0 -6
- data/app/controllers/decidim/authorization_modals_controller.rb +2 -0
- data/app/controllers/decidim/components/base_controller.rb +2 -0
- data/app/controllers/decidim/devise/passwords_controller.rb +8 -0
- data/app/controllers/decidim/favicon_controller.rb +9 -0
- data/app/controllers/decidim/free_resource_authorization_modals_controller.rb +2 -0
- data/app/controllers/decidim/gamification/badges_controller.rb +2 -0
- data/app/controllers/decidim/homepage_controller.rb +4 -1
- data/app/controllers/decidim/likes_controller.rb +1 -1
- data/app/controllers/decidim/links_controller.rb +2 -0
- data/app/controllers/decidim/manifests_controller.rb +2 -0
- data/app/controllers/decidim/messaging/conversations_controller.rb +10 -4
- data/app/controllers/decidim/newsletters_controller.rb +4 -0
- data/app/controllers/decidim/notifications_subscriptions_controller.rb +4 -0
- data/app/controllers/decidim/offline_controller.rb +5 -1
- data/app/controllers/decidim/open_data_controller.rb +5 -1
- data/app/controllers/decidim/pages_controller.rb +31 -3
- data/app/controllers/decidim/private_downloads_controller.rb +2 -0
- data/app/controllers/decidim/profiles_controller.rb +19 -1
- data/app/controllers/decidim/qr_controller.rb +3 -1
- data/app/controllers/decidim/resource_autocomplete_controller.rb +2 -0
- data/app/controllers/decidim/searches_controller.rb +2 -0
- data/app/controllers/decidim/short_links_controller.rb +4 -0
- data/app/controllers/decidim/static_map_controller.rb +2 -0
- data/app/controllers/decidim/timeouts_controller.rb +4 -0
- data/app/controllers/decidim/tos_controller.rb +2 -0
- data/app/controllers/decidim/upload_validations_controller.rb +4 -2
- data/app/controllers/decidim/user_activities_controller.rb +3 -0
- data/app/forms/decidim/upload_validation_form.rb +16 -10
- data/app/helpers/concerns/decidim/user_role_checker.rb +35 -10
- data/app/helpers/decidim/decidim_form_helper.rb +1 -1
- data/app/helpers/decidim/sanitize_helper.rb +11 -1
- data/app/jobs/decidim/application_job.rb +4 -0
- data/app/models/decidim/authorization.rb +24 -0
- data/app/models/decidim/oauth_application.rb +1 -0
- data/app/models/decidim/user.rb +1 -0
- data/app/models/decidim/user_base_entity.rb +1 -1
- data/app/packs/src/decidim/callout.js +10 -0
- data/app/packs/src/decidim/callout.test.js +32 -0
- data/app/packs/src/decidim/controllers/accordion/accordion.test.js +64 -0
- data/app/packs/src/decidim/controllers/accordion/controller.js +43 -0
- data/app/packs/src/decidim/controllers/autofocus/controller.js +30 -0
- data/app/packs/src/decidim/controllers/language_change/controller.js +21 -8
- data/app/packs/src/decidim/controllers/language_change/language_change.test.js +0 -1
- data/app/packs/src/decidim/controllers/mention/controller.js +32 -3
- data/app/packs/src/decidim/controllers/mention/input_mentions.test.js +8 -5
- data/app/packs/src/decidim/controllers/multiple_mentions/input_multiple_mentions.test.js +1 -1
- data/app/packs/src/decidim/datepicker/datepicker_functions.js +41 -25
- data/app/packs/src/decidim/datepicker/generate_datepicker.js +4 -4
- data/app/packs/src/decidim/datepicker/test/datepicker_functions_adjust_picker_position.test.js +64 -0
- data/app/packs/src/decidim/datepicker/test/time.test.js +39 -24
- data/app/packs/src/decidim/editor/common/suggestion.js +32 -22
- data/app/packs/src/decidim/editor/extensions/mention/index.js +14 -3
- data/app/packs/src/decidim/editor/index.js +4 -0
- data/app/packs/src/decidim/editor/test/extensions/mention.test.js +32 -9
- data/app/packs/src/decidim/refactor/implementation/autofocus.js +22 -0
- data/app/packs/src/decidim/refactor/implementation/autofocus.test.js +171 -0
- data/app/packs/src/decidim/utilities/dom.js +2 -2
- data/app/packs/stylesheets/decidim/_content_blocks.scss +4 -10
- data/app/packs/stylesheets/decidim/_conversations.scss +0 -8
- data/app/packs/stylesheets/decidim/_modal.scss +16 -8
- data/app/packs/stylesheets/decidim/_modal_share.scss +0 -22
- data/app/packs/stylesheets/decidim/_participatory_spaces.scss +11 -1
- data/app/packs/stylesheets/decidim/_rich_text.scss +12 -9
- data/app/packs/stylesheets/decidim/_typography.scss +5 -2
- data/app/packs/stylesheets/decidim/application.scss +1 -0
- data/app/packs/stylesheets/decidim/legacy/email.scss +0 -5
- data/app/permissions/decidim/permissions.rb +20 -0
- data/app/presenters/decidim/admin_log/component_presenter.rb +1 -1
- data/app/presenters/decidim/log/base_presenter.rb +10 -1
- data/app/presenters/decidim/log/user_presenter.rb +2 -2
- data/app/presenters/decidim/menu_item_presenter.rb +1 -5
- data/app/presenters/decidim/resource_presenter.rb +11 -0
- data/app/resolvers/decidim/meta_image_url_resolver.rb +7 -1
- data/app/serializers/decidim/exporters/open_data_blocked_user_serializer.rb +11 -2
- data/app/serializers/decidim/exporters/open_data_moderation_serializer.rb +25 -1
- data/app/services/decidim/svg_content_validator.rb +554 -0
- data/app/uploaders/decidim/application_uploader.rb +2 -2
- data/app/validators/uploader_image_content_validator.rb +276 -0
- data/app/views/decidim/application/_radio_accordion.html.erb +1 -5
- data/app/views/decidim/messaging/conversations/_add_conversation_users.html.erb +1 -1
- data/app/views/decidim/messaging/conversations/create.js.erb +1 -3
- data/app/views/decidim/shared/_login_modal.html.erb +12 -14
- data/app/views/layouts/decidim/_mailer_logo.html.erb +1 -1
- data/app/views/layouts/decidim/header/_menu_breadcrumb_desktop.html.erb +3 -2
- data/config/initializers/rack_attack.rb +10 -0
- data/config/locales/bg.yml +0 -1
- data/config/locales/ca-IT.yml +3 -1
- data/config/locales/ca.yml +10 -6
- data/config/locales/cs.yml +15 -1
- data/config/locales/de.yml +99 -3
- data/config/locales/en.yml +5 -1
- data/config/locales/es-MX.yml +5 -1
- data/config/locales/es-PY.yml +5 -1
- data/config/locales/es.yml +9 -5
- data/config/locales/eu.yml +161 -160
- data/config/locales/fi-plain.yml +3 -1
- data/config/locales/fi.yml +3 -1
- data/config/locales/fr-CA.yml +5 -1
- data/config/locales/fr.yml +5 -1
- data/config/locales/gl.yml +1485 -33
- data/config/locales/hu.yml +0 -1
- data/config/locales/ja.yml +3 -1
- data/config/locales/pl.yml +0 -1
- data/config/locales/pt-BR.yml +0 -1
- data/config/locales/ro-RO.yml +0 -1
- data/config/locales/sk.yml +2 -1
- data/config/locales/sv.yml +68 -12
- data/config/locales/tr-TR.yml +0 -2
- data/config/routes.rb +11 -2
- data/db/migrate/20260909000000_add_failed_attempts_and_locked_at_to_decidim_authorizations.rb +8 -0
- data/decidim-core.gemspec +1 -1
- data/lib/decidim/api/functions/user_entity_list.rb +1 -0
- data/lib/decidim/api/types/user_moderation_type.rb +2 -2
- data/lib/decidim/content_parsers/user_parser.rb +1 -1
- data/lib/decidim/content_parsers.rb +0 -1
- data/lib/decidim/content_renderers/mention_resource_renderer.rb +18 -4
- data/lib/decidim/content_renderers/user_renderer.rb +15 -4
- data/lib/decidim/core/test/shared_examples/participatory_space_members_page_examples.rb +77 -0
- data/lib/decidim/core/test/shared_examples/participatory_space_members_shared_examples.rb +58 -0
- data/lib/decidim/core/test/shared_examples/versions_controller_examples.rb +23 -1
- data/lib/decidim/core/version.rb +1 -1
- data/lib/decidim/core.rb +9 -0
- data/lib/decidim/form_builder.rb +9 -1
- data/lib/decidim/has_upload_validations.rb +1 -0
- data/lib/decidim/legacy_form_builder.rb +4 -0
- metadata +12 -7
- data/lib/decidim/content_parsers/inline_images_parser.rb +0 -68
- data/lib/tasks/upgrade/decidim_active_storage_migration_tasks.rake +0 -79
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* global jest, global */
|
|
2
2
|
|
|
3
|
-
import { createBasicEditor, updateContent } from "src/decidim/editor/test/helpers";
|
|
3
|
+
import { createBasicEditor, updateContent, sleep } from "src/decidim/editor/test/helpers";
|
|
4
4
|
|
|
5
5
|
import Mention from "src/decidim/editor/extensions/mention";
|
|
6
6
|
|
|
@@ -87,8 +87,11 @@ describe("Mention", () => {
|
|
|
87
87
|
const items = suggestions.querySelectorAll(".editor-suggestions-item");
|
|
88
88
|
expect(items.length).toEqual(expectedTags.length);
|
|
89
89
|
for (const item of items) {
|
|
90
|
-
expect(expectedTags.includes(item.textContent)).toBe(true);
|
|
90
|
+
expect(expectedTags.includes(item.querySelector(".editor-suggestions-item-label").textContent)).toBe(true);
|
|
91
91
|
}
|
|
92
|
+
|
|
93
|
+
expect(items[0].querySelector(".editor-suggestions-item-avatar")).toBeInstanceOf(HTMLImageElement);
|
|
94
|
+
expect(items[0].querySelector(".editor-suggestions-item-avatar").getAttribute("src")).toBe("/avatars/joannadoe.jpg");
|
|
92
95
|
});
|
|
93
96
|
|
|
94
97
|
it("does not display the suggestions when less than two characters are entered", async () => {
|
|
@@ -97,9 +100,12 @@ describe("Mention", () => {
|
|
|
97
100
|
|
|
98
101
|
const suggestions = document.querySelector(".editor-suggestions");
|
|
99
102
|
expect(suggestions).toBeInstanceOf(HTMLDivElement);
|
|
100
|
-
|
|
101
|
-
expect(
|
|
102
|
-
expect(
|
|
103
|
+
const items = suggestions.querySelectorAll(".editor-suggestions-item");
|
|
104
|
+
expect(items.length).toBe(1);
|
|
105
|
+
expect(items[0].textContent).toBe("Type to search participants");
|
|
106
|
+
expect(items[0].disabled).toBe(true);
|
|
107
|
+
expect(suggestions.classList.contains("hidden")).toBe(false);
|
|
108
|
+
expect(suggestions.classList.contains("hide")).toBe(false);
|
|
103
109
|
});
|
|
104
110
|
|
|
105
111
|
it("allows selecting a mention from the list by clicking it", async () => {
|
|
@@ -110,10 +116,10 @@ describe("Mention", () => {
|
|
|
110
116
|
suggestions.querySelector(".editor-suggestions-item").click();
|
|
111
117
|
|
|
112
118
|
expect(editorElement.innerHTML).toEqual(
|
|
113
|
-
'<p><span data-suggestion="mention" data-id="@johndoe" data-label="@johndoe
|
|
119
|
+
'<p><span data-suggestion="mention" data-id="@johndoe" data-label="@johndoe">@johndoe</span> </p>'
|
|
114
120
|
);
|
|
115
121
|
expect(normalizeHTML(editor.getHTML())).toEqual(
|
|
116
|
-
'<p><span data-type="mention" data-id="@johndoe" data-label="@johndoe
|
|
122
|
+
'<p><span data-type="mention" data-id="@johndoe" data-label="@johndoe">@johndoe</span> </p>'
|
|
117
123
|
);
|
|
118
124
|
});
|
|
119
125
|
|
|
@@ -124,10 +130,27 @@ describe("Mention", () => {
|
|
|
124
130
|
editorElement.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" }));
|
|
125
131
|
|
|
126
132
|
expect(editorElement.innerHTML).toEqual(
|
|
127
|
-
'<p><span data-suggestion="mention" data-id="@johndoe" data-label="@johndoe
|
|
133
|
+
'<p><span data-suggestion="mention" data-id="@johndoe" data-label="@johndoe">@johndoe</span> </p>'
|
|
128
134
|
);
|
|
129
135
|
expect(normalizeHTML(editor.getHTML())).toEqual(
|
|
130
|
-
'<p><span data-type="mention" data-id="@johndoe" data-label="@johndoe
|
|
136
|
+
'<p><span data-type="mention" data-id="@johndoe" data-label="@johndoe">@johndoe</span> </p>'
|
|
131
137
|
);
|
|
132
138
|
});
|
|
139
|
+
|
|
140
|
+
it("allows selecting a mention when content changes after triggering", async () => {
|
|
141
|
+
editorElement.focus();
|
|
142
|
+
await updateContent(editorElement, "@joh", editor);
|
|
143
|
+
|
|
144
|
+
const suggestions = document.querySelector(".editor-suggestions");
|
|
145
|
+
expect(suggestions).toBeInstanceOf(HTMLDivElement);
|
|
146
|
+
|
|
147
|
+
editor.commands.setContent("<p>Some new content @joh</p>");
|
|
148
|
+
await sleep(50);
|
|
149
|
+
|
|
150
|
+
expect(() => {
|
|
151
|
+
suggestions.querySelector(".editor-suggestions-item").click();
|
|
152
|
+
}).not.toThrow();
|
|
153
|
+
|
|
154
|
+
expect(normalizeHTML(editor.getHTML())).toContain('data-type="mention"');
|
|
155
|
+
});
|
|
133
156
|
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This focuses an element
|
|
3
|
+
* @param {HTMLElement} element The container element to search for a focusable input or editor.
|
|
4
|
+
* @returns {void} Nothing
|
|
5
|
+
*/
|
|
6
|
+
export default function autofocus(element) {
|
|
7
|
+
let node = element.querySelector(".editor .ProseMirror, input[type='text'], textarea")
|
|
8
|
+
|
|
9
|
+
if (!node) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
node.dispatchEvent(new CustomEvent("move-cursor-to-end", { bubbles: true }));
|
|
14
|
+
|
|
15
|
+
if (typeof node.focus === "function") {
|
|
16
|
+
node.focus();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let value = node.value;
|
|
20
|
+
node.value = "";
|
|
21
|
+
node.value = value;
|
|
22
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/* global jest */
|
|
2
|
+
|
|
3
|
+
import autofocus from "src/decidim/refactor/implementation/autofocus"
|
|
4
|
+
|
|
5
|
+
describe("autofocus", () => {
|
|
6
|
+
describe("when ProseMirror editor is present", () => {
|
|
7
|
+
it("dispatches move-cursor-to-end event on ProseMirror", () => {
|
|
8
|
+
const container = document.createElement("div");
|
|
9
|
+
const editor = document.createElement("div");
|
|
10
|
+
editor.className = "editor";
|
|
11
|
+
const prosemirror = document.createElement("div");
|
|
12
|
+
prosemirror.className = "ProseMirror";
|
|
13
|
+
editor.appendChild(prosemirror);
|
|
14
|
+
container.appendChild(editor);
|
|
15
|
+
document.body.appendChild(container);
|
|
16
|
+
|
|
17
|
+
const dispatchSpy = jest.spyOn(prosemirror, "dispatchEvent");
|
|
18
|
+
|
|
19
|
+
autofocus(container);
|
|
20
|
+
|
|
21
|
+
expect(dispatchSpy).toHaveBeenCalledWith(expect.objectContaining({
|
|
22
|
+
type: "move-cursor-to-end",
|
|
23
|
+
bubbles: true
|
|
24
|
+
}));
|
|
25
|
+
dispatchSpy.mockRestore();
|
|
26
|
+
container.remove();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("calls focus on ProseMirror element", () => {
|
|
30
|
+
const container = document.createElement("div");
|
|
31
|
+
const editor = document.createElement("div");
|
|
32
|
+
editor.className = "editor";
|
|
33
|
+
const prosemirror = document.createElement("div");
|
|
34
|
+
prosemirror.className = "ProseMirror";
|
|
35
|
+
prosemirror.contentEditable = true;
|
|
36
|
+
const focusSpy = jest.spyOn(prosemirror, "focus");
|
|
37
|
+
editor.appendChild(prosemirror);
|
|
38
|
+
container.appendChild(editor);
|
|
39
|
+
document.body.appendChild(container);
|
|
40
|
+
|
|
41
|
+
autofocus(container);
|
|
42
|
+
|
|
43
|
+
expect(focusSpy).toHaveBeenCalled();
|
|
44
|
+
focusSpy.mockRestore();
|
|
45
|
+
container.remove();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("when input[type='text'] is present", () => {
|
|
50
|
+
it("focuses on the input element", () => {
|
|
51
|
+
const container = document.createElement("div");
|
|
52
|
+
const input = document.createElement("input");
|
|
53
|
+
input.type = "text";
|
|
54
|
+
input.value = "test value";
|
|
55
|
+
const focusSpy = jest.spyOn(input, "focus");
|
|
56
|
+
container.appendChild(input);
|
|
57
|
+
document.body.appendChild(container);
|
|
58
|
+
|
|
59
|
+
autofocus(container);
|
|
60
|
+
|
|
61
|
+
expect(focusSpy).toHaveBeenCalled();
|
|
62
|
+
expect(input.value).toBe("test value");
|
|
63
|
+
focusSpy.mockRestore();
|
|
64
|
+
container.remove();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("clears and restores the input value", () => {
|
|
68
|
+
const container = document.createElement("div");
|
|
69
|
+
const input = document.createElement("input");
|
|
70
|
+
input.type = "text";
|
|
71
|
+
input.value = "original value";
|
|
72
|
+
container.appendChild(input);
|
|
73
|
+
document.body.appendChild(container);
|
|
74
|
+
|
|
75
|
+
autofocus(container);
|
|
76
|
+
|
|
77
|
+
expect(input.value).toBe("original value");
|
|
78
|
+
container.remove();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("when textarea is present", () => {
|
|
83
|
+
it("focuses on the textarea element", () => {
|
|
84
|
+
const container = document.createElement("div");
|
|
85
|
+
const textarea = document.createElement("textarea");
|
|
86
|
+
textarea.value = "textarea content";
|
|
87
|
+
const focusSpy = jest.spyOn(textarea, "focus");
|
|
88
|
+
container.appendChild(textarea);
|
|
89
|
+
document.body.appendChild(container);
|
|
90
|
+
|
|
91
|
+
autofocus(container);
|
|
92
|
+
|
|
93
|
+
expect(focusSpy).toHaveBeenCalled();
|
|
94
|
+
expect(textarea.value).toBe("textarea content");
|
|
95
|
+
focusSpy.mockRestore();
|
|
96
|
+
container.remove();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("clears and restores the textarea value", () => {
|
|
100
|
+
const container = document.createElement("div");
|
|
101
|
+
const textarea = document.createElement("textarea");
|
|
102
|
+
textarea.value = "original content";
|
|
103
|
+
container.appendChild(textarea);
|
|
104
|
+
document.body.appendChild(container);
|
|
105
|
+
|
|
106
|
+
autofocus(container);
|
|
107
|
+
|
|
108
|
+
expect(textarea.value).toBe("original content");
|
|
109
|
+
container.remove();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe("when no focusable element is present", () => {
|
|
114
|
+
it("does nothing and does not throw", () => {
|
|
115
|
+
const container = document.createElement("div");
|
|
116
|
+
container.innerHTML = "<p>No focusable element here</p>";
|
|
117
|
+
document.body.appendChild(container);
|
|
118
|
+
|
|
119
|
+
expect(() => autofocus(container)).not.toThrow();
|
|
120
|
+
container.remove();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("priority of elements", () => {
|
|
125
|
+
it("prefers ProseMirror over input", () => {
|
|
126
|
+
const container = document.createElement("div");
|
|
127
|
+
const editor = document.createElement("div");
|
|
128
|
+
editor.className = "editor";
|
|
129
|
+
const prosemirror = document.createElement("div");
|
|
130
|
+
prosemirror.className = "ProseMirror";
|
|
131
|
+
editor.appendChild(prosemirror);
|
|
132
|
+
const input = document.createElement("input");
|
|
133
|
+
input.type = "text";
|
|
134
|
+
editor.appendChild(input);
|
|
135
|
+
container.appendChild(editor);
|
|
136
|
+
document.body.appendChild(container);
|
|
137
|
+
|
|
138
|
+
const prosemirrorDispatchSpy = jest.spyOn(prosemirror, "dispatchEvent");
|
|
139
|
+
const inputFocusSpy = jest.spyOn(input, "focus");
|
|
140
|
+
|
|
141
|
+
autofocus(container);
|
|
142
|
+
|
|
143
|
+
expect(prosemirrorDispatchSpy).toHaveBeenCalled();
|
|
144
|
+
expect(inputFocusSpy).not.toHaveBeenCalled();
|
|
145
|
+
prosemirrorDispatchSpy.mockRestore();
|
|
146
|
+
inputFocusSpy.mockRestore();
|
|
147
|
+
container.remove();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("prefers input over textarea", () => {
|
|
151
|
+
const container = document.createElement("div");
|
|
152
|
+
const input = document.createElement("input");
|
|
153
|
+
input.type = "text";
|
|
154
|
+
const textarea = document.createElement("textarea");
|
|
155
|
+
container.appendChild(input);
|
|
156
|
+
container.appendChild(textarea);
|
|
157
|
+
document.body.appendChild(container);
|
|
158
|
+
|
|
159
|
+
const inputFocusSpy = jest.spyOn(input, "focus");
|
|
160
|
+
const textareaFocusSpy = jest.spyOn(textarea, "focus");
|
|
161
|
+
|
|
162
|
+
autofocus(container);
|
|
163
|
+
|
|
164
|
+
expect(inputFocusSpy).toHaveBeenCalled();
|
|
165
|
+
expect(textareaFocusSpy).not.toHaveBeenCalled();
|
|
166
|
+
inputFocusSpy.mockRestore();
|
|
167
|
+
textareaFocusSpy.mockRestore();
|
|
168
|
+
container.remove();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
});
|
|
@@ -77,7 +77,7 @@ const createUnloadPreventer = () => {
|
|
|
77
77
|
ev.preventDefault();
|
|
78
78
|
ev.stopPropagation();
|
|
79
79
|
|
|
80
|
-
confirmAction(confirmMessage, link).then((answer) => {
|
|
80
|
+
confirmAction(confirmMessage, link, { iconName: "alert-line" }).then((answer) => {
|
|
81
81
|
if (!answer) {
|
|
82
82
|
return;
|
|
83
83
|
}
|
|
@@ -109,7 +109,7 @@ const createUnloadPreventer = () => {
|
|
|
109
109
|
ev.stopImmediatePropagation();
|
|
110
110
|
ev.stopPropagation();
|
|
111
111
|
|
|
112
|
-
confirmAction(confirmMessage, button).then((answer) => {
|
|
112
|
+
confirmAction(confirmMessage, button, { iconName: "alert-line" }).then((answer) => {
|
|
113
113
|
if (!answer) {
|
|
114
114
|
return;
|
|
115
115
|
}
|
|
@@ -13,18 +13,12 @@
|
|
|
13
13
|
@apply block max-h-40 overflow-hidden relative first:[&_*]:mt-0 before:content-[''] before:absolute before:inset-0 before:h-full before:w-full before:bg-gradient-to-b before:from-transparent before:to-white after:content-[''] after:absolute after:inset-0 after:h-full after:w-full after:bg-gradient-to-b after:from-transparent after:to-white;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
[aria-expanded
|
|
17
|
-
|
|
18
|
-
[aria-expanded="true"] > span:first-of-type,
|
|
19
|
-
[aria-expanded="true"] > svg:first-of-type {
|
|
20
|
-
@apply hidden;
|
|
16
|
+
[aria-expanded] > svg {
|
|
17
|
+
@apply transition-transform;
|
|
21
18
|
}
|
|
22
19
|
|
|
23
|
-
[aria-expanded="true"] > svg
|
|
24
|
-
|
|
25
|
-
[aria-expanded="false"] > span:first-of-type,
|
|
26
|
-
[aria-expanded="false"] > svg:first-of-type {
|
|
27
|
-
@apply block;
|
|
20
|
+
[aria-expanded="true"] > svg {
|
|
21
|
+
@apply rotate-180;
|
|
28
22
|
}
|
|
29
23
|
}
|
|
30
24
|
|
|
@@ -92,14 +92,6 @@
|
|
|
92
92
|
[data-dialog-closable] {
|
|
93
93
|
@apply top-6 right-6;
|
|
94
94
|
}
|
|
95
|
-
|
|
96
|
-
[data-dialog-container] {
|
|
97
|
-
@apply inline grid-cols-none;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
[data-dialog-title] {
|
|
101
|
-
@apply col-span-2 md:col-span-1 md:col-start-2 grid grid-cols-[auto_1fr] items-start md:items-center gap-2 pb-4 text-left text-xl text-gray-2 md:border-b md:border-gray-3;
|
|
102
|
-
}
|
|
103
95
|
}
|
|
104
96
|
|
|
105
97
|
&-results {
|
|
@@ -22,19 +22,27 @@
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
[data-dialog-container] {
|
|
25
|
-
@apply
|
|
25
|
+
@apply block text-left;
|
|
26
26
|
|
|
27
|
-
> svg {
|
|
28
|
-
@apply
|
|
29
|
-
|
|
27
|
+
&:has(> svg) {
|
|
28
|
+
@apply grid grid-cols-[auto_1fr] items-start md:items-center gap-2;
|
|
29
|
+
|
|
30
|
+
> svg {
|
|
31
|
+
@apply w-6 h-6 text-gray fill-current flex-none mb-6;
|
|
32
|
+
}
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
> :last-child {
|
|
35
|
+
@apply col-span-2 md:col-span-1 md:col-start-2;
|
|
36
|
+
}
|
|
33
37
|
}
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
[data-dialog-title] {
|
|
37
|
-
@apply text-
|
|
41
|
+
@apply flex items-center gap-2 text-xl text-gray-2 font-semibold focus:outline-none pb-2 mb-4 md:border-b md:border-gray-3;
|
|
42
|
+
|
|
43
|
+
> svg {
|
|
44
|
+
@apply w-6 h-6 text-gray fill-current flex-none;
|
|
45
|
+
}
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
[data-dialog-actions] {
|
|
@@ -59,7 +67,7 @@
|
|
|
59
67
|
}
|
|
60
68
|
|
|
61
69
|
[data-dialog-container] {
|
|
62
|
-
@apply grid-cols-none;
|
|
70
|
+
@apply grid grid-cols-none;
|
|
63
71
|
|
|
64
72
|
&.layout-content {
|
|
65
73
|
@apply bg-white;
|
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
.qr-modal {
|
|
2
2
|
@apply md:p-6 lg:p-8;
|
|
3
3
|
|
|
4
|
-
[data-dialog-title] {
|
|
5
|
-
@apply flex justify-start items-center text-2xl text-black font-semibold focus:outline-none;
|
|
6
|
-
|
|
7
|
-
> svg {
|
|
8
|
-
@apply w-10 h-10 text-gray fill-current flex-none;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
> :last-child {
|
|
12
|
-
@apply ml-1;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
4
|
[data-dialog-container] {
|
|
17
|
-
@apply inline grid-cols-none;
|
|
18
|
-
|
|
19
5
|
.content {
|
|
20
6
|
@apply md:grid md:grid-cols-2 gap-5 md:py-4;
|
|
21
7
|
|
|
@@ -59,14 +45,6 @@
|
|
|
59
45
|
@apply top-6 right-6;
|
|
60
46
|
}
|
|
61
47
|
|
|
62
|
-
[data-dialog-container] {
|
|
63
|
-
@apply inline grid-cols-none;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
[data-dialog-title] {
|
|
67
|
-
@apply col-span-2 md:col-span-1 md:col-start-2 grid grid-cols-[auto_1fr] items-start md:items-center gap-2 pb-4 text-left text-xl text-gray-2 md:border-b md:border-gray-3;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
48
|
&__list {
|
|
71
49
|
@apply my-6 flex flex-wrap justify-center items-center md:grid grid-cols-4 md:grid-cols-5 gap-3 md:gap-6 max-w-96 mx-auto text-secondary;
|
|
72
50
|
|
|
@@ -111,7 +111,17 @@
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
&-item {
|
|
114
|
-
@apply flex items-center gap-1 text-white font-semibold hover:underline
|
|
114
|
+
@apply flex items-center gap-1 text-white font-semibold hover:underline;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
&-label {
|
|
118
|
+
@apply flex-1 min-w-0 overflow-hidden text-ellipsis;
|
|
119
|
+
|
|
120
|
+
overflow-wrap: anywhere;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
&-item > svg {
|
|
124
|
+
@apply shrink-0 fill-current;
|
|
115
125
|
}
|
|
116
126
|
}
|
|
117
127
|
|
|
@@ -7,18 +7,21 @@
|
|
|
7
7
|
.editor-content-videoEmbed {
|
|
8
8
|
@apply my-10 mx-auto;
|
|
9
9
|
}
|
|
10
|
+
}
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
// Resource mentions are rendered both inside rich text (`.rich-text-display`)
|
|
13
|
+
// and inside comments (`.editor-content`), so they cannot be scoped to one of
|
|
14
|
+
// those wrappers.
|
|
15
|
+
.editor-mention {
|
|
16
|
+
@apply inline-flex items-center align-bottom font-semibold underline text-secondary;
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
img,
|
|
19
|
+
svg {
|
|
20
|
+
@apply w-5 h-5 m-0 rounded-full;
|
|
21
|
+
}
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
23
|
+
span {
|
|
24
|
+
@apply ml-1;
|
|
22
25
|
}
|
|
23
26
|
}
|
|
24
27
|
|
|
@@ -27,7 +27,10 @@
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
.title-decorator {
|
|
30
|
-
@apply h2 md:h1 decorator;
|
|
30
|
+
@apply h2 md:h1 decorator break-normal;
|
|
31
|
+
|
|
32
|
+
hyphens: auto;
|
|
33
|
+
overflow-wrap: anywhere;
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
.text-with-links {
|
|
@@ -38,7 +41,7 @@
|
|
|
38
41
|
|
|
39
42
|
/* default styles for those dynamic text blocks */
|
|
40
43
|
.editor-content {
|
|
41
|
-
@apply prose max-w-full text-gray-2 text-lg prose-a:text-secondary break-words prose-ul:pl-
|
|
44
|
+
@apply prose max-w-full text-gray-2 text-lg prose-a:text-secondary break-words prose-ul:pl-8 prose-ol:pl-8;
|
|
42
45
|
|
|
43
46
|
iframe {
|
|
44
47
|
@apply w-full block max-w-[600px] max-h-[70vh] h-[450px] my-10 mx-auto;
|
|
@@ -83,6 +83,7 @@
|
|
|
83
83
|
@include meta.load-css("stylesheets/decidim/actions");
|
|
84
84
|
@include meta.load-css("stylesheets/decidim/emoji");
|
|
85
85
|
@include meta.load-css("stylesheets/decidim/success_image");
|
|
86
|
+
@include meta.load-css("stylesheets/decidim/editor_suggestions");
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
// This applies the custom style imports registered at the application.
|
|
@@ -1725,7 +1725,6 @@ body.outlook p {
|
|
|
1725
1725
|
table.body th.decidim-bar,
|
|
1726
1726
|
table.body td.decidim-bar {
|
|
1727
1727
|
padding: 10px 0;
|
|
1728
|
-
background-color: #1a181d;
|
|
1729
1728
|
}
|
|
1730
1729
|
|
|
1731
1730
|
table.body {
|
|
@@ -1742,10 +1741,6 @@ table.body {
|
|
|
1742
1741
|
height: 30px;
|
|
1743
1742
|
}
|
|
1744
1743
|
|
|
1745
|
-
.cityhall-bar {
|
|
1746
|
-
background-color: #2c2930;
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
1744
|
.cityhall-logo {
|
|
1750
1745
|
width: 150px;
|
|
1751
1746
|
margin-top: 16px;
|
|
@@ -9,6 +9,7 @@ module Decidim
|
|
|
9
9
|
locales_action?
|
|
10
10
|
component_public_action?
|
|
11
11
|
search_scope_action?
|
|
12
|
+
public_content_action?
|
|
12
13
|
|
|
13
14
|
public_report_content_action?
|
|
14
15
|
|
|
@@ -66,6 +67,23 @@ module Decidim
|
|
|
66
67
|
toggle_allow([:search, :pick].include?(permission_action.action))
|
|
67
68
|
end
|
|
68
69
|
|
|
70
|
+
# Public, non-resource content that any visitor is allowed to reach. The
|
|
71
|
+
# controllers using these subjects enforce the actual data visibility
|
|
72
|
+
# themselves (profile publication, private download tokens, newsletter
|
|
73
|
+
# tokens, etc.).
|
|
74
|
+
def public_content_action?
|
|
75
|
+
case permission_action.subject
|
|
76
|
+
when :open_data, :search, :profile, :private_download, :short_link,
|
|
77
|
+
:calendar, :badges, :static_map, :qr_code, :homepage, :manifest,
|
|
78
|
+
:offline, :link, :authorization, :error, :timeout, :page
|
|
79
|
+
allow! if permission_action.action == :read
|
|
80
|
+
when :newsletter
|
|
81
|
+
allow! if [:read, :unsubscribe].include?(permission_action.action)
|
|
82
|
+
when :upload_validation
|
|
83
|
+
allow! if permission_action.action == :create
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
69
87
|
def manage_self_user_action?
|
|
70
88
|
return unless permission_action.subject == :user
|
|
71
89
|
|
|
@@ -78,6 +96,8 @@ module Decidim
|
|
|
78
96
|
authorization = context.fetch(:authorization, nil)
|
|
79
97
|
|
|
80
98
|
case permission_action.action
|
|
99
|
+
when :read
|
|
100
|
+
allow!
|
|
81
101
|
when :create
|
|
82
102
|
toggle_allow(authorization.user == user && not_already_active?(authorization))
|
|
83
103
|
when :update, :destroy
|
|
@@ -29,7 +29,7 @@ module Decidim
|
|
|
29
29
|
|
|
30
30
|
def action_string
|
|
31
31
|
case action
|
|
32
|
-
when "export_component", "soft_delete", "restore"
|
|
32
|
+
when "export_component", "soft_delete", "restore", "menu_hidden"
|
|
33
33
|
"decidim.admin_log.component.#{action}"
|
|
34
34
|
when "create", "delete", "publish", "unpublish", "update_permissions", "update_filters"
|
|
35
35
|
generate_action_string(action)
|
|
@@ -111,9 +111,18 @@ module Decidim
|
|
|
111
111
|
# Returns an HTML-safe String.
|
|
112
112
|
def present_explanation
|
|
113
113
|
h.content_tag(:div, class: "logs__log__explanation") do
|
|
114
|
+
escaped_params = i18n_params.transform_values do |value|
|
|
115
|
+
if value.is_a?(String) && !value.html_safe?
|
|
116
|
+
ERB::Util.html_escape(value)
|
|
117
|
+
else
|
|
118
|
+
value
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
114
122
|
I18n.t(
|
|
115
123
|
action_string,
|
|
116
|
-
|
|
124
|
+
escape: false,
|
|
125
|
+
**escaped_params
|
|
117
126
|
).html_safe
|
|
118
127
|
end
|
|
119
128
|
end
|
|
@@ -67,9 +67,9 @@ module Decidim
|
|
|
67
67
|
|
|
68
68
|
# Private: Presents the nickname of the user performing the action.
|
|
69
69
|
#
|
|
70
|
-
# Returns
|
|
70
|
+
# Returns a String.
|
|
71
71
|
def present_user_nickname
|
|
72
|
-
extra["nickname"]
|
|
72
|
+
extra["nickname"]
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
# Private: Calculates the path for the user. Returns the path of the
|
|
@@ -45,5 +45,16 @@ module Decidim
|
|
|
45
45
|
content
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
|
+
|
|
49
|
+
# Prepares content for plain text areas (non-editor). Renders mentions
|
|
50
|
+
# to plain @nickname text instead of editor-specific HTML tags.
|
|
51
|
+
def plain_locales(data, all_locales)
|
|
52
|
+
handle_locales(data, all_locales) do |content|
|
|
53
|
+
content = Decidim::ContentRenderers::BlobRenderer.new(content).render
|
|
54
|
+
content = Decidim::ContentRenderers::UserRenderer.new(content).render(plain: true)
|
|
55
|
+
content = Decidim::ContentRenderers::MentionResourceRenderer.new(content).render(plain: true)
|
|
56
|
+
content.html_safe
|
|
57
|
+
end
|
|
58
|
+
end
|
|
48
59
|
end
|
|
49
60
|
end
|
|
@@ -82,7 +82,7 @@ module Decidim
|
|
|
82
82
|
blob = find_blob_by_id(image_url.split("/").last)
|
|
83
83
|
return blob if blob.present?
|
|
84
84
|
|
|
85
|
-
blob =
|
|
85
|
+
blob = find_blob_from_url(image_url)
|
|
86
86
|
return blob if blob.present?
|
|
87
87
|
end
|
|
88
88
|
|
|
@@ -119,6 +119,12 @@ module Decidim
|
|
|
119
119
|
nil
|
|
120
120
|
end
|
|
121
121
|
|
|
122
|
+
def find_blob_from_url(url)
|
|
123
|
+
return GlobalID::Locator.locate(url, only: ActiveStorage::Blob) if url.start_with?("gid://")
|
|
124
|
+
|
|
125
|
+
find_blob_by_key(url.split("/").second_to_last)
|
|
126
|
+
end
|
|
127
|
+
|
|
122
128
|
def find_blob_by_key(blob_key)
|
|
123
129
|
ActiveStorage::Blob.find_signed(blob_key) if blob_key.present?
|
|
124
130
|
end
|