@seamly/web-ui 20.4.0 → 20.5.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 (38) hide show
  1. package/build/dist/lib/index.debug.js +35 -68
  2. package/build/dist/lib/index.debug.min.js +1 -1
  3. package/build/dist/lib/index.debug.min.js.LICENSE.txt +4 -16
  4. package/build/dist/lib/index.js +374 -3850
  5. package/build/dist/lib/index.min.js +1 -1
  6. package/build/dist/lib/index.min.js.LICENSE.txt +0 -5
  7. package/build/dist/lib/standalone.js +394 -3862
  8. package/build/dist/lib/standalone.min.js +1 -1
  9. package/build/dist/lib/style-guide.js +200 -113
  10. package/build/dist/lib/style-guide.min.js +1 -1
  11. package/package.json +2 -4
  12. package/src/javascripts/api/index.js +17 -1
  13. package/src/javascripts/domains/config/reducer.js +2 -0
  14. package/src/javascripts/domains/forms/provider.js +14 -6
  15. package/src/javascripts/domains/visibility/actions.js +2 -0
  16. package/src/javascripts/domains/visibility/hooks.js +60 -1
  17. package/src/javascripts/domains/visibility/reducer.js +5 -0
  18. package/src/javascripts/domains/visibility/selectors.js +5 -0
  19. package/src/javascripts/domains/visibility/utils.js +5 -1
  20. package/src/javascripts/style-guide/components/app.js +2 -0
  21. package/src/javascripts/style-guide/states.js +30 -50
  22. package/src/javascripts/ui/components/conversation/event/card-component.js +1 -2
  23. package/src/javascripts/ui/components/conversation/event/conversation-suggestions.js +19 -9
  24. package/src/javascripts/ui/components/conversation/event/cta.js +1 -2
  25. package/src/javascripts/ui/components/conversation/event/participant.js +2 -11
  26. package/src/javascripts/ui/components/conversation/event/splash.js +1 -3
  27. package/src/javascripts/ui/components/conversation/event/text.js +9 -9
  28. package/src/javascripts/ui/components/layout/chat.js +52 -48
  29. package/src/javascripts/ui/components/suggestions/suggestions-list.js +12 -14
  30. package/src/javascripts/ui/components/view/deprecated-view.js +16 -11
  31. package/src/javascripts/ui/components/view/inline-view.js +13 -8
  32. package/src/javascripts/ui/hooks/seamly-entry-hooks.js +3 -2
  33. package/src/javascripts/ui/hooks/seamly-state-hooks.js +4 -3
  34. package/src/javascripts/ui/hooks/use-seamly-chat.js +41 -29
  35. package/src/javascripts/ui/hooks/use-seamly-commands.js +16 -4
  36. package/src/javascripts/ui/utils/seamly-utils.js +16 -6
  37. package/src/javascripts/lib/parse-body.js +0 -10
  38. package/src/javascripts/ui/components/conversation/event/hooks/use-text-rendering.js +0 -35
@@ -1,5 +1,6 @@
1
- import { useCallback } from 'preact/hooks'
1
+ import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
2
2
  import { useSelector, useStoreDispatch } from 'domains/redux'
3
+ import { useConfig } from 'domains/config'
3
4
  import * as Actions from './actions'
4
5
  import * as Selectors from './selectors'
5
6
  import { visibilityStates } from './constants'
@@ -24,3 +25,61 @@ export const useVisibility = () => {
24
25
  setVisibility,
25
26
  }
26
27
  }
28
+
29
+ /**
30
+ * Custom hook which enables initializing of IntersectionObserver on any node ref.
31
+ * @param {object} options Hook options.
32
+ * @param {boolean=} options.freezeOnceVisible Stops observing when the root element is visible.
33
+ * @param {boolean=} options.enabled Determines if useIntersect is enabled.
34
+ */
35
+ const useIntersect = ({ freezeOnceVisible = false, enabled = true }) => {
36
+ const [entry, setEntry] = useState(null)
37
+ const containerRef = useRef(null)
38
+
39
+ const isVisible = !!entry?.isIntersecting || !enabled
40
+ const frozen = isVisible && freezeOnceVisible
41
+
42
+ const observerCallback = ([updatedEntry]) => setEntry(updatedEntry)
43
+
44
+ useEffect(() => {
45
+ const node = containerRef?.current
46
+ const hasIOSupport = !!window.IntersectionObserver
47
+
48
+ if (!node && process.env.NODE_ENV === 'development') {
49
+ console.error(
50
+ 'containerRef must be set on a DOM element. check the component where useIntersect is being used.',
51
+ )
52
+ }
53
+
54
+ // Return an arrow function to have a consistent return value
55
+ if (!hasIOSupport || frozen || !node || !enabled) return () => undefined
56
+
57
+ const observerOptions = { threshold: 0, root: null, rootMargin: '0%' }
58
+ const observer = new IntersectionObserver(observerCallback, observerOptions)
59
+
60
+ observer.observe(node)
61
+
62
+ return () => observer.disconnect()
63
+ }, [enabled, frozen])
64
+
65
+ return { isVisible, containerRef }
66
+ }
67
+
68
+ export const useShowInlineView = () => {
69
+ const dispatch = useStoreDispatch()
70
+ const { connectWhenInView } = useConfig()
71
+ const showInlineView = useSelector(Selectors.selectShowInlineView)
72
+
73
+ const { containerRef, isVisible } = useIntersect({
74
+ enabled: connectWhenInView,
75
+ freezeOnceVisible: true,
76
+ })
77
+
78
+ useEffect(() => {
79
+ if (!isVisible) return
80
+
81
+ dispatch(Actions.setShowInlineView())
82
+ }, [dispatch, isVisible])
83
+
84
+ return { containerRef, showInlineView }
85
+ }
@@ -4,6 +4,7 @@ import { visibilityStates } from './constants'
4
4
 
5
5
  const initialState = {
6
6
  visibility: visibilityStates.initialize,
7
+ showInlineView: false,
7
8
  }
8
9
 
9
10
  export default createReducer(
@@ -14,6 +15,10 @@ export default createReducer(
14
15
  }),
15
16
  [Actions.setVisibility.fulfilled]: (state, { payload: visibility }) =>
16
17
  visibility ? { ...state, visibility } : state,
18
+ [Actions.setShowInlineView]: (state) => ({
19
+ ...state,
20
+ showInlineView: true,
21
+ }),
17
22
  },
18
23
  initialState,
19
24
  )
@@ -6,4 +6,9 @@ export const selectVisibility = createSelector(
6
6
  (state) => state.visibility,
7
7
  )
8
8
 
9
+ export const selectShowInlineView = createSelector(
10
+ selectState,
11
+ (state) => state.showInlineView,
12
+ )
13
+
9
14
  export { selectState }
@@ -16,9 +16,13 @@ export const calculateVisibility = ({
16
16
  config,
17
17
  }) => {
18
18
  const { defaults, layoutMode, hideOnNoUserResponse } = config
19
-
20
19
  const { visible: defaultVisibility } = defaults || {}
21
20
 
21
+ // The app layout should always be open by default.
22
+ if (layoutMode === 'app' && !previousVisibility) {
23
+ return visibilityStates.open
24
+ }
25
+
22
26
  // Requesting open should override the responded check.
23
27
  if (
24
28
  layoutMode === 'window' &&
@@ -61,6 +61,8 @@ const StyleGuideApp = ({
61
61
  config: {
62
62
  ...bareState.config,
63
63
  ...config,
64
+ showDisclaimer:
65
+ bareState.config.showDisclaimer || config.showDisclaimer,
64
66
  layoutMode: bareState.config.layoutMode,
65
67
  ...(overrideMessages ? { messages: overrideMessages } : {}),
66
68
  showFaq,
@@ -156,7 +156,6 @@ const infoMessage = {
156
156
  body: {
157
157
  text: 'This is a system generated info message',
158
158
  type: 'text',
159
- variables: {},
160
159
  },
161
160
  fromClient: false,
162
161
  id: randomId(),
@@ -199,7 +198,7 @@ const participantMessage = {
199
198
  participant: {
200
199
  avatar,
201
200
  id: 'agent',
202
- introduction: "You're now talking to {{name}} gimme a minit",
201
+ introduction: "You're now talking to Mrs. Bot gimme a minit",
203
202
  name: 'Mrs. Bot',
204
203
  service: {
205
204
  expose: { map: {}, version: 2 },
@@ -223,7 +222,7 @@ const participantMessageDefaultIcon = {
223
222
  messageStatus: 'received',
224
223
  participant: {
225
224
  id: 'user',
226
- introduction: "You're now talking to {{name}} gimme a minit",
225
+ introduction: "You're now talking to Mrs. Bot gimme a minit",
227
226
  name: 'Mrs. Bot',
228
227
  service: {
229
228
  expose: { map: {}, version: 2 },
@@ -245,7 +244,6 @@ const getCustomMessage = ({ type, data, text }) => ({
245
244
  type,
246
245
  text,
247
246
  data,
248
- variables: {},
249
247
  },
250
248
  participant: 'agent',
251
249
  service: {
@@ -269,7 +267,6 @@ const shortTextMessage = {
269
267
  body: {
270
268
  text: 'What do you want to do?',
271
269
  type: 'text',
272
- variables: {},
273
270
  },
274
271
  fromClient: false,
275
272
  fromHistory: true,
@@ -304,7 +301,8 @@ const ctaMessage = {
304
301
  type: 'message',
305
302
  payload: {
306
303
  body: {
307
- description: 'Thanks for chatting!\n\nMore info about our **products**?',
304
+ description:
305
+ 'Thanks for chatting!\n\nMore info about our <strong>products</strong>?',
308
306
  buttonLink: 'https://seamly.ai',
309
307
  buttonText: 'View website',
310
308
  buttonNewTab: true,
@@ -331,7 +329,6 @@ const longTextMessage = {
331
329
  body: {
332
330
  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.',
333
331
  type: 'text',
334
- variables: {},
335
332
  },
336
333
  fromClient: false,
337
334
  fromHistory: true,
@@ -353,9 +350,8 @@ const textMessageBoldItalicUnderline = {
353
350
  type: 'message',
354
351
  payload: {
355
352
  body: {
356
- text: 'Bubble with **bold** *italic* <u>underline</u>',
353
+ text: 'Bubble with <strong>bold</strong> <em>italic</em> <u>underline</u>',
357
354
  type: 'text',
358
- variables: {},
359
355
  },
360
356
  fromClient: false,
361
357
  fromHistory: true,
@@ -377,24 +373,8 @@ const textMessageWithLinks = {
377
373
  type: 'message',
378
374
  payload: {
379
375
  body: {
380
- text: '{{link_1}} and {{link_2}} embedded in text',
376
+ 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',
381
377
  type: 'text',
382
- variables: {
383
- link_1: {
384
- id: '1',
385
- name: 'Link in same window',
386
- newTab: false,
387
- type: 'link',
388
- url: 'https://google.com',
389
- },
390
- link_2: {
391
- id: '2',
392
- name: 'link in new window',
393
- newTab: true,
394
- type: 'link',
395
- url: 'https://google.com',
396
- },
397
- },
398
378
  },
399
379
  fromClient: false,
400
380
  fromHistory: true,
@@ -416,17 +396,8 @@ const textMessageWithLongLink = {
416
396
  type: 'message',
417
397
  payload: {
418
398
  body: {
419
- text: 'Here is a long link {{link_1}} embedded in text',
399
+ 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',
420
400
  type: 'text',
421
- variables: {
422
- link_1: {
423
- id: '1',
424
- 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',
425
- newTab: false,
426
- type: 'link',
427
- url: 'https://google.com',
428
- },
429
- },
430
401
  },
431
402
  fromClient: false,
432
403
  fromHistory: true,
@@ -450,7 +421,6 @@ const textMesageWithBullets = {
450
421
  body: {
451
422
  text: '<ul>\n<li>Bullets</li>\n<li>bullets</li>\n<li>bullets</li>\n</ul>\n',
452
423
  type: 'text',
453
- variables: {},
454
424
  },
455
425
  fromClient: false,
456
426
  fromHistory: true,
@@ -605,7 +575,6 @@ const userMessage = {
605
575
  body: {
606
576
  text: 'This is what the user typed',
607
577
  type: 'text',
608
- variables: {},
609
578
  },
610
579
  fromClient: true,
611
580
  fromHistory: true,
@@ -624,7 +593,23 @@ const userMessageLong = {
624
593
  body: {
625
594
  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!!',
626
595
  type: 'text',
627
- variables: {},
596
+ },
597
+ fromClient: true,
598
+ fromHistory: true,
599
+ id: randomId(),
600
+ messageStatus: 'read',
601
+ participant: 'user',
602
+ transactionId: '1cdefea9-7437-4672-bcf8-2c75dc99244c',
603
+ transactionLast: null,
604
+ type: 'text',
605
+ },
606
+ }
607
+ const userMessageWithLinks = {
608
+ type: 'message',
609
+ payload: {
610
+ body: {
611
+ 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',
612
+ type: 'text',
628
613
  },
629
614
  fromClient: true,
630
615
  fromHistory: true,
@@ -836,7 +821,7 @@ const cardAskText = {
836
821
  },
837
822
  buttonText: 'Ask about pizzas!',
838
823
  description:
839
- '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.',
824
+ '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.',
840
825
  image:
841
826
  'https://developers.seamly.ai/clients/web-ui/static/photos/card-square.jpg',
842
827
  title: 'Pizza Margherita',
@@ -855,7 +840,8 @@ const cardNavigate = {
855
840
  type: 'navigate',
856
841
  },
857
842
  buttonText: 'Order now!',
858
- description: 'Pizza Margherita is a **typical Neapolitan pizza**.',
843
+ description:
844
+ 'Pizza Margherita is a <strong>typical Neapolitan pizza</strong>.',
859
845
  image:
860
846
  'https://developers.seamly.ai/clients/web-ui/static/photos/card-landscape.jpg',
861
847
  title: 'Pizza Margherita',
@@ -1093,6 +1079,7 @@ const standardState = {
1093
1079
  longTextMessage,
1094
1080
  userMessage,
1095
1081
  textMessageBoldItalicUnderline,
1082
+ userMessageWithLinks,
1096
1083
  newTopicDivider,
1097
1084
  imageMessage,
1098
1085
  fileDownloadAgentMessage,
@@ -1120,6 +1107,7 @@ const standardState = {
1120
1107
  userMessageLong,
1121
1108
  fileDownloadUserMessage,
1122
1109
  emptyUrlFileDownloadUserMessage,
1110
+ userMessageWithLinks,
1123
1111
  ],
1124
1112
  },
1125
1113
  withParticipants: {
@@ -1197,7 +1185,6 @@ const standardState = {
1197
1185
  body: {
1198
1186
  text: 'Long ago when a dialog started',
1199
1187
  type: 'text',
1200
- variables: {},
1201
1188
  },
1202
1189
  },
1203
1190
  },
@@ -1259,7 +1246,6 @@ const standardState = {
1259
1246
  body: {
1260
1247
  text: 'Long ago when a dialog started',
1261
1248
  type: 'text',
1262
- variables: {},
1263
1249
  },
1264
1250
  },
1265
1251
  },
@@ -1272,7 +1258,6 @@ const standardState = {
1272
1258
  body: {
1273
1259
  text: 'Above me should be a time indicator showing the full date',
1274
1260
  type: 'text',
1275
- variables: {},
1276
1261
  },
1277
1262
  },
1278
1263
  },
@@ -1285,7 +1270,6 @@ const standardState = {
1285
1270
  body: {
1286
1271
  text: 'Another message',
1287
1272
  type: 'text',
1288
- variables: {},
1289
1273
  },
1290
1274
  },
1291
1275
  },
@@ -1298,7 +1282,6 @@ const standardState = {
1298
1282
  body: {
1299
1283
  text: 'And another message',
1300
1284
  type: 'text',
1301
- variables: {},
1302
1285
  },
1303
1286
  },
1304
1287
  },
@@ -1311,7 +1294,6 @@ const standardState = {
1311
1294
  body: {
1312
1295
  text: 'Above me should be a time indicator showing "yesterday"',
1313
1296
  type: 'text',
1314
- variables: {},
1315
1297
  },
1316
1298
  },
1317
1299
  },
@@ -1324,7 +1306,6 @@ const standardState = {
1324
1306
  body: {
1325
1307
  text: 'Another message',
1326
1308
  type: 'text',
1327
- variables: {},
1328
1309
  },
1329
1310
  },
1330
1311
  },
@@ -1337,7 +1318,6 @@ const standardState = {
1337
1318
  body: {
1338
1319
  text: 'And another message',
1339
1320
  type: 'text',
1340
- variables: {},
1341
1321
  },
1342
1322
  },
1343
1323
  },
@@ -1350,7 +1330,6 @@ const standardState = {
1350
1330
  body: {
1351
1331
  text: 'Above me should be a time indicator showing me the dialog continues today',
1352
1332
  type: 'text',
1353
- variables: {},
1354
1333
  },
1355
1334
  },
1356
1335
  },
@@ -1933,6 +1912,7 @@ const newInterface = {
1933
1912
  description: '',
1934
1913
  ...baseState,
1935
1914
  events: [
1915
+ getCustomMessage(shortTextMessage.payload.body),
1936
1916
  {
1937
1917
  type: 'service_data',
1938
1918
  payload: {
@@ -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,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"
@@ -1,3 +1,4 @@
1
+ import { forwardRef } from 'preact/compat'
1
2
  import { className } from '../../../lib/css'
2
3
  import {
3
4
  useSeamlyAppContainerClassNames,
@@ -9,54 +10,57 @@ import { useI18n } from '../../../domains/i18n'
9
10
  import { useVisibility, visibilityStates } from '../../../domains/visibility'
10
11
  import Suggestions from '../suggestions'
11
12
 
12
- const Chat = ({ children, className: givenClassName = '' }) => {
13
- const { isOpen, isVisible, setVisibility } = useVisibility()
14
- const { namespace, layoutMode } = useConfig()
15
- const { isInline } = useSeamlyLayoutMode()
16
- const appContainerClassNames = useSeamlyAppContainerClassNames()
17
- const userResponded = useUserHasResponded()
18
- const { t } = useI18n()
19
-
20
- const defaultClassNames = [
21
- 'chat',
22
- `chat--layout-${layoutMode}`,
23
- `namespace--${namespace}`,
24
- ]
25
-
26
- const classNames = [
27
- ...defaultClassNames,
28
- ...appContainerClassNames,
29
- givenClassName,
30
- ]
31
-
32
- if (!isOpen && layoutMode !== 'app') {
33
- classNames.push('chat--collapsed')
34
- }
35
-
36
- if (userResponded) {
37
- classNames.push('chat--user-responded')
38
- }
39
-
40
- const onKeyDownHandler = (e) => {
41
- if ((e.code && e.code === 'Escape') || e.keyCode === 27)
42
- if (!isInline && isOpen) {
43
- setVisibility(visibilityStates.minimized)
44
- }
45
- }
46
-
47
- return (
48
- isVisible && (
49
- <section
50
- className={className(classNames)}
51
- onKeyDown={onKeyDownHandler}
52
- tabIndex="-1"
53
- aria-label={t('chat.srLabel')}
54
- >
55
- <div className={className('chat-wrapper')}>{children}</div>
56
- {layoutMode === 'inline' && isOpen && <Suggestions isAside={true} />}
57
- </section>
13
+ const Chat = forwardRef(
14
+ ({ children, className: givenClassName = '' }, forwardedRef) => {
15
+ const { isOpen, isVisible, setVisibility } = useVisibility()
16
+ const { namespace, layoutMode } = useConfig()
17
+ const { isInline } = useSeamlyLayoutMode()
18
+ const appContainerClassNames = useSeamlyAppContainerClassNames()
19
+ const userResponded = useUserHasResponded()
20
+ const { t } = useI18n()
21
+
22
+ const defaultClassNames = [
23
+ 'chat',
24
+ `chat--layout-${layoutMode}`,
25
+ `namespace--${namespace}`,
26
+ ]
27
+
28
+ const classNames = [
29
+ ...defaultClassNames,
30
+ ...appContainerClassNames,
31
+ givenClassName,
32
+ ]
33
+
34
+ if (!isOpen && layoutMode !== 'app') {
35
+ classNames.push('chat--collapsed')
36
+ }
37
+
38
+ if (userResponded) {
39
+ classNames.push('chat--user-responded')
40
+ }
41
+
42
+ const onKeyDownHandler = (e) => {
43
+ if ((e.code && e.code === 'Escape') || e.keyCode === 27)
44
+ if (!isInline && isOpen) {
45
+ setVisibility(visibilityStates.minimized)
46
+ }
47
+ }
48
+
49
+ return (
50
+ isVisible && (
51
+ <section
52
+ className={className(classNames)}
53
+ onKeyDown={onKeyDownHandler}
54
+ tabIndex="-1"
55
+ ref={forwardedRef}
56
+ aria-label={t('chat.srLabel')}
57
+ >
58
+ <div className={className('chat-wrapper')}>{children}</div>
59
+ {layoutMode === 'inline' && isOpen && <Suggestions isAside={true} />}
60
+ </section>
61
+ )
58
62
  )
59
- )
60
- }
63
+ },
64
+ )
61
65
 
62
66
  export default Chat