@hellotext/hellotext 2.5.1 → 2.5.3
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.
- package/dist/hellotext.js +2 -1
- package/dist/hellotext.js.LICENSE.txt +1 -0
- package/lib/controllers/message_controller.cjs +6 -0
- package/lib/controllers/message_controller.js +6 -0
- package/lib/controllers/webchat_controller.cjs +5 -8
- package/lib/controllers/webchat_controller.js +5 -8
- package/lib/core/sanitize_html.cjs +46 -0
- package/lib/core/sanitize_html.js +37 -0
- package/lib/models/form.cjs +3 -2
- package/lib/models/form.js +3 -2
- package/package.json +2 -1
- package/src/controllers/message_controller.js +9 -1
- package/src/controllers/webchat_controller.js +8 -10
- package/src/core/sanitize_html.js +40 -0
- package/src/models/form.js +3 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/*! @license DOMPurify 3.4.13 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.13/LICENSE */
|
|
@@ -87,6 +87,11 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
87
87
|
_hellotext.default.track('cart.added', {
|
|
88
88
|
object_parameters: {
|
|
89
89
|
items: [item]
|
|
90
|
+
},
|
|
91
|
+
source: {
|
|
92
|
+
kind: this.kindValue,
|
|
93
|
+
message_id: this.idValue,
|
|
94
|
+
button_id: currentTarget.dataset.id
|
|
90
95
|
}
|
|
91
96
|
});
|
|
92
97
|
_hellotext.default.eventEmitter.dispatch('cart.added', {
|
|
@@ -285,6 +290,7 @@ _default.values = {
|
|
|
285
290
|
default: 64
|
|
286
291
|
},
|
|
287
292
|
id: String,
|
|
293
|
+
kind: String,
|
|
288
294
|
pageStartOffset: {
|
|
289
295
|
type: Number,
|
|
290
296
|
default: 0
|
|
@@ -86,6 +86,11 @@ var _default = /*#__PURE__*/function (_Controller) {
|
|
|
86
86
|
Hellotext.track('cart.added', {
|
|
87
87
|
object_parameters: {
|
|
88
88
|
items: [item]
|
|
89
|
+
},
|
|
90
|
+
source: {
|
|
91
|
+
kind: this.kindValue,
|
|
92
|
+
message_id: this.idValue,
|
|
93
|
+
button_id: currentTarget.dataset.id
|
|
89
94
|
}
|
|
90
95
|
});
|
|
91
96
|
Hellotext.eventEmitter.dispatch('cart.added', {
|
|
@@ -280,6 +285,7 @@ _default.values = {
|
|
|
280
285
|
default: 64
|
|
281
286
|
},
|
|
282
287
|
id: String,
|
|
288
|
+
kind: String,
|
|
283
289
|
pageStartOffset: {
|
|
284
290
|
type: Number,
|
|
285
291
|
default: 0
|
|
@@ -11,6 +11,7 @@ var _webchat_channel = _interopRequireDefault(require("../channels/webchat_chann
|
|
|
11
11
|
var _hellotext = _interopRequireDefault(require("../hellotext"));
|
|
12
12
|
var _core = require("../core");
|
|
13
13
|
var _webchat = require("../core/configuration/webchat");
|
|
14
|
+
var _sanitize_html = require("../core/sanitize_html");
|
|
14
15
|
var _usePopover = require("./mixins/usePopover");
|
|
15
16
|
var _useBehaviour = require("./webchat/useBehaviour");
|
|
16
17
|
var _useOpeningSequence = require("./webchat/useOpeningSequence");
|
|
@@ -265,8 +266,6 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
265
266
|
attachments
|
|
266
267
|
} = message;
|
|
267
268
|
const createdAt = message.created_at || message.createdAt;
|
|
268
|
-
const div = document.createElement('div');
|
|
269
|
-
div.innerHTML = body;
|
|
270
269
|
const element = this.messageTemplateTarget.cloneNode(true);
|
|
271
270
|
element.classList.add('hellotext--webchat-message');
|
|
272
271
|
element.setAttribute('data-hellotext--webchat-target', 'message');
|
|
@@ -275,7 +274,7 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
275
274
|
element.setAttribute('data-created-at', createdAt);
|
|
276
275
|
}
|
|
277
276
|
element.style.removeProperty('display');
|
|
278
|
-
element.querySelector('[data-body]')
|
|
277
|
+
(0, _sanitize_html.setSanitizedRichText)(element.querySelector('[data-body]'), body);
|
|
279
278
|
if (message.state === 'received') {
|
|
280
279
|
element.classList.add('received');
|
|
281
280
|
} else {
|
|
@@ -412,12 +411,10 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
412
411
|
if (message.carousel) {
|
|
413
412
|
return this.insertCarouselMessage(message, options);
|
|
414
413
|
}
|
|
415
|
-
const div = document.createElement('div');
|
|
416
|
-
div.innerHTML = body;
|
|
417
414
|
const element = this.messageTemplateTarget.cloneNode(true);
|
|
418
415
|
element.classList.add('hellotext--webchat-message');
|
|
419
416
|
element.style.display = 'flex';
|
|
420
|
-
element.querySelector('[data-body]')
|
|
417
|
+
(0, _sanitize_html.setSanitizedRichText)(element.querySelector('[data-body]'), body);
|
|
421
418
|
element.setAttribute('data-id', id);
|
|
422
419
|
element.setAttribute('data-hellotext--webchat-target', 'message');
|
|
423
420
|
this.setMessageCreatedAt(element, createdAt);
|
|
@@ -533,7 +530,7 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
533
530
|
return;
|
|
534
531
|
}
|
|
535
532
|
this.teaserMessageTargets.forEach(teaserMessage => teaserMessage.classList.add('hidden'));
|
|
536
|
-
this.inboundMessageTeaserBodyTarget.
|
|
533
|
+
this.inboundMessageTeaserBodyTarget.textContent = this.messageTeaserValue;
|
|
537
534
|
this.inboundMessageTeaserTarget.classList.remove('hidden');
|
|
538
535
|
this.teaserTarget.classList.toggle('invisible', this.openValue);
|
|
539
536
|
}
|
|
@@ -543,7 +540,7 @@ let _default = /*#__PURE__*/function (_Controller) {
|
|
|
543
540
|
var _element$querySelecto;
|
|
544
541
|
const html = message.html;
|
|
545
542
|
const createdAt = message.created_at || message.createdAt;
|
|
546
|
-
const element =
|
|
543
|
+
const element = (0, _sanitize_html.sanitizedWebchatComponentFragment)(html).firstElementChild;
|
|
547
544
|
element.classList.add('hellotext--webchat-message');
|
|
548
545
|
element.setAttribute('data-id', message.id);
|
|
549
546
|
element.setAttribute('data-hellotext--webchat-target', 'message');
|
|
@@ -24,6 +24,7 @@ import WebchatChannel from '../channels/webchat_channel';
|
|
|
24
24
|
import Hellotext from '../hellotext';
|
|
25
25
|
import { Locale } from '../core';
|
|
26
26
|
import { Webchat as WebchatConfiguration, modes } from '../core/configuration/webchat';
|
|
27
|
+
import { sanitizedWebchatComponentFragment, setSanitizedRichText } from '../core/sanitize_html';
|
|
27
28
|
import { usePopover } from './mixins/usePopover';
|
|
28
29
|
import { useBehaviour } from './webchat/useBehaviour';
|
|
29
30
|
import { useOpeningSequence } from './webchat/useOpeningSequence';
|
|
@@ -264,8 +265,6 @@ var _default = /*#__PURE__*/function (_Controller) {
|
|
|
264
265
|
attachments
|
|
265
266
|
} = message;
|
|
266
267
|
var createdAt = message.created_at || message.createdAt;
|
|
267
|
-
var div = document.createElement('div');
|
|
268
|
-
div.innerHTML = body;
|
|
269
268
|
var element = this.messageTemplateTarget.cloneNode(true);
|
|
270
269
|
element.classList.add('hellotext--webchat-message');
|
|
271
270
|
element.setAttribute('data-hellotext--webchat-target', 'message');
|
|
@@ -274,7 +273,7 @@ var _default = /*#__PURE__*/function (_Controller) {
|
|
|
274
273
|
element.setAttribute('data-created-at', createdAt);
|
|
275
274
|
}
|
|
276
275
|
element.style.removeProperty('display');
|
|
277
|
-
element.querySelector('[data-body]')
|
|
276
|
+
setSanitizedRichText(element.querySelector('[data-body]'), body);
|
|
278
277
|
if (message.state === 'received') {
|
|
279
278
|
element.classList.add('received');
|
|
280
279
|
} else {
|
|
@@ -417,12 +416,10 @@ var _default = /*#__PURE__*/function (_Controller) {
|
|
|
417
416
|
if (message.carousel) {
|
|
418
417
|
return this.insertCarouselMessage(message, options);
|
|
419
418
|
}
|
|
420
|
-
var div = document.createElement('div');
|
|
421
|
-
div.innerHTML = body;
|
|
422
419
|
var element = this.messageTemplateTarget.cloneNode(true);
|
|
423
420
|
element.classList.add('hellotext--webchat-message');
|
|
424
421
|
element.style.display = 'flex';
|
|
425
|
-
element.querySelector('[data-body]')
|
|
422
|
+
setSanitizedRichText(element.querySelector('[data-body]'), body);
|
|
426
423
|
element.setAttribute('data-id', id);
|
|
427
424
|
element.setAttribute('data-hellotext--webchat-target', 'message');
|
|
428
425
|
this.setMessageCreatedAt(element, createdAt);
|
|
@@ -543,7 +540,7 @@ var _default = /*#__PURE__*/function (_Controller) {
|
|
|
543
540
|
return;
|
|
544
541
|
}
|
|
545
542
|
this.teaserMessageTargets.forEach(teaserMessage => teaserMessage.classList.add('hidden'));
|
|
546
|
-
this.inboundMessageTeaserBodyTarget.
|
|
543
|
+
this.inboundMessageTeaserBodyTarget.textContent = this.messageTeaserValue;
|
|
547
544
|
this.inboundMessageTeaserTarget.classList.remove('hidden');
|
|
548
545
|
this.teaserTarget.classList.toggle('invisible', this.openValue);
|
|
549
546
|
}
|
|
@@ -554,7 +551,7 @@ var _default = /*#__PURE__*/function (_Controller) {
|
|
|
554
551
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
555
552
|
var html = message.html;
|
|
556
553
|
var createdAt = message.created_at || message.createdAt;
|
|
557
|
-
var element =
|
|
554
|
+
var element = sanitizedWebchatComponentFragment(html).firstElementChild;
|
|
558
555
|
element.classList.add('hellotext--webchat-message');
|
|
559
556
|
element.setAttribute('data-id', message.id);
|
|
560
557
|
element.setAttribute('data-hellotext--webchat-target', 'message');
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.sanitizedRichTextFragment = sanitizedRichTextFragment;
|
|
7
|
+
exports.sanitizedWebchatComponentFragment = sanitizedWebchatComponentFragment;
|
|
8
|
+
exports.setSanitizedRichText = setSanitizedRichText;
|
|
9
|
+
var _dompurify = _interopRequireDefault(require("dompurify"));
|
|
10
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
11
|
+
const RICH_TEXT_SANITIZER_OPTIONS = {
|
|
12
|
+
ADD_ATTR: ['target'],
|
|
13
|
+
ALLOW_DATA_ATTR: false,
|
|
14
|
+
RETURN_DOM_FRAGMENT: true,
|
|
15
|
+
USE_PROFILES: {
|
|
16
|
+
html: true
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const WEBCHAT_COMPONENT_SANITIZER_OPTIONS = {
|
|
20
|
+
ADD_ATTR: ['target'],
|
|
21
|
+
ADD_TAGS: ['hellotext-icon'],
|
|
22
|
+
RETURN_DOM_FRAGMENT: true,
|
|
23
|
+
USE_PROFILES: {
|
|
24
|
+
html: true,
|
|
25
|
+
svg: true
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
function sanitizedFragment(html, options) {
|
|
29
|
+
const fragment = _dompurify.default.sanitize(html, options);
|
|
30
|
+
fragment.querySelectorAll('a[target="_blank"]').forEach(link => {
|
|
31
|
+
const rel = new Set(link.rel.split(/\s+/).filter(Boolean));
|
|
32
|
+
rel.add('noopener');
|
|
33
|
+
rel.add('noreferrer');
|
|
34
|
+
link.rel = Array.from(rel).join(' ');
|
|
35
|
+
});
|
|
36
|
+
return fragment;
|
|
37
|
+
}
|
|
38
|
+
function sanitizedRichTextFragment(html) {
|
|
39
|
+
return sanitizedFragment(html, RICH_TEXT_SANITIZER_OPTIONS);
|
|
40
|
+
}
|
|
41
|
+
function sanitizedWebchatComponentFragment(html) {
|
|
42
|
+
return sanitizedFragment(html, WEBCHAT_COMPONENT_SANITIZER_OPTIONS);
|
|
43
|
+
}
|
|
44
|
+
function setSanitizedRichText(element, html) {
|
|
45
|
+
element.replaceChildren(sanitizedRichTextFragment(html));
|
|
46
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import DOMPurify from 'dompurify';
|
|
2
|
+
var RICH_TEXT_SANITIZER_OPTIONS = {
|
|
3
|
+
ADD_ATTR: ['target'],
|
|
4
|
+
ALLOW_DATA_ATTR: false,
|
|
5
|
+
RETURN_DOM_FRAGMENT: true,
|
|
6
|
+
USE_PROFILES: {
|
|
7
|
+
html: true
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
var WEBCHAT_COMPONENT_SANITIZER_OPTIONS = {
|
|
11
|
+
ADD_ATTR: ['target'],
|
|
12
|
+
ADD_TAGS: ['hellotext-icon'],
|
|
13
|
+
RETURN_DOM_FRAGMENT: true,
|
|
14
|
+
USE_PROFILES: {
|
|
15
|
+
html: true,
|
|
16
|
+
svg: true
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
function sanitizedFragment(html, options) {
|
|
20
|
+
var fragment = DOMPurify.sanitize(html, options);
|
|
21
|
+
fragment.querySelectorAll('a[target="_blank"]').forEach(link => {
|
|
22
|
+
var rel = new Set(link.rel.split(/\s+/).filter(Boolean));
|
|
23
|
+
rel.add('noopener');
|
|
24
|
+
rel.add('noreferrer');
|
|
25
|
+
link.rel = Array.from(rel).join(' ');
|
|
26
|
+
});
|
|
27
|
+
return fragment;
|
|
28
|
+
}
|
|
29
|
+
export function sanitizedRichTextFragment(html) {
|
|
30
|
+
return sanitizedFragment(html, RICH_TEXT_SANITIZER_OPTIONS);
|
|
31
|
+
}
|
|
32
|
+
export function sanitizedWebchatComponentFragment(html) {
|
|
33
|
+
return sanitizedFragment(html, WEBCHAT_COMPONENT_SANITIZER_OPTIONS);
|
|
34
|
+
}
|
|
35
|
+
export function setSanitizedRichText(element, html) {
|
|
36
|
+
element.replaceChildren(sanitizedRichTextFragment(html));
|
|
37
|
+
}
|
package/lib/models/form.cjs
CHANGED
|
@@ -7,6 +7,7 @@ exports.Form = void 0;
|
|
|
7
7
|
var _hellotext = _interopRequireDefault(require("../hellotext"));
|
|
8
8
|
var _input_builder = require("../builders/input_builder");
|
|
9
9
|
var _logo_builder = require("../builders/logo_builder");
|
|
10
|
+
var _sanitize_html = require("../core/sanitize_html");
|
|
10
11
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
11
12
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
12
13
|
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
|
|
@@ -58,7 +59,7 @@ let Form = /*#__PURE__*/function () {
|
|
|
58
59
|
key: "buildHeader",
|
|
59
60
|
value: function buildHeader(header) {
|
|
60
61
|
const headerElement = _classPrivateFieldLooseBase(this, _findOrCreateComponent)[_findOrCreateComponent]('[data-form-header]', 'header');
|
|
61
|
-
headerElement
|
|
62
|
+
(0, _sanitize_html.setSanitizedRichText)(headerElement, header.content);
|
|
62
63
|
if (this.element.querySelector('[data-form-header]')) {
|
|
63
64
|
this.element.querySelector('[data-form-header]').replaceWith(headerElement);
|
|
64
65
|
} else {
|
|
@@ -94,7 +95,7 @@ let Form = /*#__PURE__*/function () {
|
|
|
94
95
|
key: "buildFooter",
|
|
95
96
|
value: function buildFooter(footer) {
|
|
96
97
|
const element = _classPrivateFieldLooseBase(this, _findOrCreateComponent)[_findOrCreateComponent]('[data-form-footer]', 'footer');
|
|
97
|
-
element
|
|
98
|
+
(0, _sanitize_html.setSanitizedRichText)(element, footer.content);
|
|
98
99
|
if (this.element.querySelector('[data-form-footer]')) {
|
|
99
100
|
this.element.querySelector('[data-form-footer]').replaceWith(element);
|
|
100
101
|
} else {
|
package/lib/models/form.js
CHANGED
|
@@ -14,6 +14,7 @@ function _classPrivateFieldLooseKey(name) { return "__private_" + id++ + "_" + n
|
|
|
14
14
|
import Hellotext from '../hellotext';
|
|
15
15
|
import { InputBuilder } from '../builders/input_builder';
|
|
16
16
|
import { LogoBuilder } from '../builders/logo_builder';
|
|
17
|
+
import { setSanitizedRichText } from '../core/sanitize_html';
|
|
17
18
|
var _findOrCreateComponent = /*#__PURE__*/_classPrivateFieldLooseKey("findOrCreateComponent");
|
|
18
19
|
var Form = /*#__PURE__*/function () {
|
|
19
20
|
function Form(data) {
|
|
@@ -63,7 +64,7 @@ var Form = /*#__PURE__*/function () {
|
|
|
63
64
|
key: "buildHeader",
|
|
64
65
|
value: function buildHeader(header) {
|
|
65
66
|
var headerElement = _classPrivateFieldLooseBase(this, _findOrCreateComponent)[_findOrCreateComponent]('[data-form-header]', 'header');
|
|
66
|
-
headerElement
|
|
67
|
+
setSanitizedRichText(headerElement, header.content);
|
|
67
68
|
if (this.element.querySelector('[data-form-header]')) {
|
|
68
69
|
this.element.querySelector('[data-form-header]').replaceWith(headerElement);
|
|
69
70
|
} else {
|
|
@@ -99,7 +100,7 @@ var Form = /*#__PURE__*/function () {
|
|
|
99
100
|
key: "buildFooter",
|
|
100
101
|
value: function buildFooter(footer) {
|
|
101
102
|
var element = _classPrivateFieldLooseBase(this, _findOrCreateComponent)[_findOrCreateComponent]('[data-form-footer]', 'footer');
|
|
102
|
-
element
|
|
103
|
+
setSanitizedRichText(element, footer.content);
|
|
103
104
|
if (this.element.querySelector('[data-form-footer]')) {
|
|
104
105
|
this.element.querySelector('[data-form-footer]').replaceWith(element);
|
|
105
106
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hellotext/hellotext",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.3",
|
|
4
4
|
"description": "Hellotext JavaScript Client",
|
|
5
5
|
"source": "src/index.js",
|
|
6
6
|
"main": "lib/index.cjs",
|
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
"@emoji-mart/data": "^1.2.1",
|
|
95
95
|
"@floating-ui/dom": "^1.7.3",
|
|
96
96
|
"@hotwired/stimulus": "^3.0.0",
|
|
97
|
+
"dompurify": "^3.4.13",
|
|
97
98
|
"emoji-mart": "^5.6.0"
|
|
98
99
|
},
|
|
99
100
|
"packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610",
|
|
@@ -6,6 +6,7 @@ export default class extends Controller {
|
|
|
6
6
|
static values = {
|
|
7
7
|
fadeDistance: { type: Number, default: 64 },
|
|
8
8
|
id: String,
|
|
9
|
+
kind: String,
|
|
9
10
|
pageStartOffset: { type: Number, default: 0 },
|
|
10
11
|
}
|
|
11
12
|
|
|
@@ -51,7 +52,14 @@ export default class extends Controller {
|
|
|
51
52
|
const { id, reference, source } = card.dataset
|
|
52
53
|
const item = { product: id, quantity: 1 }
|
|
53
54
|
|
|
54
|
-
Hellotext.track('cart.added', {
|
|
55
|
+
Hellotext.track('cart.added', {
|
|
56
|
+
object_parameters: { items: [item] },
|
|
57
|
+
source: {
|
|
58
|
+
kind: this.kindValue,
|
|
59
|
+
message_id: this.idValue,
|
|
60
|
+
button_id: currentTarget.dataset.id,
|
|
61
|
+
},
|
|
62
|
+
})
|
|
55
63
|
|
|
56
64
|
Hellotext.eventEmitter.dispatch('cart.added', {
|
|
57
65
|
object_parameters: {
|
|
@@ -7,6 +7,10 @@ import Hellotext from '../hellotext'
|
|
|
7
7
|
|
|
8
8
|
import { Locale } from '../core'
|
|
9
9
|
import { Webchat as WebchatConfiguration, modes } from '../core/configuration/webchat'
|
|
10
|
+
import {
|
|
11
|
+
sanitizedWebchatComponentFragment,
|
|
12
|
+
setSanitizedRichText,
|
|
13
|
+
} from '../core/sanitize_html'
|
|
10
14
|
|
|
11
15
|
import { usePopover } from './mixins/usePopover'
|
|
12
16
|
import { useBehaviour } from './webchat/useBehaviour'
|
|
@@ -333,9 +337,6 @@ export default class extends Controller {
|
|
|
333
337
|
const { body, attachments } = message
|
|
334
338
|
const createdAt = message.created_at || message.createdAt
|
|
335
339
|
|
|
336
|
-
const div = document.createElement('div')
|
|
337
|
-
div.innerHTML = body
|
|
338
|
-
|
|
339
340
|
const element = this.messageTemplateTarget.cloneNode(true)
|
|
340
341
|
|
|
341
342
|
element.classList.add('hellotext--webchat-message')
|
|
@@ -348,7 +349,7 @@ export default class extends Controller {
|
|
|
348
349
|
|
|
349
350
|
element.style.removeProperty('display')
|
|
350
351
|
|
|
351
|
-
element.querySelector('[data-body]')
|
|
352
|
+
setSanitizedRichText(element.querySelector('[data-body]'), body)
|
|
352
353
|
|
|
353
354
|
if (message.state === 'received') {
|
|
354
355
|
element.classList.add('received')
|
|
@@ -497,14 +498,11 @@ export default class extends Controller {
|
|
|
497
498
|
return this.insertCarouselMessage(message, options)
|
|
498
499
|
}
|
|
499
500
|
|
|
500
|
-
const div = document.createElement('div')
|
|
501
|
-
div.innerHTML = body
|
|
502
|
-
|
|
503
501
|
const element = this.messageTemplateTarget.cloneNode(true)
|
|
504
502
|
element.classList.add('hellotext--webchat-message')
|
|
505
503
|
element.style.display = 'flex'
|
|
506
504
|
|
|
507
|
-
element.querySelector('[data-body]')
|
|
505
|
+
setSanitizedRichText(element.querySelector('[data-body]'), body)
|
|
508
506
|
|
|
509
507
|
element.setAttribute('data-id', id)
|
|
510
508
|
element.setAttribute('data-hellotext--webchat-target', 'message')
|
|
@@ -633,7 +631,7 @@ export default class extends Controller {
|
|
|
633
631
|
}
|
|
634
632
|
|
|
635
633
|
this.teaserMessageTargets.forEach(teaserMessage => teaserMessage.classList.add('hidden'))
|
|
636
|
-
this.inboundMessageTeaserBodyTarget.
|
|
634
|
+
this.inboundMessageTeaserBodyTarget.textContent = this.messageTeaserValue
|
|
637
635
|
this.inboundMessageTeaserTarget.classList.remove('hidden')
|
|
638
636
|
|
|
639
637
|
this.teaserTarget.classList.toggle('invisible', this.openValue)
|
|
@@ -642,7 +640,7 @@ export default class extends Controller {
|
|
|
642
640
|
insertCarouselMessage(message, options = {}) {
|
|
643
641
|
const html = message.html
|
|
644
642
|
const createdAt = message.created_at || message.createdAt
|
|
645
|
-
const element =
|
|
643
|
+
const element = sanitizedWebchatComponentFragment(html).firstElementChild
|
|
646
644
|
|
|
647
645
|
element.classList.add('hellotext--webchat-message')
|
|
648
646
|
element.setAttribute('data-id', message.id)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import DOMPurify from 'dompurify'
|
|
2
|
+
|
|
3
|
+
const RICH_TEXT_SANITIZER_OPTIONS = {
|
|
4
|
+
ADD_ATTR: ['target'],
|
|
5
|
+
ALLOW_DATA_ATTR: false,
|
|
6
|
+
RETURN_DOM_FRAGMENT: true,
|
|
7
|
+
USE_PROFILES: { html: true },
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const WEBCHAT_COMPONENT_SANITIZER_OPTIONS = {
|
|
11
|
+
ADD_ATTR: ['target'],
|
|
12
|
+
ADD_TAGS: ['hellotext-icon'],
|
|
13
|
+
RETURN_DOM_FRAGMENT: true,
|
|
14
|
+
USE_PROFILES: { html: true, svg: true },
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function sanitizedFragment(html, options) {
|
|
18
|
+
const fragment = DOMPurify.sanitize(html, options)
|
|
19
|
+
|
|
20
|
+
fragment.querySelectorAll('a[target="_blank"]').forEach(link => {
|
|
21
|
+
const rel = new Set(link.rel.split(/\s+/).filter(Boolean))
|
|
22
|
+
rel.add('noopener')
|
|
23
|
+
rel.add('noreferrer')
|
|
24
|
+
link.rel = Array.from(rel).join(' ')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
return fragment
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function sanitizedRichTextFragment(html) {
|
|
31
|
+
return sanitizedFragment(html, RICH_TEXT_SANITIZER_OPTIONS)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function sanitizedWebchatComponentFragment(html) {
|
|
35
|
+
return sanitizedFragment(html, WEBCHAT_COMPONENT_SANITIZER_OPTIONS)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function setSanitizedRichText(element, html) {
|
|
39
|
+
element.replaceChildren(sanitizedRichTextFragment(html))
|
|
40
|
+
}
|
package/src/models/form.js
CHANGED
|
@@ -2,6 +2,7 @@ import Hellotext from '../hellotext'
|
|
|
2
2
|
|
|
3
3
|
import { InputBuilder } from '../builders/input_builder'
|
|
4
4
|
import { LogoBuilder } from '../builders/logo_builder'
|
|
5
|
+
import { setSanitizedRichText } from '../core/sanitize_html'
|
|
5
6
|
|
|
6
7
|
class Form {
|
|
7
8
|
constructor(data, element = null) {
|
|
@@ -44,7 +45,7 @@ class Form {
|
|
|
44
45
|
|
|
45
46
|
buildHeader(header) {
|
|
46
47
|
const headerElement = this.#findOrCreateComponent('[data-form-header]', 'header')
|
|
47
|
-
headerElement
|
|
48
|
+
setSanitizedRichText(headerElement, header.content)
|
|
48
49
|
|
|
49
50
|
if (this.element.querySelector('[data-form-header]')) {
|
|
50
51
|
this.element.querySelector('[data-form-header]').replaceWith(headerElement)
|
|
@@ -86,7 +87,7 @@ class Form {
|
|
|
86
87
|
|
|
87
88
|
buildFooter(footer) {
|
|
88
89
|
const element = this.#findOrCreateComponent('[data-form-footer]', 'footer')
|
|
89
|
-
element
|
|
90
|
+
setSanitizedRichText(element, footer.content)
|
|
90
91
|
|
|
91
92
|
if (this.element.querySelector('[data-form-footer]')) {
|
|
92
93
|
this.element.querySelector('[data-form-footer]').replaceWith(element)
|