@hellotext/hellotext 2.1.0 → 2.1.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.
@@ -45,6 +45,8 @@ let _default = /*#__PURE__*/function (_Controller) {
45
45
  this.onAgentOnline = this.onAgentOnline.bind(this);
46
46
  this.onMessageReaction = this.onMessageReaction.bind(this);
47
47
  this.onScroll = this.onScroll.bind(this);
48
+ this.onOutboundMessageSent = this.onOutboundMessageSent.bind(this);
49
+ this.broadcastChannel = new BroadcastChannel(`hellotext--webchat--${this.idValue}`);
48
50
  _get(_getPrototypeOf(_default.prototype), "initialize", this).call(this);
49
51
  }
50
52
  }, {
@@ -69,15 +71,43 @@ let _default = /*#__PURE__*/function (_Controller) {
69
71
  this.openValue = true;
70
72
  }
71
73
  _hellotext.default.eventEmitter.dispatch('webchat:mounted');
74
+ this.broadcastChannel.addEventListener('message', this.onOutboundMessageSent);
72
75
  _get(_getPrototypeOf(_default.prototype), "connect", this).call(this);
73
76
  }
74
77
  }, {
75
78
  key: "disconnect",
76
79
  value: function disconnect() {
80
+ this.broadcastChannel.removeEventListener('message', this.onOutboundMessageSent);
77
81
  this.messagesContainerTarget.removeEventListener('scroll', this.onScroll);
82
+ this.broadcastChannel.close();
78
83
  this.floatingUICleanup();
79
84
  _get(_getPrototypeOf(_default.prototype), "disconnect", this).call(this);
80
85
  }
86
+ }, {
87
+ key: "onOutboundMessageSent",
88
+ value: function onOutboundMessageSent(event) {
89
+ const {
90
+ data
91
+ } = event;
92
+ const callbacks = {
93
+ 'message:sent': data => {
94
+ const element = new DOMParser().parseFromString(data.element, 'text/html').body.firstElementChild;
95
+ this.messagesContainerTarget.appendChild(element);
96
+ element.scrollIntoView({
97
+ behavior: 'instant'
98
+ });
99
+ },
100
+ 'message:failed': data => {
101
+ var _this$messagesContain;
102
+ (_this$messagesContain = this.messagesContainerTarget.querySelector(`#${data.id}`)) === null || _this$messagesContain === void 0 ? void 0 : _this$messagesContain.classList.add('failed');
103
+ }
104
+ };
105
+ if (callbacks[data.type]) {
106
+ callbacks[data.type](data);
107
+ } else {
108
+ console.log(`Unhandled message event: ${data.type}`);
109
+ }
110
+ }
81
111
  }, {
82
112
  key: "onScroll",
83
113
  value: async function onScroll() {
@@ -139,9 +169,11 @@ let _default = /*#__PURE__*/function (_Controller) {
139
169
  value: function onPopoverOpened() {
140
170
  this.inputTarget.focus();
141
171
  if (!this.scrolled) {
142
- this.messagesContainerTarget.scroll({
143
- top: this.messagesContainerTarget.scrollHeight,
144
- behavior: 'instant'
172
+ requestAnimationFrame(() => {
173
+ this.messagesContainerTarget.scroll({
174
+ top: this.messagesContainerTarget.scrollHeight,
175
+ behavior: 'instant'
176
+ });
145
177
  });
146
178
  this.scrolled = true;
147
179
  }
@@ -247,16 +279,28 @@ let _default = /*#__PURE__*/function (_Controller) {
247
279
  body: this.inputTarget.value,
248
280
  attachments: this.files
249
281
  };
250
- formData.append('message[body]', this.inputTarget.value);
282
+ if (this.inputTarget.value.trim().length === 0 && this.files.length === 0) {
283
+ return;
284
+ }
285
+ if (this.inputTarget.value.trim().length > 0) {
286
+ formData.append('message[body]', this.inputTarget.value);
287
+ } else {
288
+ delete message.body;
289
+ }
251
290
  this.files.forEach(file => {
252
291
  formData.append('message[attachments][]', file);
253
292
  });
254
293
  formData.append('session', _hellotext.default.session);
255
294
  const element = this.messageTemplateTarget.cloneNode(true);
295
+ element.id = `hellotext--webchat--${this.idValue}--message--${Date.now()}`;
256
296
  element.classList.add('received');
257
297
  element.style.removeProperty('display');
258
298
  element.setAttribute('data-hellotext--webchat-target', 'message');
259
- element.querySelector('[data-body]').innerText = this.inputTarget.value;
299
+ if (this.inputTarget.value.trim().length > 0) {
300
+ element.querySelector('[data-body]').innerText = this.inputTarget.value;
301
+ } else {
302
+ element.querySelector('[data-message-bubble]').remove();
303
+ }
260
304
  const attachments = this.attachmentContainerTarget.querySelectorAll('img');
261
305
  if (attachments.length > 0) {
262
306
  attachments.forEach(attachment => {
@@ -267,13 +311,23 @@ let _default = /*#__PURE__*/function (_Controller) {
267
311
  element.scrollIntoView({
268
312
  behavior: 'smooth'
269
313
  });
314
+ this.broadcastChannel.postMessage({
315
+ type: 'message:sent',
316
+ element: element.outerHTML
317
+ });
270
318
  this.inputTarget.value = '';
271
319
  this.files = [];
320
+ this.attachmentInputTarget.value = '';
321
+ this.attachmentContainerTarget.innerHTML = '';
272
322
  this.attachmentContainerTarget.style.display = 'none';
273
323
  this.errorMessageContainerTarget.style.display = 'none';
274
324
  this.inputTarget.focus();
275
325
  const response = await this.messagesAPI.create(formData);
276
326
  if (response.failed) {
327
+ this.broadcastChannel.postMessage({
328
+ type: 'message:failed',
329
+ id: element.id
330
+ });
277
331
  return element.classList.add('failed');
278
332
  }
279
333
  const data = await response.json();
@@ -295,8 +349,9 @@ let _default = /*#__PURE__*/function (_Controller) {
295
349
  key: "onFileInputChange",
296
350
  value: function onFileInputChange() {
297
351
  this.errorMessageContainerTarget.style.display = 'none';
298
- this.files = Array.from(this.attachmentInputTarget.files);
299
- const fileMaxSizeTooMuch = this.files.find(file => {
352
+ const newFiles = Array.from(this.attachmentInputTarget.files);
353
+ this.attachmentInputTarget.value = '';
354
+ const fileMaxSizeTooMuch = newFiles.find(file => {
300
355
  const type = file.type.split('/')[0];
301
356
  if (['image', 'video', 'audio'].includes(type)) {
302
357
  return this.mediaValue[type].max_size < file.size;
@@ -308,10 +363,11 @@ let _default = /*#__PURE__*/function (_Controller) {
308
363
  const type = fileMaxSizeTooMuch.type.split('/')[0];
309
364
  const mediaType = ['image', 'audio', 'video'].includes(type) ? type : 'document';
310
365
  this.errorMessageContainerTarget.innerText = this.fileSizeErrorMessageValue.replace('%{limit}', this.byteToMegabyte(this.mediaValue[mediaType].max_size));
311
- return;
366
+ return this.errorMessageContainerTarget.style.display = 'block';
312
367
  }
368
+ this.files = [...this.files, ...newFiles];
313
369
  this.errorMessageContainerTarget.innerText = '';
314
- this.files.forEach(file => this.createAttachmentElement(file));
370
+ newFiles.forEach(file => this.createAttachmentElement(file));
315
371
  this.inputTarget.focus();
316
372
  }
317
373
  }, {
@@ -334,6 +390,8 @@ let _default = /*#__PURE__*/function (_Controller) {
334
390
  main.style.backgroundColor = '#e5e7eb';
335
391
  main.style.padding = '0.25rem';
336
392
  element.querySelector('p[data-attachment-name]').innerText = file.name;
393
+ this.attachmentContainerTarget.appendChild(element);
394
+ this.attachmentContainerTarget.style.display = 'flex';
337
395
  }
338
396
  }
339
397
  }, {
@@ -343,6 +401,7 @@ let _default = /*#__PURE__*/function (_Controller) {
343
401
  }) {
344
402
  const attachment = currentTarget.closest("[data-hellotext--webchat-target='attachment']");
345
403
  this.files = this.files.filter(file => file.name !== attachment.dataset.name);
404
+ this.attachmentInputTarget.value = '';
346
405
  attachment.remove();
347
406
  this.inputTarget.focus();
348
407
  }
@@ -43,6 +43,8 @@ var _default = /*#__PURE__*/function (_Controller) {
43
43
  this.onAgentOnline = this.onAgentOnline.bind(this);
44
44
  this.onMessageReaction = this.onMessageReaction.bind(this);
45
45
  this.onScroll = this.onScroll.bind(this);
46
+ this.onOutboundMessageSent = this.onOutboundMessageSent.bind(this);
47
+ this.broadcastChannel = new BroadcastChannel("hellotext--webchat--".concat(this.idValue));
46
48
  _get(_getPrototypeOf(_default.prototype), "initialize", this).call(this);
47
49
  }
48
50
  }, {
@@ -67,15 +69,43 @@ var _default = /*#__PURE__*/function (_Controller) {
67
69
  this.openValue = true;
68
70
  }
69
71
  Hellotext.eventEmitter.dispatch('webchat:mounted');
72
+ this.broadcastChannel.addEventListener('message', this.onOutboundMessageSent);
70
73
  _get(_getPrototypeOf(_default.prototype), "connect", this).call(this);
71
74
  }
72
75
  }, {
73
76
  key: "disconnect",
74
77
  value: function disconnect() {
78
+ this.broadcastChannel.removeEventListener('message', this.onOutboundMessageSent);
75
79
  this.messagesContainerTarget.removeEventListener('scroll', this.onScroll);
80
+ this.broadcastChannel.close();
76
81
  this.floatingUICleanup();
77
82
  _get(_getPrototypeOf(_default.prototype), "disconnect", this).call(this);
78
83
  }
84
+ }, {
85
+ key: "onOutboundMessageSent",
86
+ value: function onOutboundMessageSent(event) {
87
+ var {
88
+ data
89
+ } = event;
90
+ var callbacks = {
91
+ 'message:sent': data => {
92
+ var element = new DOMParser().parseFromString(data.element, 'text/html').body.firstElementChild;
93
+ this.messagesContainerTarget.appendChild(element);
94
+ element.scrollIntoView({
95
+ behavior: 'instant'
96
+ });
97
+ },
98
+ 'message:failed': data => {
99
+ var _this$messagesContain;
100
+ (_this$messagesContain = this.messagesContainerTarget.querySelector("#".concat(data.id))) === null || _this$messagesContain === void 0 ? void 0 : _this$messagesContain.classList.add('failed');
101
+ }
102
+ };
103
+ if (callbacks[data.type]) {
104
+ callbacks[data.type](data);
105
+ } else {
106
+ console.log("Unhandled message event: ".concat(data.type));
107
+ }
108
+ }
79
109
  }, {
80
110
  key: "onScroll",
81
111
  value: function () {
@@ -143,9 +173,11 @@ var _default = /*#__PURE__*/function (_Controller) {
143
173
  value: function onPopoverOpened() {
144
174
  this.inputTarget.focus();
145
175
  if (!this.scrolled) {
146
- this.messagesContainerTarget.scroll({
147
- top: this.messagesContainerTarget.scrollHeight,
148
- behavior: 'instant'
176
+ requestAnimationFrame(() => {
177
+ this.messagesContainerTarget.scroll({
178
+ top: this.messagesContainerTarget.scrollHeight,
179
+ behavior: 'instant'
180
+ });
149
181
  });
150
182
  this.scrolled = true;
151
183
  }
@@ -251,16 +283,28 @@ var _default = /*#__PURE__*/function (_Controller) {
251
283
  body: this.inputTarget.value,
252
284
  attachments: this.files
253
285
  };
254
- formData.append('message[body]', this.inputTarget.value);
286
+ if (this.inputTarget.value.trim().length === 0 && this.files.length === 0) {
287
+ return;
288
+ }
289
+ if (this.inputTarget.value.trim().length > 0) {
290
+ formData.append('message[body]', this.inputTarget.value);
291
+ } else {
292
+ delete message.body;
293
+ }
255
294
  this.files.forEach(file => {
256
295
  formData.append('message[attachments][]', file);
257
296
  });
258
297
  formData.append('session', Hellotext.session);
259
298
  var element = this.messageTemplateTarget.cloneNode(true);
299
+ element.id = "hellotext--webchat--".concat(this.idValue, "--message--").concat(Date.now());
260
300
  element.classList.add('received');
261
301
  element.style.removeProperty('display');
262
302
  element.setAttribute('data-hellotext--webchat-target', 'message');
263
- element.querySelector('[data-body]').innerText = this.inputTarget.value;
303
+ if (this.inputTarget.value.trim().length > 0) {
304
+ element.querySelector('[data-body]').innerText = this.inputTarget.value;
305
+ } else {
306
+ element.querySelector('[data-message-bubble]').remove();
307
+ }
264
308
  var attachments = this.attachmentContainerTarget.querySelectorAll('img');
265
309
  if (attachments.length > 0) {
266
310
  attachments.forEach(attachment => {
@@ -271,13 +315,23 @@ var _default = /*#__PURE__*/function (_Controller) {
271
315
  element.scrollIntoView({
272
316
  behavior: 'smooth'
273
317
  });
318
+ this.broadcastChannel.postMessage({
319
+ type: 'message:sent',
320
+ element: element.outerHTML
321
+ });
274
322
  this.inputTarget.value = '';
275
323
  this.files = [];
324
+ this.attachmentInputTarget.value = '';
325
+ this.attachmentContainerTarget.innerHTML = '';
276
326
  this.attachmentContainerTarget.style.display = 'none';
277
327
  this.errorMessageContainerTarget.style.display = 'none';
278
328
  this.inputTarget.focus();
279
329
  var response = yield this.messagesAPI.create(formData);
280
330
  if (response.failed) {
331
+ this.broadcastChannel.postMessage({
332
+ type: 'message:failed',
333
+ id: element.id
334
+ });
281
335
  return element.classList.add('failed');
282
336
  }
283
337
  var data = yield response.json();
@@ -304,8 +358,9 @@ var _default = /*#__PURE__*/function (_Controller) {
304
358
  key: "onFileInputChange",
305
359
  value: function onFileInputChange() {
306
360
  this.errorMessageContainerTarget.style.display = 'none';
307
- this.files = Array.from(this.attachmentInputTarget.files);
308
- var fileMaxSizeTooMuch = this.files.find(file => {
361
+ var newFiles = Array.from(this.attachmentInputTarget.files);
362
+ this.attachmentInputTarget.value = '';
363
+ var fileMaxSizeTooMuch = newFiles.find(file => {
309
364
  var type = file.type.split('/')[0];
310
365
  if (['image', 'video', 'audio'].includes(type)) {
311
366
  return this.mediaValue[type].max_size < file.size;
@@ -317,10 +372,11 @@ var _default = /*#__PURE__*/function (_Controller) {
317
372
  var type = fileMaxSizeTooMuch.type.split('/')[0];
318
373
  var mediaType = ['image', 'audio', 'video'].includes(type) ? type : 'document';
319
374
  this.errorMessageContainerTarget.innerText = this.fileSizeErrorMessageValue.replace('%{limit}', this.byteToMegabyte(this.mediaValue[mediaType].max_size));
320
- return;
375
+ return this.errorMessageContainerTarget.style.display = 'block';
321
376
  }
377
+ this.files = [...this.files, ...newFiles];
322
378
  this.errorMessageContainerTarget.innerText = '';
323
- this.files.forEach(file => this.createAttachmentElement(file));
379
+ newFiles.forEach(file => this.createAttachmentElement(file));
324
380
  this.inputTarget.focus();
325
381
  }
326
382
  }, {
@@ -343,6 +399,8 @@ var _default = /*#__PURE__*/function (_Controller) {
343
399
  main.style.backgroundColor = '#e5e7eb';
344
400
  main.style.padding = '0.25rem';
345
401
  element.querySelector('p[data-attachment-name]').innerText = file.name;
402
+ this.attachmentContainerTarget.appendChild(element);
403
+ this.attachmentContainerTarget.style.display = 'flex';
346
404
  }
347
405
  }
348
406
  }, {
@@ -353,6 +411,7 @@ var _default = /*#__PURE__*/function (_Controller) {
353
411
  } = _ref;
354
412
  var attachment = currentTarget.closest("[data-hellotext--webchat-target='attachment']");
355
413
  this.files = this.files.filter(file => file.name !== attachment.dataset.name);
414
+ this.attachmentInputTarget.value = '';
356
415
  attachment.remove();
357
416
  this.inputTarget.focus();
358
417
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hellotext/hellotext",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "Hellotext JavaScript Client",
5
5
  "source": "src/index.js",
6
6
  "main": "lib/index.cjs",
@@ -62,6 +62,9 @@ export default class extends Controller {
62
62
 
63
63
  this.onScroll = this.onScroll.bind(this)
64
64
 
65
+ this.onOutboundMessageSent = this.onOutboundMessageSent.bind(this)
66
+ this.broadcastChannel = new BroadcastChannel(`hellotext--webchat--${this.idValue}`)
67
+
65
68
  super.initialize()
66
69
  }
67
70
 
@@ -90,16 +93,44 @@ export default class extends Controller {
90
93
  }
91
94
 
92
95
  Hellotext.eventEmitter.dispatch('webchat:mounted')
96
+ this.broadcastChannel.addEventListener('message', this.onOutboundMessageSent)
97
+
93
98
  super.connect()
94
99
  }
95
100
 
96
101
  disconnect() {
102
+ this.broadcastChannel.removeEventListener('message', this.onOutboundMessageSent)
97
103
  this.messagesContainerTarget.removeEventListener('scroll', this.onScroll)
104
+
105
+ this.broadcastChannel.close()
98
106
  this.floatingUICleanup()
99
107
 
100
108
  super.disconnect()
101
109
  }
102
110
 
111
+ onOutboundMessageSent(event) {
112
+ const { data } = event
113
+
114
+ const callbacks = {
115
+ 'message:sent': data => {
116
+ const element = new DOMParser().parseFromString(data.element, 'text/html').body
117
+ .firstElementChild
118
+
119
+ this.messagesContainerTarget.appendChild(element)
120
+ element.scrollIntoView({ behavior: 'instant' })
121
+ },
122
+ 'message:failed': data => {
123
+ this.messagesContainerTarget.querySelector(`#${data.id}`)?.classList.add('failed')
124
+ },
125
+ }
126
+
127
+ if (callbacks[data.type]) {
128
+ callbacks[data.type](data)
129
+ } else {
130
+ console.log(`Unhandled message event: ${data.type}`)
131
+ }
132
+ }
133
+
103
134
  async onScroll() {
104
135
  if (
105
136
  this.messagesContainerTarget.scrollTop > 300 ||
@@ -178,9 +209,11 @@ export default class extends Controller {
178
209
  this.inputTarget.focus()
179
210
 
180
211
  if (!this.scrolled) {
181
- this.messagesContainerTarget.scroll({
182
- top: this.messagesContainerTarget.scrollHeight,
183
- behavior: 'instant',
212
+ requestAnimationFrame(() => {
213
+ this.messagesContainerTarget.scroll({
214
+ top: this.messagesContainerTarget.scrollHeight,
215
+ behavior: 'instant',
216
+ })
184
217
  })
185
218
 
186
219
  this.scrolled = true
@@ -297,7 +330,15 @@ export default class extends Controller {
297
330
  attachments: this.files,
298
331
  }
299
332
 
300
- formData.append('message[body]', this.inputTarget.value)
333
+ if (this.inputTarget.value.trim().length === 0 && this.files.length === 0) {
334
+ return
335
+ }
336
+
337
+ if (this.inputTarget.value.trim().length > 0) {
338
+ formData.append('message[body]', this.inputTarget.value)
339
+ } else {
340
+ delete message.body
341
+ }
301
342
 
302
343
  this.files.forEach(file => {
303
344
  formData.append('message[attachments][]', file)
@@ -307,11 +348,18 @@ export default class extends Controller {
307
348
 
308
349
  const element = this.messageTemplateTarget.cloneNode(true)
309
350
 
351
+ element.id = `hellotext--webchat--${this.idValue}--message--${Date.now()}`
352
+
310
353
  element.classList.add('received')
311
354
  element.style.removeProperty('display')
312
355
 
313
356
  element.setAttribute('data-hellotext--webchat-target', 'message')
314
- element.querySelector('[data-body]').innerText = this.inputTarget.value
357
+
358
+ if (this.inputTarget.value.trim().length > 0) {
359
+ element.querySelector('[data-body]').innerText = this.inputTarget.value
360
+ } else {
361
+ element.querySelector('[data-message-bubble]').remove()
362
+ }
315
363
 
316
364
  const attachments = this.attachmentContainerTarget.querySelectorAll('img')
317
365
 
@@ -324,8 +372,17 @@ export default class extends Controller {
324
372
  this.messagesContainerTarget.appendChild(element)
325
373
  element.scrollIntoView({ behavior: 'smooth' })
326
374
 
375
+ this.broadcastChannel.postMessage({
376
+ type: 'message:sent',
377
+ element: element.outerHTML,
378
+ })
379
+
327
380
  this.inputTarget.value = ''
328
381
  this.files = []
382
+
383
+ this.attachmentInputTarget.value = ''
384
+ this.attachmentContainerTarget.innerHTML = ''
385
+
329
386
  this.attachmentContainerTarget.style.display = 'none'
330
387
  this.errorMessageContainerTarget.style.display = 'none'
331
388
 
@@ -334,6 +391,11 @@ export default class extends Controller {
334
391
  const response = await this.messagesAPI.create(formData)
335
392
 
336
393
  if (response.failed) {
394
+ this.broadcastChannel.postMessage({
395
+ type: 'message:failed',
396
+ id: element.id,
397
+ })
398
+
337
399
  return element.classList.add('failed')
338
400
  }
339
401
 
@@ -358,9 +420,10 @@ export default class extends Controller {
358
420
  onFileInputChange() {
359
421
  this.errorMessageContainerTarget.style.display = 'none'
360
422
 
361
- this.files = Array.from(this.attachmentInputTarget.files)
423
+ const newFiles = Array.from(this.attachmentInputTarget.files)
424
+ this.attachmentInputTarget.value = ''
362
425
 
363
- const fileMaxSizeTooMuch = this.files.find(file => {
426
+ const fileMaxSizeTooMuch = newFiles.find(file => {
364
427
  const type = file.type.split('/')[0]
365
428
 
366
429
  if (['image', 'video', 'audio'].includes(type)) {
@@ -378,11 +441,14 @@ export default class extends Controller {
378
441
  '%{limit}',
379
442
  this.byteToMegabyte(this.mediaValue[mediaType].max_size),
380
443
  )
381
- return
444
+
445
+ return (this.errorMessageContainerTarget.style.display = 'block')
382
446
  }
383
447
 
448
+ this.files = [...this.files, ...newFiles]
384
449
  this.errorMessageContainerTarget.innerText = ''
385
- this.files.forEach(file => this.createAttachmentElement(file))
450
+
451
+ newFiles.forEach(file => this.createAttachmentElement(file))
386
452
  this.inputTarget.focus()
387
453
  }
388
454
 
@@ -405,12 +471,16 @@ export default class extends Controller {
405
471
  this.attachmentContainerTarget.style.display = 'flex'
406
472
  } else {
407
473
  const main = element.querySelector('main')
474
+
408
475
  main.style.height = '5rem'
409
476
  main.style.borderRadius = '0.375rem'
410
477
  main.style.backgroundColor = '#e5e7eb'
411
478
  main.style.padding = '0.25rem'
412
479
 
413
480
  element.querySelector('p[data-attachment-name]').innerText = file.name
481
+
482
+ this.attachmentContainerTarget.appendChild(element)
483
+ this.attachmentContainerTarget.style.display = 'flex'
414
484
  }
415
485
  }
416
486
 
@@ -418,6 +488,7 @@ export default class extends Controller {
418
488
  const attachment = currentTarget.closest("[data-hellotext--webchat-target='attachment']")
419
489
 
420
490
  this.files = this.files.filter(file => file.name !== attachment.dataset.name)
491
+ this.attachmentInputTarget.value = ''
421
492
 
422
493
  attachment.remove()
423
494
  this.inputTarget.focus()