Skip to content

Commit 4379973

Browse files
committed
Merge remote-tracking branch 'origin/4.6'
2 parents 60060bb + 72a64d9 commit 4379973

File tree

14 files changed

+172
-148
lines changed

14 files changed

+172
-148
lines changed

src/bundle/Resources/public/js/scripts/admin.search.autocomplete.content.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
return total;
2121
}
2222

23-
return index === 0 ? parentLocation.name : `${total} / ${parentLocation.name}`;
23+
const parentLocationNameHTMLEscaped = escapeHTML(parentLocation.name);
24+
25+
return index === 0 ? parentLocationNameHTMLEscaped : `${total} / ${parentLocationNameHTMLEscaped}`;
2426
}, '');
2527
const breadcrumbsClass = !breadcrumb ? 'ibexa-global-search__autocomplete-item-breadcrumbs--empty' : '';
2628
const autocompleteItemTemplate = autocompleteContentTemplateNode.dataset.templateItem;

src/bundle/Resources/public/js/scripts/admin.search.filters.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
(function (global, doc, ibexa, flatpickr, React, ReactDOMClient) {
2+
const { escapeHTML, escapeHTMLAttribute } = ibexa.helpers.text;
3+
const { dangerouslySetInnerHTML } = ibexa.helpers.dom;
24
let getUsersTimeout;
35
const CLASS_DATE_RANGE = 'ibexa-filters__range-wrapper';
46
const CLASS_VISIBLE_DATE_RANGE = 'ibexa-filters__range-wrapper--visible';
@@ -118,11 +120,11 @@
118120
};
119121
const filterByContentType = () => {
120122
const selectedCheckboxes = [...contentTypeCheckboxes].filter((checkbox) => checkbox.checked);
121-
const contentTypesText = selectedCheckboxes.map((checkbox) => checkbox.dataset.name).join(', ');
123+
const contentTypesText = selectedCheckboxes.map((checkbox) => escapeHTML(checkbox.dataset.name)).join(', ');
122124
const [option] = contentTypeSelect;
123125
const defaultText = option.dataset.default;
124126

125-
option.innerHTML = contentTypesText || defaultText;
127+
dangerouslySetInnerHTML(option, contentTypesText || defaultText);
126128

127129
toggleDisabledStateOnApplyBtn();
128130
};
@@ -186,14 +188,17 @@
186188
.then(showUsersList);
187189
};
188190
const createUsersListItem = (user) => {
189-
return `<li data-id="${user._id}" data-name="${user.TranslatedName}" class="ibexa-filters__user-item">${user.TranslatedName}</li>`;
191+
const userNameHtmlEscaped = escapeHTML(user.TranslatedName);
192+
const userNameHtmlAttributeEscaped = escapeHTMLAttribute(user.TranslatedName);
193+
194+
return `<li data-id="${user._id}" data-name="${userNameHtmlAttributeEscaped}" class="ibexa-filters__user-item">${userNameHtmlEscaped}</li>`;
190195
};
191196
const showUsersList = (data) => {
192197
const hits = data.View.Result.searchHits.searchHit;
193198
const users = hits.reduce((total, hit) => total + createUsersListItem(hit.value.Content), '');
194199
const methodName = users ? 'addEventListener' : 'removeEventListener';
195200

196-
usersList.innerHTML = users;
201+
dangerouslySetInnerHTML(usersList, users);
197202
usersList.classList.remove('ibexa-filters__user-list--hidden');
198203

199204
doc.querySelector('body')[methodName]('click', handleClickOutsideUserList, false);

src/bundle/Resources/public/js/scripts/admin.trash.list.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
(function (global, doc, ibexa, React, ReactDOMClient, Translator) {
2+
const { escapeHTML, escapeHTMLAttribute } = ibexa.helpers.text;
3+
const { dangerouslySetInnerHTML } = ibexa.helpers.dom;
24
let getUsersTimeout;
35
const CLASS_SORTED_ASC = 'ibexa-table__sort-column--asc';
46
const CLASS_SORTED_DESC = 'ibexa-table__sort-column--desc';
@@ -150,14 +152,17 @@
150152
.catch(() => ibexa.helpers.notification.showErrorNotification(errorMessage));
151153
};
152154
const createUsersListItem = (user) => {
153-
return `<li data-id="${user._id}" data-name="${user.TranslatedName}" class="ibexa-trash-search-form__user-item">${user.TranslatedName}</li>`;
155+
const userNameHtmlEscaped = escapeHTML(user.TranslatedName);
156+
const userNameHtmlAttributeEscaped = escapeHTMLAttribute(user.TranslatedName);
157+
158+
return `<li data-id="${user._id}" data-name="${userNameHtmlAttributeEscaped}" class="ibexa-trash-search-form__user-item">${userNameHtmlEscaped}</li>`;
154159
};
155160
const showUsersList = (data) => {
156161
const hits = data.View.Result.searchHits.searchHit;
157162
const users = hits.reduce((total, hit) => total + createUsersListItem(hit.value.Content), '');
158163
const methodName = users ? 'addEventListener' : 'removeEventListener';
159164

160-
usersList.innerHTML = users;
165+
dangerouslySetInnerHTML(usersList, users);
161166
usersList.classList.remove('ibexa-trash-search-form__user-list--hidden');
162167

163168
doc.querySelector('body')[methodName]('click', handleClickOutsideUserList, false);

src/bundle/Resources/public/js/scripts/core/doughnut.chart.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
(function (global, doc, ibexa, ChartDataLabels) {
2+
const { escapeHTML } = ibexa.helpers.text;
3+
const { dangerouslyInsertAdjacentHTML } = ibexa.helpers.dom;
24
const IBEXA_WHITE = '#fff';
35
const IBEXA_COLOR_BASE_DARK = '#878b90';
46
const dataLabelsMap = new Map();
@@ -71,13 +73,14 @@
7173
data.legend.forEach((legendItem, index) => {
7274
dataLabelsMap.set(index, true);
7375

76+
const legendItemHtmlEscaped = escapeHTML(legendItem);
7477
const container = doc.createElement('div');
7578
const renderedItemTemplate = itemTemplate
7679
.replace('{{ checked_color }}', data.backgroundColor[index])
7780
.replace('{{ dataset_index }}', index)
78-
.replace('{{ label }}', legendItem);
81+
.replace('{{ label }}', legendItemHtmlEscaped);
7982

80-
container.insertAdjacentHTML('beforeend', renderedItemTemplate);
83+
dangerouslyInsertAdjacentHTML(container, 'beforeend', renderedItemTemplate);
8184

8285
const checkboxNode = container.querySelector('.ibexa-chart-legend__item-wrapper');
8386

src/bundle/Resources/public/js/scripts/core/dropdown.js

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
(function (global, doc, ibexa, bootstrap, Translator) {
2+
const { escapeHTML, escapeHTMLAttribute } = ibexa.helpers.text;
3+
const { dangerouslySetInnerHTML, dangerouslyInsertAdjacentHTML } = ibexa.helpers.dom;
4+
25
const EVENT_VALUE_CHANGED = 'change';
36
const RESTRICTED_AREA_ITEMS_CONTAINER = 190;
47
const MINIMUM_LETTERS_TO_FILTER = 3;
@@ -104,10 +107,10 @@
104107
createSelectedItem(value, label, icon) {
105108
const container = doc.createElement('div');
106109
const selectedItemRendered = this.selectedItemTemplate
107-
.replaceAll('{{ value }}', ibexa.helpers.text.escapeHTMLAttribute(value))
110+
.replaceAll('{{ value }}', escapeHTMLAttribute(value))
108111
.replaceAll('{{ label }}', label);
109112

110-
container.insertAdjacentHTML('beforeend', selectedItemRendered);
113+
dangerouslyInsertAdjacentHTML(container, 'beforeend', selectedItemRendered);
111114

112115
const selectedItemNode = container.querySelector('.ibexa-dropdown__selected-item');
113116
const removeSelectionBtn = selectedItemNode.querySelector('.ibexa-dropdown__remove-selection');
@@ -341,7 +344,7 @@
341344
const separator = this.itemsListContainer.querySelector('.ibexa-dropdown__separator');
342345
const emptyMessage = Translator.trans(
343346
/* @Desc("No results found for &quot;%phrase%&quot;") */ 'dropdown.no_results',
344-
{ phrase: searchedTerm },
347+
{ phrase: escapeHTML(searchedTerm) },
345348
'ibexa_dropdown',
346349
);
347350
let hideSeparator = true;
@@ -378,7 +381,10 @@
378381

379382
this.itemsListContainer.toggleAttribute('hidden', !anyItemVisible);
380383
this.itemsListFilterEmptyContainer.toggleAttribute('hidden', anyItemVisible);
381-
this.itemsListFilterEmptyContainer.querySelector('.ibexa-dropdown__items-list-filter-empty-message').innerHTML = emptyMessage;
384+
385+
const emptyMessageNode = this.itemsListFilterEmptyContainer.querySelector('.ibexa-dropdown__items-list-filter-empty-message');
386+
387+
dangerouslySetInnerHTML(emptyMessageNode, emptyMessage);
382388
}
383389

384390
itemsPopoverContent() {
@@ -434,9 +440,7 @@
434440

435441
createOption(value, label) {
436442
const container = doc.createElement('div');
437-
const itemRendered = this.itemTemplate
438-
.replaceAll('{{ value }}', ibexa.helpers.text.escapeHTMLAttribute(value))
439-
.replaceAll('{{ label }}', label);
443+
const itemRendered = this.itemTemplate.replaceAll('{{ value }}', escapeHTMLAttribute(value)).replaceAll('{{ label }}', label);
440444

441445
container.insertAdjacentHTML('beforeend', itemRendered);
442446

src/bundle/Resources/public/js/scripts/core/tag.view.select.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import * as middleEllipsisHelper from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/middle.ellipsis';
22

33
(function (global, doc, ibexa) {
4+
const { escapeHTML } = ibexa.helpers.text;
5+
const { dangerouslyAppend } = ibexa.helpers.dom;
6+
47
class TagViewSelect {
58
constructor(config) {
69
this.inputSelector = config.inputSelector || 'input';
@@ -76,14 +79,14 @@ import * as middleEllipsisHelper from '@ibexa-admin-ui/src/bundle/Resources/publ
7679

7780
items.forEach((item) => {
7881
const { id, name } = item;
79-
const itemTemplate = this.selectedItemTemplate.replace('{{ id }}', id).replaceAll('{{ name }}', name);
82+
const itemTemplate = this.selectedItemTemplate.replace('{{ id }}', id).replaceAll('{{ name }}', escapeHTML(name));
8083
const range = doc.createRange();
8184
const itemHtmlWidget = range.createContextualFragment(itemTemplate);
8285
const deleteButton = itemHtmlWidget.querySelector('.ibexa-tag-view-select__selected-item-tag-remove-btn');
8386

8487
deleteButton.toggleAttribute('disabled', false);
8588
deleteButton.addEventListener('click', () => this.removeItem(String(id)), false);
86-
this.listContainer.append(itemHtmlWidget);
89+
dangerouslyAppend(this.listContainer, itemHtmlWidget);
8790
});
8891

8992
this.inputField.dispatchEvent(new Event('change'));

src/bundle/Resources/public/js/scripts/core/taggify.js

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
(function (global, doc, ibexa) {
2+
const { escapeHTML, escapeHTMLAttribute } = ibexa.helpers.text;
3+
const { dangerouslyInsertAdjacentHTML } = ibexa.helpers.dom;
4+
25
class Taggify {
36
constructor(config) {
47
this.container = config.container;
@@ -10,6 +13,7 @@
1013

1114
this.attachEventsToTag = this.attachEventsToTag.bind(this);
1215
this.handleInputKeyUp = this.handleInputKeyUp.bind(this);
16+
this.handleInputBlur = this.handleInputBlur.bind(this);
1317
this.addElementAttributes = this.addElementAttributes.bind(this);
1418
}
1519

@@ -19,6 +23,14 @@
1923
return this.acceptKeys.includes(key);
2024
}
2125

26+
removeAcceptKey(name) {
27+
if (this.acceptKeys.includes(name.at(-1))) {
28+
return name.slice(0, -1);
29+
}
30+
31+
return name;
32+
}
33+
2234
addElementAttributes(element, attrs) {
2335
const getValue = (value) => (typeof value === 'object' ? JSON.stringify(value) : value);
2436

@@ -36,23 +48,37 @@
3648
}
3749

3850
addTag(name, value, attrs = {}, tooltipAttrs = {}) {
51+
if (!value || this.tags.has(value)) {
52+
return;
53+
}
54+
3955
const tagTemplate = this.listNode.dataset.template;
40-
const renderedTemplate = tagTemplate.replace('{{ name }}', name).replace('{{ value }}', value);
56+
const nameHtmlEscaped = escapeHTML(name);
57+
const valueHtmlAttributeEscaped = escapeHTMLAttribute(value);
58+
const renderedTemplate = tagTemplate.replace('{{ name }}', nameHtmlEscaped).replace('{{ value }}', valueHtmlAttributeEscaped);
4159
const div = doc.createElement('div');
4260

43-
div.insertAdjacentHTML('beforeend', renderedTemplate);
61+
dangerouslyInsertAdjacentHTML(div, 'beforeend', renderedTemplate);
4462

4563
const tag = div.querySelector('.ibexa-taggify__list-tag');
4664
const tagNameNode = tag.querySelector('.ibexa-taggify__list-tag-name');
4765

48-
this.addElementAttributes(tag, attrs);
66+
this.addElementAttributes(tag, { ...attrs, dataset: { ...attrs.dataset, value } });
4967
this.addElementAttributes(tagNameNode, tooltipAttrs);
5068
this.attachEventsToTag(tag, value);
5169
this.listNode.insertBefore(tag, this.inputNode);
5270
this.tags.add(value);
5371
this.afterTagsUpdate();
5472
}
5573

74+
removeTagWithValue(value) {
75+
const tag = this.listNode.querySelector(`.ibexa-taggify__list-tag[data-value="${value}"]`);
76+
77+
if (tag) {
78+
this.removeTag(tag, value);
79+
}
80+
}
81+
5682
removeTag(tag, value) {
5783
this.tags.delete(value);
5884

@@ -61,26 +87,49 @@
6187
this.afterTagsUpdate();
6288
}
6389

90+
removeAllTags() {
91+
this.tags.forEach((tag) => {
92+
const tagNode = this.listNode.querySelector(`.ibexa-taggify__list-tag[data-value="${tag}"]`);
93+
94+
if (tagNode) {
95+
tagNode.remove();
96+
}
97+
});
98+
99+
this.tags.clear();
100+
this.afterTagsUpdate();
101+
}
102+
64103
attachEventsToTag(tag, value) {
65104
const removeBtn = tag.querySelector('.ibexa-taggify__btn--remove');
66105

67106
removeBtn.addEventListener('click', () => this.removeTag(tag, value), false);
68107
}
69108

109+
handleInputBlur() {
110+
const inputValue = this.inputNode.value.trim();
111+
112+
this.addTag(inputValue, inputValue);
113+
this.inputNode.value = '';
114+
}
115+
70116
handleInputKeyUp(event) {
71117
if (this.tagsPattern && !this.tagsPattern.test(this.inputNode.value)) {
72118
return;
73119
}
74120

75-
if (this.isAcceptKeyPressed(event.key) && this.inputNode.value && !this.tags.has(this.inputNode.value)) {
76-
this.addTag(this.inputNode.value, this.inputNode.value);
121+
if (this.isAcceptKeyPressed(event.key)) {
122+
const nameWithoutAcceptKey = this.removeAcceptKey(this.inputNode.value);
123+
124+
this.addTag(nameWithoutAcceptKey, nameWithoutAcceptKey);
77125

78126
this.inputNode.value = '';
79127
}
80128
}
81129

82130
init() {
83131
this.inputNode.addEventListener('keyup', this.handleInputKeyUp, false);
132+
this.inputNode.addEventListener('blur', this.handleInputBlur, false);
84133
}
85134
}
86135

0 commit comments

Comments
 (0)