@seamly/web-ui 20.3.1 → 20.6.0

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +769 -0
  2. package/build/dist/lib/index.debug.js +66 -99
  3. package/build/dist/lib/index.debug.min.js +1 -1
  4. package/build/dist/lib/index.debug.min.js.LICENSE.txt +8 -20
  5. package/build/dist/lib/index.js +748 -4372
  6. package/build/dist/lib/index.min.js +1 -1
  7. package/build/dist/lib/index.min.js.LICENSE.txt +0 -5
  8. package/build/dist/lib/standalone.js +606 -4449
  9. package/build/dist/lib/standalone.min.js +1 -1
  10. package/build/dist/lib/style-guide.js +373 -178
  11. package/build/dist/lib/style-guide.min.js +1 -1
  12. package/build/dist/lib/styles.css +1 -1
  13. package/package.json +2 -4
  14. package/src/javascripts/api/index.js +17 -1
  15. package/src/javascripts/domains/config/reducer.js +2 -0
  16. package/src/javascripts/domains/forms/provider.js +14 -6
  17. package/src/javascripts/domains/store/index.js +11 -11
  18. package/src/javascripts/domains/store/state-reducer.js +1 -0
  19. package/src/javascripts/domains/translations/middleware.js +6 -5
  20. package/src/javascripts/domains/visibility/actions.js +2 -0
  21. package/src/javascripts/domains/visibility/hooks.js +60 -1
  22. package/src/javascripts/domains/visibility/reducer.js +5 -0
  23. package/src/javascripts/domains/visibility/selectors.js +5 -0
  24. package/src/javascripts/domains/visibility/utils.js +5 -1
  25. package/src/javascripts/style-guide/components/app.js +2 -0
  26. package/src/javascripts/style-guide/states.js +86 -51
  27. package/src/javascripts/ui/components/conversation/conversation.js +40 -36
  28. package/src/javascripts/ui/components/conversation/event/card-component.js +1 -2
  29. package/src/javascripts/ui/components/conversation/event/conversation-suggestions.js +19 -9
  30. package/src/javascripts/ui/components/conversation/event/cta.js +1 -2
  31. package/src/javascripts/ui/components/conversation/event/image.js +11 -3
  32. package/src/javascripts/ui/components/conversation/event/participant.js +2 -11
  33. package/src/javascripts/ui/components/conversation/event/splash.js +1 -3
  34. package/src/javascripts/ui/components/conversation/event/text.js +9 -9
  35. package/src/javascripts/ui/components/entry/text-entry/text-entry-form.js +6 -0
  36. package/src/javascripts/ui/components/layout/chat.js +52 -48
  37. package/src/javascripts/ui/components/suggestions/suggestions-list.js +12 -14
  38. package/src/javascripts/ui/components/view/deprecated-view.js +16 -11
  39. package/src/javascripts/ui/components/view/inline-view.js +13 -8
  40. package/src/javascripts/ui/hooks/seamly-entry-hooks.js +3 -2
  41. package/src/javascripts/ui/hooks/seamly-state-hooks.js +7 -3
  42. package/src/javascripts/ui/hooks/use-seamly-chat.js +41 -29
  43. package/src/javascripts/ui/hooks/use-seamly-commands.js +16 -4
  44. package/src/javascripts/ui/utils/seamly-utils.js +27 -7
  45. package/src/stylesheets/5-components/_message-count.scss +5 -2
  46. package/webpack/parts/dev-server.js +10 -1
  47. package/src/.DS_Store +0 -0
  48. package/src/javascripts/lib/parse-body.js +0 -10
  49. package/src/javascripts/ui/components/conversation/event/hooks/use-text-rendering.js +0 -35
@@ -17,6 +17,7 @@ const baseState = {
17
17
  },
18
18
  initialState: {},
19
19
  unreadEvents: 0,
20
+ loadedImageEventIds: [],
20
21
  isLoading: false,
21
22
  idleDetachCountdown: { hasCountdown: false, isActive: false },
22
23
  resumeConversationPrompt: false,
@@ -58,7 +59,9 @@ const baseState = {
58
59
  }
59
60
 
60
61
  const avatar =
61
- 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMnB4IiBoZWlnaHQ9IjMycHgiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMzIgMzIiPjxkZWZzLz48cGF0aCBmaWxsPSIjNEE0OEMxIiBkPSJNMCAwSDMyVjMySDB6Ii8+PHBhdGggZmlsbD0iI0ZGRiIgZD0iTTExLjE3NSwxMi4yMjJjMC0zLjc5MywyLjE2LTUuNTY1LDQuODI1LTUuNTY1YzIuNjY0LDAsNC44MjYsMS43NzIsNC44MjYsNS41NjUJYzAsMy43OTQtMi4xNjIsNi45MDQtNC44MjYsNi45MDRDMTMuMzM1LDE5LjEyNiwxMS4xNzUsMTYuMDE2LDExLjE3NSwxMi4yMjJ6IE0yNi42NjYsMjMuMDg0Yy0wLjgzNC0xLjU4My00LjIxNi0yLjYyNS01Ljg0MS0yLjY2NwlzLTMuNTM0LDMtNC44MjUsM2MtMS4yOTMsMC0zLjIwMS0zLjA0Mi00LjgyNS0zYy0xLjYyNSwwLjA0Mi01LjAwOCwxLjA4NC01Ljg0MSwyLjY2N0M0LjAwNSwyNS42MDYsNC41ODMsMzIsNC41ODMsMzJIMTZoMTEuNDE2CUMyNy40MTYsMzIsMjcuOTk0LDI1LjYwNiwyNi42NjYsMjMuMDg0eiIvPjwvc3ZnPg=='
62
+ 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAzMiAzMiIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMzIgMzIiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8cGF0aCBmaWxsPSIjNEE0OEMxIiBkPSJNMTAsMTQuOGMtMS4xLDAtMi0wLjktMi0yczAuOS0yLDItMnMyLDAuOSwyLDJTMTEuMSwxNC44LDEwLDE0Ljh6IE0xNS45LDE0LjhjMS4xLDAsMi0wLjksMi0ycy0wLjktMi0yLTIKCXMtMiwwLjktMiwyUzE0LjgsMTQuOCwxNS45LDE0Ljh6IE0yMS44LDEwLjhjLTEuMSwwLTIsMC45LTIsMnMwLjksMiwyLDJzMi0wLjksMi0yUzIyLjksMTAuOCwyMS44LDEwLjh6IE0yMS4zLDE4LjFIMTAuNwoJQzExLjcsMjMuOSwyMC4yLDIzLjksMjEuMywxOC4xeiIvPgo8L3N2Zz4K'
63
+ const transferAvatar =
64
+ 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAzMiAzMiIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMzIgMzIiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8cGF0aCBmaWxsPSIjNEE0OEMxIiBkPSJNMTAuMSwxNC45Yy0xLjEsMC0yLTAuOS0yLTJzMC45LTIsMi0yczIsMC45LDIsMlMxMS4yLDE0LjksMTAuMSwxNC45eiBNMjEuOSwxMC45Yy0xLjEsMC0yLDAuOS0yLDIKCXMwLjksMiwyLDJzMi0wLjksMi0yUzIzLDEwLjksMjEuOSwxMC45eiBNMjEuNCwxOC4ySDEwLjhDMTEuOCwyNCwyMC4zLDI0LDIxLjQsMTguMnogTTE3LjIsMjUuM2gtMi40Yy0wLjYsMC0xLjIsMC40LTEuNCwxCgljLTUuMS0wLjItOS4yLTQuNC05LjQtOS42YzAuMSwwLDAuMiwwLjEsMC40LDAuMWgwLjFjMC44LDAsMS40LTAuNiwxLjQtMS40di00LjdjMC0wLjgtMC42LTEuNC0xLjQtMS40aDBjLTAuOCwwLTEuNCwwLjYtMS40LDEuNAoJdjIuN3YydjEuMWMwLDUuOSw0LjcsMTAuNiwxMC41LDEwLjhjMC4yLDAuNSwwLjcsMC44LDEuMiwwLjhoMi40YzAuOCwwLDEuNC0wLjYsMS40LTEuNFMxOCwyNS4zLDE3LjIsMjUuM3oiLz4KPC9zdmc+Cg=='
62
65
 
63
66
  const participantInfo = {
64
67
  participants: {
@@ -155,7 +158,6 @@ const infoMessage = {
155
158
  body: {
156
159
  text: 'This is a system generated info message',
157
160
  type: 'text',
158
- variables: {},
159
161
  },
160
162
  fromClient: false,
161
163
  id: randomId(),
@@ -198,7 +200,7 @@ const participantMessage = {
198
200
  participant: {
199
201
  avatar,
200
202
  id: 'agent',
201
- introduction: "You're now talking to {{name}} gimme a minit",
203
+ introduction: "You're now talking to Mrs. Bot gimme a minit",
202
204
  name: 'Mrs. Bot',
203
205
  service: {
204
206
  expose: { map: {}, version: 2 },
@@ -213,6 +215,32 @@ const participantMessage = {
213
215
  },
214
216
  }
215
217
 
218
+ const participantTransferMessage = {
219
+ type: 'participant',
220
+ payload: {
221
+ fromClient: false,
222
+ fromHistory: true,
223
+ id: randomId(),
224
+ messageStatus: 'received',
225
+ participant: {
226
+ introduction:
227
+ 'Welcome, you are now chatting with Mrs. Bot 2, give me a minit to read back the chat history.',
228
+ id: 'agent',
229
+ avatar: transferAvatar,
230
+ name: 'Two',
231
+ service: {
232
+ expose: { map: {}, version: 3 },
233
+ meta: {},
234
+ name: 'new service',
235
+ serviceSessionId: randomId(),
236
+ settings: {},
237
+ },
238
+ },
239
+ transactionId: randomId(),
240
+ type: 'participant',
241
+ },
242
+ }
243
+
216
244
  const participantMessageDefaultIcon = {
217
245
  type: 'participant',
218
246
  payload: {
@@ -222,7 +250,7 @@ const participantMessageDefaultIcon = {
222
250
  messageStatus: 'received',
223
251
  participant: {
224
252
  id: 'user',
225
- introduction: "You're now talking to {{name}} gimme a minit",
253
+ introduction: "You're now talking to Mrs. Bot gimme a minit",
226
254
  name: 'Mrs. Bot',
227
255
  service: {
228
256
  expose: { map: {}, version: 2 },
@@ -244,7 +272,6 @@ const getCustomMessage = ({ type, data, text }) => ({
244
272
  type,
245
273
  text,
246
274
  data,
247
- variables: {},
248
275
  },
249
276
  participant: 'agent',
250
277
  service: {
@@ -268,7 +295,6 @@ const shortTextMessage = {
268
295
  body: {
269
296
  text: 'What do you want to do?',
270
297
  type: 'text',
271
- variables: {},
272
298
  },
273
299
  fromClient: false,
274
300
  fromHistory: true,
@@ -303,7 +329,8 @@ const ctaMessage = {
303
329
  type: 'message',
304
330
  payload: {
305
331
  body: {
306
- description: 'Thanks for chatting!\n\nMore info about our **products**?',
332
+ description:
333
+ 'Thanks for chatting!\n\nMore info about our <strong>products</strong>?',
307
334
  buttonLink: 'https://seamly.ai',
308
335
  buttonText: 'View website',
309
336
  buttonNewTab: true,
@@ -330,7 +357,6 @@ const longTextMessage = {
330
357
  body: {
331
358
  text: 'What do you want to do? This is a really long message from a bot that has a lot to say about a lot of things. Currently I am contemplating my own bot existence and constantly asking myself who I am. What do you want to do? This is a really long message from a bot that has a lot to say about a lot of things. Currently I am contemplating my own bot existence and constantly asking myself who I am. What do you want to do? This is a really long message from a bot that has a lot to say about a lot of things. Currently I am contemplating my own bot existence and constantly asking myself who I am.',
332
359
  type: 'text',
333
- variables: {},
334
360
  },
335
361
  fromClient: false,
336
362
  fromHistory: true,
@@ -352,9 +378,8 @@ const textMessageBoldItalicUnderline = {
352
378
  type: 'message',
353
379
  payload: {
354
380
  body: {
355
- text: 'Bubble with **bold** *italic* <u>underline</u>',
381
+ text: 'Bubble with <strong>bold</strong> <em>italic</em> <u>underline</u>',
356
382
  type: 'text',
357
- variables: {},
358
383
  },
359
384
  fromClient: false,
360
385
  fromHistory: true,
@@ -376,24 +401,8 @@ const textMessageWithLinks = {
376
401
  type: 'message',
377
402
  payload: {
378
403
  body: {
379
- text: '{{link_1}} and {{link_2}} embedded in text',
404
+ text: '<a href="https://google.com" data-link-id="1">Link in same window</a> and <a href="https://google.com" data-link-id="2" target="_blank">link in new window</a> embedded in text',
380
405
  type: 'text',
381
- variables: {
382
- link_1: {
383
- id: '1',
384
- name: 'Link in same window',
385
- newTab: false,
386
- type: 'link',
387
- url: 'https://google.com',
388
- },
389
- link_2: {
390
- id: '2',
391
- name: 'link in new window',
392
- newTab: true,
393
- type: 'link',
394
- url: 'https://google.com',
395
- },
396
- },
397
406
  },
398
407
  fromClient: false,
399
408
  fromHistory: true,
@@ -415,17 +424,8 @@ const textMessageWithLongLink = {
415
424
  type: 'message',
416
425
  payload: {
417
426
  body: {
418
- text: 'Here is a long link {{link_1}} embedded in text',
427
+ text: 'Here is a long link <a href="https://google.com" data-link-id="1">click me click me please click me yoohoooo please please click me here I am click me now what are you waiting for click me now now now now now click meeeeeeeeeeeeee</a> embedded in text',
419
428
  type: 'text',
420
- variables: {
421
- link_1: {
422
- id: '1',
423
- name: 'click me click me please click me yoohoooo please please click me here I am click me now what are you waiting for click me now now now now now click meeeeeeeeeeeeee',
424
- newTab: false,
425
- type: 'link',
426
- url: 'https://google.com',
427
- },
428
- },
429
429
  },
430
430
  fromClient: false,
431
431
  fromHistory: true,
@@ -449,7 +449,6 @@ const textMesageWithBullets = {
449
449
  body: {
450
450
  text: '<ul>\n<li>Bullets</li>\n<li>bullets</li>\n<li>bullets</li>\n</ul>\n',
451
451
  type: 'text',
452
- variables: {},
453
452
  },
454
453
  fromClient: false,
455
454
  fromHistory: true,
@@ -604,7 +603,6 @@ const userMessage = {
604
603
  body: {
605
604
  text: 'This is what the user typed',
606
605
  type: 'text',
607
- variables: {},
608
606
  },
609
607
  fromClient: true,
610
608
  fromHistory: true,
@@ -623,7 +621,23 @@ const userMessageLong = {
623
621
  body: {
624
622
  text: 'This is what the user typed. And sometimes the user has quite a lot to say and then we get longer lines that need to wrap well and not break the styling so here goes with just such a line right here!!',
625
623
  type: 'text',
626
- variables: {},
624
+ },
625
+ fromClient: true,
626
+ fromHistory: true,
627
+ id: randomId(),
628
+ messageStatus: 'read',
629
+ participant: 'user',
630
+ transactionId: '1cdefea9-7437-4672-bcf8-2c75dc99244c',
631
+ transactionLast: null,
632
+ type: 'text',
633
+ },
634
+ }
635
+ const userMessageWithLinks = {
636
+ type: 'message',
637
+ payload: {
638
+ body: {
639
+ text: '<a href="https://google.com" data-link-id="1">Link in same window</a> and <a href="https://google.com" data-link-id="2" target="_blank">link in new window</a> embedded in text',
640
+ type: 'text',
627
641
  },
628
642
  fromClient: true,
629
643
  fromHistory: true,
@@ -835,7 +849,7 @@ const cardAskText = {
835
849
  },
836
850
  buttonText: 'Ask about pizzas!',
837
851
  description:
838
- 'Pizza Margherita is a **typical Neapolitan pizza**.\n\nIt is made with San Marzano tomatoes, mozzarella cheese, fresh basil, salt, and extra-virgin olive oil.',
852
+ 'Pizza Margherita is a <strong>typical Neapolitan pizza</strong>.\n\nIt is made with San Marzano tomatoes, mozzarella cheese, fresh basil, salt, and extra-virgin olive oil.',
839
853
  image:
840
854
  'https://developers.seamly.ai/clients/web-ui/static/photos/card-square.jpg',
841
855
  title: 'Pizza Margherita',
@@ -854,7 +868,8 @@ const cardNavigate = {
854
868
  type: 'navigate',
855
869
  },
856
870
  buttonText: 'Order now!',
857
- description: 'Pizza Margherita is a **typical Neapolitan pizza**.',
871
+ description:
872
+ 'Pizza Margherita is a <strong>typical Neapolitan pizza</strong>.',
858
873
  image:
859
874
  'https://developers.seamly.ai/clients/web-ui/static/photos/card-landscape.jpg',
860
875
  title: 'Pizza Margherita',
@@ -1090,8 +1105,10 @@ const standardState = {
1090
1105
  },
1091
1106
  },
1092
1107
  longTextMessage,
1108
+ participantTransferMessage,
1093
1109
  userMessage,
1094
1110
  textMessageBoldItalicUnderline,
1111
+ userMessageWithLinks,
1095
1112
  newTopicDivider,
1096
1113
  imageMessage,
1097
1114
  fileDownloadAgentMessage,
@@ -1119,6 +1136,7 @@ const standardState = {
1119
1136
  userMessageLong,
1120
1137
  fileDownloadUserMessage,
1121
1138
  emptyUrlFileDownloadUserMessage,
1139
+ userMessageWithLinks,
1122
1140
  ],
1123
1141
  },
1124
1142
  withParticipants: {
@@ -1196,12 +1214,12 @@ const standardState = {
1196
1214
  body: {
1197
1215
  text: 'Long ago when a dialog started',
1198
1216
  type: 'text',
1199
- variables: {},
1200
1217
  },
1201
1218
  },
1202
1219
  },
1203
1220
  participantMessage,
1204
1221
  participantMessageDefaultIcon,
1222
+ participantTransferMessage,
1205
1223
  newTopicDivider,
1206
1224
  transcriptInfoMessage,
1207
1225
  ...[newTranslationDividerStart, newTranslationDividerStop].map(
@@ -1258,7 +1276,6 @@ const standardState = {
1258
1276
  body: {
1259
1277
  text: 'Long ago when a dialog started',
1260
1278
  type: 'text',
1261
- variables: {},
1262
1279
  },
1263
1280
  },
1264
1281
  },
@@ -1271,7 +1288,6 @@ const standardState = {
1271
1288
  body: {
1272
1289
  text: 'Above me should be a time indicator showing the full date',
1273
1290
  type: 'text',
1274
- variables: {},
1275
1291
  },
1276
1292
  },
1277
1293
  },
@@ -1284,7 +1300,6 @@ const standardState = {
1284
1300
  body: {
1285
1301
  text: 'Another message',
1286
1302
  type: 'text',
1287
- variables: {},
1288
1303
  },
1289
1304
  },
1290
1305
  },
@@ -1297,7 +1312,6 @@ const standardState = {
1297
1312
  body: {
1298
1313
  text: 'And another message',
1299
1314
  type: 'text',
1300
- variables: {},
1301
1315
  },
1302
1316
  },
1303
1317
  },
@@ -1310,7 +1324,6 @@ const standardState = {
1310
1324
  body: {
1311
1325
  text: 'Above me should be a time indicator showing "yesterday"',
1312
1326
  type: 'text',
1313
- variables: {},
1314
1327
  },
1315
1328
  },
1316
1329
  },
@@ -1323,7 +1336,6 @@ const standardState = {
1323
1336
  body: {
1324
1337
  text: 'Another message',
1325
1338
  type: 'text',
1326
- variables: {},
1327
1339
  },
1328
1340
  },
1329
1341
  },
@@ -1336,7 +1348,6 @@ const standardState = {
1336
1348
  body: {
1337
1349
  text: 'And another message',
1338
1350
  type: 'text',
1339
- variables: {},
1340
1351
  },
1341
1352
  },
1342
1353
  },
@@ -1349,7 +1360,6 @@ const standardState = {
1349
1360
  body: {
1350
1361
  text: 'Above me should be a time indicator showing me the dialog continues today',
1351
1362
  type: 'text',
1352
- variables: {},
1353
1363
  },
1354
1364
  },
1355
1365
  },
@@ -1772,6 +1782,30 @@ const inlineInterface = {
1772
1782
  },
1773
1783
  },
1774
1784
  },
1785
+ minimizedInlineCharacterLimit: {
1786
+ category: categoryKeys.minimizedInline,
1787
+ headingText: 'Inline minimized with limited characters',
1788
+ description: '',
1789
+ inline: {
1790
+ ...baseState,
1791
+ config: {
1792
+ ...baseState.config,
1793
+ layoutMode: 'inline',
1794
+ },
1795
+ visibility: {
1796
+ ...baseState.visibility,
1797
+ visibility: visibilityStates.minimized,
1798
+ },
1799
+ entryMeta: {
1800
+ ...baseState.entryMeta,
1801
+ options: {
1802
+ text: {
1803
+ limit: 120,
1804
+ },
1805
+ },
1806
+ },
1807
+ },
1808
+ },
1775
1809
  minimizedInlinePrechat: {
1776
1810
  category: categoryKeys.minimizedInline,
1777
1811
  headingText: 'Inline minimized with pre-chat messages',
@@ -1908,6 +1942,7 @@ const newInterface = {
1908
1942
  description: '',
1909
1943
  ...baseState,
1910
1944
  events: [
1945
+ getCustomMessage(shortTextMessage.payload.body),
1911
1946
  {
1912
1947
  type: 'service_data',
1913
1948
  payload: {
@@ -6,55 +6,57 @@ import {
6
6
  useSkiplink,
7
7
  useSkiplinkTargetFocusing,
8
8
  } from 'ui/hooks/seamly-hooks'
9
- import { useEvents } from 'ui/hooks/seamly-state-hooks'
9
+ import { useEvents, useLoadedImageEventIds } from 'ui/hooks/seamly-state-hooks'
10
10
  import PrivacyDisclaimer from 'ui/components/layout/privacy-disclaimer'
11
11
  import { useVisibility } from 'domains/visibility'
12
12
  import Event from './event/event'
13
13
  import Loader from './loader'
14
14
  import ComponentFilter from './component-filter'
15
15
 
16
+ const Events = () => {
17
+ const events = useEvents()
18
+
19
+ let prevParticipant = null
20
+ return events.map((event) => {
21
+ const { type, payload } = event
22
+ const { participant, fromClient } = payload
23
+ let participantChanged = false
24
+ if (type !== 'participant') {
25
+ const currentParticipant = fromClient
26
+ ? 'seamly-client-participant'
27
+ : participant
28
+ if (event.type !== 'info' && prevParticipant !== currentParticipant) {
29
+ participantChanged = true
30
+ }
31
+ prevParticipant = currentParticipant
32
+ }
33
+
34
+ return (
35
+ <Event
36
+ key={event.payload.key || event.payload.id}
37
+ event={event}
38
+ newParticipant={participantChanged}
39
+ />
40
+ )
41
+ })
42
+ }
43
+
16
44
  const Conversation = () => {
17
45
  const { t } = useI18n()
18
- const appBodyContainer = useRef(null)
46
+ const chatBodyContainer = useRef(null)
19
47
  const events = useEvents()
20
48
  const isLoading = useSeamlyIsLoading()
21
49
  const { isOpen } = useVisibility()
22
50
  const skiplinkTargetId = useSkiplink()
23
51
  const focusSkiplinkTarget = useSkiplinkTargetFocusing()
52
+ const loadedImageEventIds = useLoadedImageEventIds()
24
53
 
25
54
  useEffect(() => {
26
- window.requestAnimationFrame(() => {
27
- if (appBodyContainer.current) {
28
- appBodyContainer.current.scrollTop =
29
- appBodyContainer.current.scrollHeight
30
- }
31
- })
32
- }, [events, isLoading, isOpen])
33
-
34
- const renderEvents = () => {
35
- let prevParticipant = null
36
- return events.map((event) => {
37
- const { type, payload } = event
38
- const { participant, fromClient } = payload
39
- let participantChanged = false
40
- if (type !== 'participant') {
41
- const currentParticipant = fromClient
42
- ? 'seamly-client-participant'
43
- : participant
44
- if (event.type !== 'info' && prevParticipant !== currentParticipant) {
45
- participantChanged = true
46
- }
47
- prevParticipant = currentParticipant
48
- }
49
- return (
50
- <Event
51
- key={event.payload.id}
52
- event={event}
53
- newParticipant={participantChanged}
54
- />
55
- )
56
- })
57
- }
55
+ const containerElement = chatBodyContainer.current
56
+ if (containerElement) {
57
+ containerElement.scrollTop = containerElement.scrollHeight
58
+ }
59
+ }, [events, isLoading, isOpen, loadedImageEventIds])
58
60
 
59
61
  const onClickHandler = (e) => {
60
62
  e.preventDefault()
@@ -72,11 +74,13 @@ const Conversation = () => {
72
74
  {t('skiplinkText')}
73
75
  </a>
74
76
  )}
75
- <div className={className('chat__body')} ref={appBodyContainer}>
77
+ <div className={className('chat__body')} ref={chatBodyContainer}>
76
78
  <div className={className('conversation__container')}>
77
79
  <PrivacyDisclaimer />
78
80
  <ol className={className('conversation')}>
79
- <ComponentFilter>{renderEvents()}</ComponentFilter>
81
+ <ComponentFilter>
82
+ <Events />
83
+ </ComponentFilter>
80
84
  {isLoading && <Loader />}
81
85
  </ol>
82
86
  </div>
@@ -1,6 +1,5 @@
1
1
  import { useCallback, useEffect, useMemo, useRef } from 'preact/hooks'
2
2
  import { className } from 'lib/css'
3
- import parseBody from 'lib/parse-body'
4
3
  import { useGeneratedId, useSeamlyCommands } from 'ui/hooks/seamly-hooks'
5
4
  import { cardTypes, actionTypes } from 'ui/utils/seamly-utils'
6
5
 
@@ -84,7 +83,7 @@ const CardComponent = ({
84
83
  {description && (
85
84
  <div
86
85
  className={className('card__description')}
87
- dangerouslySetInnerHTML={{ __html: parseBody(description) }}
86
+ dangerouslySetInnerHTML={{ __html: description }}
88
87
  />
89
88
  )}
90
89
  <CardActionComponent
@@ -1,16 +1,17 @@
1
- import { useCallback, useState } from 'preact/hooks'
1
+ import { useCallback, useMemo, useState } from 'preact/hooks'
2
2
  import { className } from 'lib/css'
3
+ import {
4
+ useEvents,
5
+ useSeamlyCommands,
6
+ useSeamlyDispatchContext,
7
+ } from 'ui/hooks/seamly-hooks'
3
8
  import { useI18n } from 'domains/i18n'
4
9
  import { useTranslatedEventData } from 'domains/translations'
5
10
  import { useUserHasResponded } from 'domains/app'
6
11
  import { setHasResponded } from 'domains/app/actions'
7
- import { actionTypes } from '../../../utils/seamly-utils'
8
- import MessageContainer from '../message-container'
9
- import {
10
- useSeamlyCommands,
11
- useSeamlyDispatchContext,
12
- } from '../../../hooks/seamly-hooks'
13
- import SuggestionsList from '../../suggestions/suggestions-list'
12
+ import { actionTypes } from 'ui/utils/seamly-utils'
13
+ import MessageContainer from 'ui/components/conversation/message-container'
14
+ import SuggestionsList from 'ui/components/suggestions/suggestions-list'
14
15
 
15
16
  export const useSuggestions = (event) => {
16
17
  const { payload } = event
@@ -28,11 +29,20 @@ const ConversationSuggestions = ({ event, ...props }) => {
28
29
  const userResponded = useUserHasResponded()
29
30
  const { sendAction, addMessageBubble } = useSeamlyCommands()
30
31
  const { suggestions, payload } = useSuggestions(event)
32
+ const events = useEvents()
31
33
 
32
34
  const { t } = useI18n()
33
35
  const headingText = t('suggestions.headingText')
34
36
  const footerText = t('suggestions.footerText')
35
37
 
38
+ // We check if there is at least one last transaction
39
+ // to avoid rendering the suggestions before prior events are rendered.
40
+ const hasLastTransactionEvent = useMemo(
41
+ () =>
42
+ events.some(({ payload: eventPayload }) => eventPayload?.transactionLast),
43
+ [events],
44
+ )
45
+
36
46
  const handleClick = useCallback(
37
47
  ({ id, question }) => {
38
48
  setIsExpanded(false)
@@ -56,7 +66,7 @@ const ConversationSuggestions = ({ event, ...props }) => {
56
66
  [dispatch, sendAction, payload.id, addMessageBubble],
57
67
  )
58
68
 
59
- if (!isExpanded || userResponded) {
69
+ if (!isExpanded || userResponded || !hasLastTransactionEvent) {
60
70
  return null
61
71
  }
62
72
 
@@ -1,6 +1,5 @@
1
1
  import { useCallback } from 'preact/hooks'
2
2
  import { className } from 'lib/css'
3
- import parseBody from 'lib/parse-body'
4
3
  import { useGeneratedId, useSeamlyCommands } from 'ui/hooks/seamly-hooks'
5
4
  import { actionTypes } from 'ui/utils/seamly-utils'
6
5
  import MessageContainer from 'ui/components/conversation/message-container'
@@ -31,7 +30,7 @@ const Cta = ({ event }) => {
31
30
  <div
32
31
  className={className('cta__content')}
33
32
  id={descriptionId}
34
- dangerouslySetInnerHTML={{ __html: parseBody(body.description) }}
33
+ dangerouslySetInnerHTML={{ __html: body.description }}
35
34
  onClick={eventClick}
36
35
  />
37
36
  <a
@@ -1,12 +1,22 @@
1
1
  import { useState } from 'preact/hooks'
2
2
  import MessageContainer from 'ui/components/conversation/message-container'
3
3
  import { useTranslatedEventData } from 'domains/translations'
4
+ import { useStoreDispatch } from 'domains/redux'
4
5
  import ImageLightbox from './image-lightbox'
6
+ import { seamlyActions } from '../../../utils/seamly-utils'
7
+
8
+ const { SET_LOADED_IMAGE_EVENT_IDS } = seamlyActions
5
9
 
6
10
  const Image = ({ event, descriptorId, ...props }) => {
7
11
  const [body] = useTranslatedEventData(event)
8
12
  const { description, url, isZoomable } = body
9
13
  const [showLighbox, setShowLightbox] = useState(false)
14
+ const dispatch = useStoreDispatch()
15
+
16
+ const handleOnLoad = () => {
17
+ dispatch({ type: SET_LOADED_IMAGE_EVENT_IDS, eventId: event.payload.id })
18
+ setShowLightbox(true)
19
+ }
10
20
 
11
21
  return (
12
22
  <MessageContainer event={event} type="image" {...props}>
@@ -14,9 +24,7 @@ const Image = ({ event, descriptorId, ...props }) => {
14
24
  src={url}
15
25
  id={descriptorId}
16
26
  alt={description}
17
- onLoad={() => {
18
- setShowLightbox(true)
19
- }}
27
+ onLoad={handleOnLoad}
20
28
  />
21
29
  {isZoomable && showLighbox && (
22
30
  <ImageLightbox description={description} url={url} />
@@ -1,6 +1,3 @@
1
- import { useMemo } from 'preact/hooks'
2
- import Mustache from 'mustache'
3
- import parseBody from 'lib/parse-body'
4
1
  import EventDivider from 'ui/components/conversation/event-divider'
5
2
  import { useTranslatedEventData } from 'domains/translations'
6
3
 
@@ -8,13 +5,7 @@ const Participant = ({ event }) => {
8
5
  const { participant } = event.payload
9
6
  const [introduction] = useTranslatedEventData(event)
10
7
 
11
- const intro = useMemo(() => {
12
- return introduction
13
- ? Mustache.render(parseBody(introduction), participant)
14
- : undefined
15
- }, [introduction, participant])
16
-
17
- if (!intro) {
8
+ if (!introduction) {
18
9
  return null
19
10
  }
20
11
 
@@ -23,7 +14,7 @@ const Participant = ({ event }) => {
23
14
  graphicSrc={participant.avatar}
24
15
  graphicType={participant.avatar ? 'avatar' : undefined}
25
16
  iconName={!participant.avatar ? 'balloon' : undefined}
26
- childrenHTML={intro}
17
+ childrenHTML={introduction}
27
18
  dividerType="participant"
28
19
  />
29
20
  )
@@ -1,6 +1,4 @@
1
- import parseBody from '../../../../lib/parse-body'
2
1
  import useEventLinkClickHandler from './hooks/use-event-link-click-handler'
3
- import { parseRichText } from './hooks/use-text-rendering'
4
2
  import MessageContainer from '../message-container'
5
3
  import { useTranslatedEventData } from '../../../../domains/translations'
6
4
 
@@ -17,7 +15,7 @@ const Splash = ({ event, ...props }) => {
17
15
  {...props}
18
16
  bodyProps={{
19
17
  dangerouslySetInnerHTML: {
20
- __html: parseRichText(parseBody(body.text), body.variables),
18
+ __html: body.text,
21
19
  },
22
20
  }}
23
21
  />
@@ -1,28 +1,28 @@
1
1
  import { useMemo } from 'preact/hooks'
2
- import parseBody from 'lib/parse-body'
3
2
  import MessageContainer from 'ui/components/conversation/message-container'
4
3
  import { useTranslatedEventData } from 'domains/translations'
5
4
  import useEventLinkClickHandler from './hooks/use-event-link-click-handler'
6
- import { parseRichText } from './hooks/use-text-rendering'
7
5
 
8
6
  const Text = ({ event, ...props }) => {
9
7
  const [body] = useTranslatedEventData(event)
10
8
  const eventClick = useEventLinkClickHandler(event.payload.id)
11
9
 
12
10
  const containerProps = useMemo(() => {
13
- if (!event.payload.fromClient) {
11
+ if (event.payload.optimisticallyInjected) {
14
12
  return {
15
- bodyProps: {
16
- dangerouslySetInnerHTML: {
17
- __html: parseRichText(parseBody(body.text), body.variables),
18
- },
19
- },
13
+ children: <p>{body.text}</p>,
20
14
  }
21
15
  }
16
+
22
17
  return {
23
- children: <p>{body.text}</p>,
18
+ bodyProps: {
19
+ dangerouslySetInnerHTML: {
20
+ __html: body.text,
21
+ },
22
+ },
24
23
  }
25
24
  }, [body, event])
25
+
26
26
  return (
27
27
  <MessageContainer
28
28
  type="text"