@subiz/widget-v4 4.1.210 → 4.1.212

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 (130) hide show
  1. package/README.md +21 -2
  2. package/assets/img/dot_chat.svg +3 -0
  3. package/assets/less/chatbox.less +9 -0
  4. package/assets/less/form.less +10 -0
  5. package/assets/less/message.less +114 -13
  6. package/assets/less/widget.less +15 -18
  7. package/compiled_src/call/css.js +1 -1
  8. package/compiled_src/chatbox/Buttons.js +7 -4
  9. package/compiled_src/chatbox/ChatButton.js +127 -29
  10. package/compiled_src/chatbox/Event.js +37 -5
  11. package/compiled_src/chatbox/Events.js +348 -129
  12. package/compiled_src/chatbox/FormAttachment.js +1 -1
  13. package/compiled_src/chatbox/Header.js +7 -2
  14. package/compiled_src/chatbox/InfoMessage.js +1 -1
  15. package/compiled_src/chatbox/MessageInput.js +24 -9
  16. package/compiled_src/chatbox/OrderAttachments.js +13 -13
  17. package/compiled_src/chatbox/QuoteMessage.js +3 -2
  18. package/compiled_src/chatbox/TextMessage.js +22 -18
  19. package/compiled_src/chatbox/Widget.js +200 -129
  20. package/compiled_src/chatbox/block.js +39 -29
  21. package/compiled_src/chatbox/conversation-history.js +44 -0
  22. package/compiled_src/chatbox/event-layout.js +59 -0
  23. package/compiled_src/chatbox/greeting.js +32 -0
  24. package/compiled_src/chatbox/main.js +3 -0
  25. package/compiled_src/common.js +13 -9
  26. package/compiled_src/form/css.js +1 -1
  27. package/compiled_src/lib.js +22 -32
  28. package/compiled_src/notif/css.js +1 -1
  29. package/compiled_src/plugin.js +11 -18
  30. package/compiled_src/popup/css.js +1 -1
  31. package/compiled_src/popup/popup.js +1 -1
  32. package/compiled_src/store/api.js +0 -2
  33. package/compiled_src/store/api_fake.js +1 -1
  34. package/compiled_src/store/attachments.js +28 -0
  35. package/compiled_src/store/chain.js +24 -10
  36. package/compiled_src/store/kv.js +9 -13
  37. package/compiled_src/store/lsdb.js +1 -1
  38. package/compiled_src/store/store.js +356 -117
  39. package/compiled_src/util/common.js +48 -41
  40. package/compiled_src/util/event.js +15 -0
  41. package/compiled_src/util/pubsub.js +28 -13
  42. package/compiled_src/util/url.js +18 -0
  43. package/dist/127.js +13 -13
  44. package/dist/132.js +1 -1
  45. package/dist/1a0f6f3ff1a.127.js +1 -0
  46. package/dist/{1a0d80f6e72.132.js → 1a0f6f3ff1a.132.js} +1 -1
  47. package/dist/{1a0d80f6e72.27.js → 1a0f6f3ff1a.27.js} +1 -1
  48. package/dist/1a0f6f3ff1a.278.js +1 -0
  49. package/dist/{1a0d80f6e72.339.js → 1a0f6f3ff1a.339.js} +1 -1
  50. package/dist/1a0f6f3ff1a.459.js +1 -0
  51. package/dist/1a0f6f3ff1a.app.js +1 -0
  52. package/dist/{1a0d80f6e72.chat.html → 1a0f6f3ff1a.chat.html} +1 -1
  53. package/dist/1a0f6f3ff1a.chat.js +1 -0
  54. package/dist/{1a0d80f6e72.form.html → 1a0f6f3ff1a.form.html} +1 -1
  55. package/dist/1a0f6f3ff1a.form.js +1 -0
  56. package/dist/{1a0d80f6e72.index.html → 1a0f6f3ff1a.index.html} +1 -1
  57. package/dist/{1a0d80f6e72.library.html → 1a0f6f3ff1a.library.html} +1 -1
  58. package/dist/1a0f6f3ff1a.library.js +1 -0
  59. package/dist/{1a0d80f6e72.preview.html → 1a0f6f3ff1a.preview.html} +1 -1
  60. package/dist/1a0f6f3ff1a.preview.js +1 -0
  61. package/dist/1a0f6f3ff1a.webview.js +1 -0
  62. package/dist/27.js +2 -2
  63. package/dist/459.js +2 -2
  64. package/dist/HASH +1 -1
  65. package/dist/assets/img/dot_chat.335fd8a.svg +3 -0
  66. package/dist/screenshoot.js +1523 -619
  67. package/package.json +12 -5
  68. package/src/chatbox/Buttons.js +5 -4
  69. package/src/chatbox/ChatButton.js +133 -39
  70. package/src/chatbox/Event.js +38 -5
  71. package/src/chatbox/Events.js +306 -134
  72. package/src/chatbox/FormAttachment.js +1 -1
  73. package/src/chatbox/Header.js +5 -4
  74. package/src/chatbox/InfoMessage.js +1 -1
  75. package/src/chatbox/MessageInput.js +15 -10
  76. package/src/chatbox/OrderAttachments.js +13 -13
  77. package/src/chatbox/QuoteMessage.js +3 -2
  78. package/src/chatbox/TextMessage.js +21 -20
  79. package/src/chatbox/Widget.js +150 -150
  80. package/src/chatbox/block.js +29 -27
  81. package/src/chatbox/conversation-history.js +37 -0
  82. package/src/chatbox/event-layout.js +72 -0
  83. package/src/chatbox/greeting.js +13 -0
  84. package/src/chatbox/main.js +3 -0
  85. package/src/common.js +15 -9
  86. package/src/lib.js +22 -35
  87. package/src/plugin.js +11 -13
  88. package/src/popup/popup.js +1 -1
  89. package/src/store/api.js +0 -2
  90. package/src/store/api_fake.js +1 -1
  91. package/src/store/attachments.js +21 -0
  92. package/src/store/chain.js +25 -12
  93. package/src/store/kv.js +10 -19
  94. package/src/store/lsdb.js +1 -1
  95. package/src/store/store.js +313 -130
  96. package/src/util/common.js +48 -43
  97. package/src/util/event.js +7 -0
  98. package/src/util/pubsub.js +27 -8
  99. package/src/util/url.js +14 -0
  100. package/test.cjs +59 -0
  101. package/assets/less/contact.less +0 -126
  102. package/compiled_src/util/common.test.js +0 -414
  103. package/compiled_src/util/event.test.js +0 -38
  104. package/compiled_src/util/extractor.test.js +0 -299
  105. package/compiled_src/util/extractor.test2.js +0 -61
  106. package/compiled_src/util/gate.test.js +0 -18
  107. package/compiled_src/util/pubsub.test.js +0 -35
  108. package/dist/1a0d80f6e72.127.js +0 -1
  109. package/dist/1a0d80f6e72.278.js +0 -1
  110. package/dist/1a0d80f6e72.459.js +0 -1
  111. package/dist/1a0d80f6e72.74.js +0 -1
  112. package/dist/1a0d80f6e72.997.js +0 -1
  113. package/dist/1a0d80f6e72.app.js +0 -1
  114. package/dist/1a0d80f6e72.chat.js +0 -1
  115. package/dist/1a0d80f6e72.form.js +0 -1
  116. package/dist/1a0d80f6e72.library.js +0 -1
  117. package/dist/1a0d80f6e72.preview.js +0 -1
  118. package/dist/1a0d80f6e72.webview.js +0 -1
  119. package/dist/74.js +0 -376
  120. package/dist/997.js +0 -37
  121. package/src/contact/contact.js +0 -400
  122. package/src/contact/css.js +0 -1
  123. package/src/util/common.test.js +0 -301
  124. package/src/util/event.test.js +0 -31
  125. package/src/util/extractor.test.js +0 -240
  126. package/src/util/extractor.test2.js +0 -56
  127. package/src/util/gate.test.js +0 -20
  128. package/src/util/pubsub.test.js +0 -37
  129. /package/dist/{1a0d80f6e72.185.js → 1a0f6f3ff1a.185.js} +0 -0
  130. /package/dist/{1a0d80f6e72.490.js → 1a0f6f3ff1a.490.js} +0 -0
package/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "@subiz/widget-v4",
3
- "version": "4.1.210",
3
+ "version": "4.1.212",
4
4
  "main": "./compiled_src/lib.js",
5
5
  "scripts": {
6
6
  "dev": "npx webpack serve --config webpack.dev.js",
7
- "build": "rm -rf dist && npx webpack --config webpack.screenshoot.js && npx webpack --config webpack.prod.js",
7
+ "build": "npm run lib && rm -rf dist && webpack --config webpack.screenshoot.js && webpack --config webpack.prod.js",
8
8
  "report": "npx webpack --config webpack.report.js",
9
- "lib": "rm -rf compiled_src && npx babel --out-dir=compiled_src src"
9
+ "lib": "babel src --out-dir compiled_src --ignore \"**/*.test.js,**/*.test2.js\"",
10
+ "test": "node test.cjs",
11
+ "lint": "eslint src",
12
+ "check": "npm run lint && npm test",
13
+ "prepack": "npm run check && npm run lib"
10
14
  },
11
15
  "dependencies": {
12
16
  "@subiz/ajax": "^3.0.22",
@@ -16,8 +20,8 @@
16
20
  "core-js": "^3.50.0",
17
21
  "css-loader": "5.2.7",
18
22
  "file-loader": "^6.2.0",
19
- "less": "^4.6.4",
20
- "less-loader": "^12.3.2",
23
+ "less": "^4.9.1",
24
+ "less-loader": "^13.0.0",
21
25
  "preact": "10.19.3"
22
26
  },
23
27
  "devDependencies": {
@@ -28,7 +32,10 @@
28
32
  "@babel/preset-env": "^7.29.7",
29
33
  "babel-loader": "^10.1.1",
30
34
  "babel-plugin-syntax-jsx": "^6.18.0",
35
+ "eslint": "^10.11.0",
36
+ "globals": "^17.13.0",
31
37
  "html-webpack-plugin": "^5.6.8",
38
+ "jsdom": "^30.1.1",
32
39
  "puppeteer": "^25.12.0",
33
40
  "tape": "^5.10.2",
34
41
  "webpack": "^5.111.1",
@@ -1,12 +1,13 @@
1
1
  import sb from '../util/index.js'
2
2
  import {h} from 'preact'
3
+ import {safeUrl} from '../util/url.js'
3
4
 
4
5
  export default function Buttons(props) {
5
6
  if (sb.size(props.buttons) === 0) return null
6
7
  var $buttons = sb.map(props.buttons, (button, index) => {
7
8
  if (button.type === 'call_button') {
8
9
  return (
9
- <a class='url-button-attachment' href={'tel:' + button.phone_number} key={index} title={button.title}>
10
+ <a class='url-button-attachment' href={safeUrl('tel:' + button.phone_number)} key={index} title={button.title}>
10
11
  {props.t.i18n(button.i18n_title) || props.t.i18n(button.old_i18n_title) || button.title || `${props.t('call')} ${button.phone_number}`}
11
12
  </a>
12
13
  )
@@ -14,7 +15,7 @@ export default function Buttons(props) {
14
15
 
15
16
  if (button.type === 'url_button') {
16
17
  return (
17
- <a class='url-button-attachment' href={button.url} target='_blank' key={index} title={button.title}>
18
+ <a class='url-button-attachment' href={safeUrl(button.url) || undefined} target='_blank' rel='noopener noreferrer' key={index} title={button.title}>
18
19
  {props.t.i18n(button.i18n_title) || props.t.i18n(button.old_i18n_title) || button.title || button.url}
19
20
  </a>
20
21
  )
@@ -22,9 +23,9 @@ export default function Buttons(props) {
22
23
 
23
24
  if (button.type === 'postback_button') {
24
25
  return (
25
- <a class='url-button-attachment' href='javascript:;' onClick={(_) => props.onSelect(button)}>
26
+ <button type='button' class='url-button-attachment' key={index} onClick={(_) => props.onSelect(button)}>
26
27
  {props.t.i18n(button.i18n_title) || button.title}
27
- </a>
28
+ </button>
28
29
  )
29
30
  }
30
31
 
@@ -1,6 +1,7 @@
1
1
  import sb from '../util/index.js'
2
2
  import Emoji from './Emoji.js'
3
3
  var defaultBubble = require('../../assets/img/bubble_default.svg')
4
+ const dotChatIcon = require('../../assets/img/dot_chat.svg')
4
5
  let defaultAvatar = require('../../assets/img/default_avatar.png')
5
6
  import {Com} from '../common.js'
6
7
  import Block from './block.js'
@@ -378,30 +379,7 @@ export default class ChatButton extends Com {
378
379
  )
379
380
  }
380
381
 
381
- async prepareButtonImage() {
382
- let accountSetting = this.props.plugin.chatbox
383
- accountSetting = this.fixMissingSetting(accountSetting)
384
- let imgsrc =
385
- sb.get(accountSetting, 'desktop_button.background_image_url') ||
386
- sb.get(accountSetting, 'desktop_button.background_mask_url') ||
387
- defaultBubble
388
-
389
- if (this.props.store.isMobile()) {
390
- imgsrc =
391
- sb.get(accountSetting, 'mobile_button.background_image_url') ||
392
- sb.get(accountSetting, 'mobile_button.background_mask_url') ||
393
- imgsrc
394
- }
395
-
396
- imgsrc = this.props.store.replaceFileUrl(imgsrc)
397
- await sb.preloadImg(imgsrc)
398
- this.setState({ready: true})
399
- this.readyGate.open()
400
- }
401
-
402
- renderButton() {
403
- if (typeof sbz_info != 'undefined') sbz_info.render_chatbox_button = sb.Date.now()
404
-
382
+ getButtonAppearance() {
405
383
  let accountSetting = this.props.plugin.chatbox
406
384
  accountSetting = this.fixMissingSetting(accountSetting)
407
385
  let imgsrc =
@@ -433,24 +411,94 @@ export default class ChatButton extends Com {
433
411
  }
434
412
  }
435
413
 
414
+ const imageUrl = (url) => {
415
+ const asset = normalizeButtonImageUrl(this.props.store.replaceFileUrl(url))
416
+ return asset === dotChatIcon ? asset : addVersionParam(asset)
417
+ }
418
+ imgsrc = imageUrl(imgsrc)
419
+ imgurl = imageUrl(imgurl)
420
+ maskurl = imageUrl(maskurl)
421
+ const defaultMask = !imgurl && !maskurl ? DEF_BG_URL : ''
422
+ const urls = [
423
+ ...new Set([imgsrc, imgurl && encodeURI(imgurl), maskurl && encodeURI(maskurl), defaultMask].filter(Boolean)),
424
+ ]
425
+ return {
426
+ imgsrc,
427
+ imgurl,
428
+ maskurl,
429
+ background,
430
+ position,
431
+ imgwidth,
432
+ imgheight,
433
+ urls,
434
+ maskAsset: maskurl ? encodeURI(maskurl) : defaultMask,
435
+ key: JSON.stringify([urls, maskurl, defaultMask]),
436
+ }
437
+ }
438
+
439
+ componentDidUpdate() {
440
+ if (this.getButtonAppearance().key !== this._buttonImageKey) this.prepareButtonImage()
441
+ }
442
+
443
+ async prepareButtonImage() {
444
+ const appearance = this.getButtonAppearance()
445
+ const version = (this._buttonImageVersion = (this._buttonImageVersion || 0) + 1)
446
+ this._buttonImageKey = appearance.key
447
+ this.readyGate.close()
448
+ const [loaded, mask] = await Promise.all([
449
+ Promise.all(appearance.urls.filter((url) => url !== appearance.maskAsset).map((url) => preloadButtonImage(url))),
450
+ appearance.maskAsset ? prepareButtonMask(appearance.maskAsset) : Promise.resolve(null),
451
+ ])
452
+ const stale = () => !this._isMounted || this._buttonImageVersion !== version
453
+ if (stale()) {
454
+ if (mask) URL.revokeObjectURL(mask)
455
+ return
456
+ }
457
+ const fallback = loaded.some((success) => !success) || (!!appearance.maskAsset && !mask)
458
+ const ready = !fallback || (await preloadButtonImage(defaultBubble))
459
+ if (stale()) {
460
+ if (mask) URL.revokeObjectURL(mask)
461
+ return
462
+ }
463
+ if (this._buttonMaskUrl) URL.revokeObjectURL(this._buttonMaskUrl)
464
+ this._buttonMaskUrl = mask
465
+ // Commit the hidden style before revealing even when images are cached.
466
+ // Otherwise older browsers can paint only the final state and skip the transition.
467
+ if (this._buttonElement) window.getComputedStyle(this._buttonElement).transform
468
+ this.setState({ready, readyImageKey: appearance.key, fallbackButtonImage: fallback, buttonMaskUrl: mask})
469
+ this.readyGate.open()
470
+ }
471
+
472
+ componentWillUnmount() {
473
+ if (this._buttonMaskUrl) URL.revokeObjectURL(this._buttonMaskUrl)
474
+ if (this._animateTimeout) clearTimeout(this._animateTimeout)
475
+ }
476
+
477
+ renderButton() {
478
+ if (typeof sbz_info != 'undefined') sbz_info.render_chatbox_button = sb.Date.now()
479
+ const appearance = this.getButtonAppearance()
480
+ let {imgsrc, imgurl, maskurl, background, position, imgwidth, imgheight} = appearance
481
+ // Never attach a remote CSS mask while the local copy is being prepared.
482
+ if (appearance.maskAsset) {
483
+ maskurl = this.state.readyImageKey === appearance.key ? this.state.buttonMaskUrl : ''
484
+ if (imgsrc === appearance.maskAsset) imgsrc = maskurl || ''
485
+ }
486
+ if (this.state.readyImageKey === appearance.key && this.state.fallbackButtonImage) {
487
+ imgsrc = imgurl = defaultBubble
488
+ maskurl = ''
489
+ }
490
+
436
491
  let whitebgcls = 'chat-button--white-bg'
437
492
  if (!imgurl && maskurl) whitebgcls += ' chat-button--white-bg--mask-only'
438
493
  let imgcontainerstyle = ''
439
494
 
440
- imgurl = this.props.store.replaceFileUrl(imgurl)
441
- imgsrc = this.props.store.replaceFileUrl(imgsrc)
442
- maskurl = this.props.store.replaceFileUrl(maskurl)
443
- imgurl = addVersionParam(imgurl)
444
- imgsrc = addVersionParam(imgsrc)
445
- maskurl = addVersionParam(maskurl)
446
-
447
495
  if (!imgurl && !maskurl) {
448
496
  whitebgcls += ' chat-button--white-bg--mask-only'
449
497
  imgcontainerstyle = `
450
- -webkit-mask-image: url("${encodeURI(DEF_BG_URL)}") !important;
451
- -o-mask-image: url("${encodeURI(DEF_BG_URL)}") !important;
452
- -moz-mask-image: url("${encodeURI(DEF_BG_URL)}") !important;
453
- mask-image: url("${encodeURI(DEF_BG_URL)}") !important;
498
+ -webkit-mask-image: none !important;
499
+ -o-mask-image: none !important;
500
+ -moz-mask-image: none !important;
501
+ mask-image: none !important;
454
502
  height: ${imgheight}px !important;
455
503
  width: ${imgwidth}px !important;
456
504
  background: ${background} !important;
@@ -509,16 +557,23 @@ export default class ChatButton extends Com {
509
557
 
510
558
  `
511
559
 
512
- if (!this.state.ready) cls += ' hidden'
560
+ if (!this.state.ready || this.state.readyImageKey !== appearance.key) cls += ' hidden'
513
561
 
514
562
  return (
515
- <div class={cls} onClick={(e) => this.onClick(e)}>
563
+ <button
564
+ ref={(element) => {this._buttonElement = element}}
565
+ type='button'
566
+ class={cls}
567
+ aria-label={this.props.t('chat')}
568
+ aria-expanded={!this.props.isShow}
569
+ onClick={(e) => this.onClick(e)}
570
+ >
516
571
  <div class={whitebgcls} style={whiteBgStyle}></div>
517
572
  <div class='chat-button--img-container' style={imgcontainerstyle}>
518
573
  <img width='70' height='70' alt='chat button' class='chat-button--img' src={imgsrc} />
519
574
  </div>
520
575
  {this.renderUnread()}
521
- </div>
576
+ </button>
522
577
  )
523
578
  }
524
579
 
@@ -551,7 +606,7 @@ export default class ChatButton extends Com {
551
606
  }
552
607
  return defmsg
553
608
  }
554
- return <Block block={block} />
609
+ return <Block block={block} store={this.props.store} t={this.props.t} />
555
610
  }
556
611
 
557
612
  let message = sb.get(ev, 'data.message.text', defmsg)
@@ -582,6 +637,11 @@ function randomAgent(agentM) {
582
637
 
583
638
  const Ms30Mins = 30 * 60 * 1000
584
639
 
640
+ function normalizeButtonImageUrl(url) {
641
+ if (/^https:\/\/(?:storage\.googleapis\.com\/public-gcs\.subiz-cdn\.com|public-gcs\.subiz-cdn\.com|vcdn\.subiz-cdn\.com)\/dashboard-v4\/public\/img\/bubble\/dot_chat\.svg(?=$|[?#])/.test(url)) return dotChatIcon
642
+ return url
643
+ }
644
+
585
645
  // v=5 trick to clear cache
586
646
  const DEF_BG_URL = 'https://vcdn.subiz-cdn.com/file/bubble_11.svg?v=5'
587
647
  const DEF_BG = 'linear-gradient(280.16deg, #0053d3 22.03%, #0265ff 78.2%)'
@@ -600,3 +660,37 @@ function addVersionParam(url, version = 8) {
600
660
  return hash ? newUrl + '#' + hash : newUrl
601
661
  }
602
662
  }
663
+
664
+ // Use the exact rendered URL, including its version, for both images and masks.
665
+ function preloadButtonImage(src, {crossOrigin = false} = {}) {
666
+ return new Promise((resolve) => {
667
+ const image = new Image()
668
+ if (crossOrigin) image.crossOrigin = 'anonymous'
669
+ image.onerror = () => resolve(false)
670
+ image.onload = async () => {
671
+ try {
672
+ if (image.decode) await image.decode()
673
+ resolve(true)
674
+ } catch (_) {
675
+ resolve(false)
676
+ }
677
+ }
678
+ image.src = src
679
+ })
680
+ }
681
+
682
+ // CSS masks use a separate CORS image request. Pin the loaded bytes locally so
683
+ // revealing the white backing never races another network request by CSS.
684
+ async function prepareButtonMask(src) {
685
+ let url
686
+ try {
687
+ const response = await fetch(src)
688
+ if (!response.ok) return null
689
+ url = URL.createObjectURL(await response.blob())
690
+ if (await preloadButtonImage(url)) return url
691
+ } catch (_) {
692
+ // Use the bundled fallback if the custom mask cannot be loaded.
693
+ }
694
+ if (url) URL.revokeObjectURL(url)
695
+ return null
696
+ }
@@ -28,11 +28,31 @@ export default class Event extends Com {
28
28
  this.props.store.onConvoEvent(this, (ev) => this.onEvent(ev))
29
29
  this.refForm = createRef()
30
30
  this.state = {pongs: sb.get(this.props.event, 'ev.data.message.pongs') || []}
31
+ const ev = this.props.event.ev
32
+ const age = (this.props.store.now ? this.props.store.now() : sb.Date.now()) - ev.created
33
+ // Capture this once: acknowledgement keeps the same component and animation.
34
+ this.animateSend = !props.isHistory && ev.type === 'message_sent' &&
35
+ sb.get(ev, 'by.type') === 'user' && ev.id === sb.get(ev, 'source_event_id') &&
36
+ age >= 0 && age < 2000
31
37
  this.textMessageRef = createRef()
32
38
  this.fileAttachmentRef = createRef()
33
39
  this.imageAttachmentRef = createRef()
34
40
  }
35
41
 
42
+ componentDidMount() {
43
+ if (!this.animateSend) return
44
+ // Paint the starting position before transitioning to the resting position.
45
+ this.sendAnimationFrame = window.requestAnimationFrame(() => {
46
+ this.sendAnimationFrame = window.requestAnimationFrame(() => {
47
+ if (this._isMounted) this.setState({sendEntered: true})
48
+ })
49
+ })
50
+ }
51
+
52
+ componentWillUnmount() {
53
+ if (this.sendAnimationFrame) window.cancelAnimationFrame(this.sendAnimationFrame)
54
+ }
55
+
36
56
  focusSourceEventFromQuote = (ev) => {
37
57
  let $textMessage = this.textMessageRef.current
38
58
  if ($textMessage && typeof $textMessage.focusSourceEventFromQuote === 'function') {
@@ -90,6 +110,7 @@ export default class Event extends Com {
90
110
  }
91
111
 
92
112
  chooseQuickReply(reply) {
113
+ if (this.props.isHistory) return
93
114
  let ev = sb.get(this.props.event, 'ev', {})
94
115
  let message = {text: reply.title, format: 'plaintext'}
95
116
  if (reply.payload) {
@@ -107,6 +128,7 @@ export default class Event extends Com {
107
128
  }
108
129
 
109
130
  choosePostbackBtn(button) {
131
+ if (this.props.isHistory) return
110
132
  let ev = sb.get(this.props.event, 'ev', {})
111
133
  let message = {text: button.title, format: 'plaintext'}
112
134
  if (button.payload) {
@@ -252,6 +274,14 @@ export default class Event extends Com {
252
274
 
253
275
  renderMessageSent() {
254
276
  let ev = this.props.event.ev
277
+ if (sb.get(ev, 'data.message.deleted')) {
278
+ let byType = sb.get(ev, 'by.type') === 'user' ? 'user' : 'agent'
279
+ return (
280
+ <div class={'convo-event ' + byType + '-message'} data-ev-id={ev.id}>
281
+ <TextMessage ev={ev} t={this.props.t} store={this.props.store} />
282
+ </div>
283
+ )
284
+ }
255
285
  let attachments = sb.get(ev, 'data.message.attachments')
256
286
  let hasForm = sb.find(attachments, (att) => att.type === 'form')
257
287
  if (sb.isEmptyMessage(sb.get(ev, 'data.message'))) return null
@@ -312,7 +342,7 @@ export default class Event extends Com {
312
342
  ev={ev}
313
343
  />
314
344
  <OrderAttachments t={this.props.t} store={this.props.store} ev={ev} />
315
- <QuickReplies store={this.props.store} ev={ev} onSelect={(e) => this.chooseQuickReply(e)} />
345
+ {!this.props.isHistory && <QuickReplies store={this.props.store} ev={ev} onSelect={(e) => this.chooseQuickReply(e)} />}
316
346
  {this.renderReactions()}
317
347
  {this.renderAIDevNote()}
318
348
  </div>
@@ -321,14 +351,14 @@ export default class Event extends Com {
321
351
  )
322
352
  }
323
353
 
324
- if (sb.get(ev, 'data.message.idempotency_key') === ev.id && this.props.store.now() - ev.created < 900) {
354
+ if (sb.get(ev, 'source_event_id') === ev.id && this.props.store.now() - ev.created < 900) {
325
355
  sb.setTimeout(() => this.setState({refresh: this.state.refresh}), 1000)
326
356
  }
327
357
 
328
358
  // only show sending while the message is send too long (>300ms) but haven't returned from
329
359
  // the server
330
360
  let $dot = <div class='sending_dot'></div>
331
- if (this.props.store.now() - ev.created < 900 || sb.get(ev, 'data.message.idempotency_key') !== ev.id) {
361
+ if (this.props.store.now() - ev.created < 900 || sb.get(ev, 'source_event_id') !== ev.id) {
332
362
  $dot = <div class='sending_dot sending_dot__hide'></div>
333
363
  }
334
364
 
@@ -352,11 +382,12 @@ export default class Event extends Com {
352
382
  return (
353
383
  <div class='convo-event user-message' data-ev-id={ev.id}>
354
384
  {$sender}
355
- <div class='message-user__container'>
385
+ <div class={'message-user__container' + (this.animateSend && !this.props.isHistory ? ' message-user__container--send' + (this.state.sendEntered ? '' : ' message-user__container--enter') : '')}>
356
386
  {$dot}
357
387
  <TextMessage
358
388
  ref={this.textMessageRef}
359
389
  store={this.props.store}
390
+ t={this.props.t}
360
391
  ev={ev}
361
392
  refresh={this.state.refresh}
362
393
  onLink={this.props.onLink}
@@ -376,6 +407,7 @@ export default class Event extends Com {
376
407
  }
377
408
 
378
409
  async onSendReaction(action) {
410
+ if (this.props.isHistory) return
379
411
  if (this._reacting) return
380
412
  let ev = this.props.event.ev
381
413
  let cid = sb.get(ev, 'data.message.conversation_id')
@@ -423,6 +455,7 @@ export default class Event extends Com {
423
455
  }
424
456
 
425
457
  renderReactions() {
458
+ if (this.props.isHistory) return null
426
459
  if (this.props.store.isAIDevMode()) return null
427
460
  let cid = sb.get(this.props.event.ev, 'data.message.conversation_id', '')
428
461
  if (cid.startsWith('cslight')) return null
@@ -515,7 +548,7 @@ export default class Event extends Com {
515
548
  let byid = sb.get(ev, 'by.display_id', sb.get(ev, 'by.id')) || ''
516
549
  if (bytype === 'user') return sb.getUserDisplayName(this.props.store.matchUser())
517
550
  let acc = this.props.store.matchAccount()
518
- if (bytype === 'agent') return sb.getAgentDisplayName(acc, this.props.store.matchAgent(byid))
551
+ if (bytype === 'agent' || sb.get(ev, 'by.display_type') === 'agent') return sb.getAgentDisplayName(acc, this.props.store.matchAgent(byid))
519
552
  if (byid.startsWith('at')) return sb.getAgentDisplayName(acc, this.props.store.matchAgent(byid))
520
553
  if (bytype === 'bot') {
521
554
  let bot = this.props.store.matchBot()[byid]